| ... | @@ -207,49 +207,53 @@ fn count(a: usize, b: usize, result: *usize) void { | ... | @@ -207,49 +207,53 @@ fn count(a: usize, b: usize, result: *usize) void { |
| 207 | result.* = sum; | 207 | result.* = sum; |
| 208 | } | 208 | } |
| 209 | | 209 | |
| 210 | test "Group cancelation" { | 210 | test "Group.cancel" { |
| 211 | const io = testing.io; | 211 | const global = struct { |
| | 212 | fn sleep(io: Io, result: *usize) Io.Cancelable!void { |
| | 213 | defer result.* = 1; |
| | 214 | io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { |
| | 215 | error.Canceled => |e| return e, |
| | 216 | else => {}, |
| | 217 | }; |
| | 218 | } |
| 212 | | 219 | |
| 213 | var group: Io.Group = .init; | 220 | fn sleepRecancel(io: Io, result: *usize) void { |
| 214 | var results: [4]usize = .{ 0, 0, 0, 0 }; | 221 | io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { |
| | 222 | error.Canceled => io.recancel(), |
| | 223 | else => {}, |
| | 224 | }; |
| | 225 | result.* = 1; |
| | 226 | } |
| 215 | | 227 | |
| 216 | // TODO when robust cancelation is available, make the sleep timeouts much | 228 | fn sleepUncancelable(io: Io, result: *usize) void { |
| 217 | // longer so that it causes the unit test to be failed if not canceled. | 229 | const old_prot = io.swapCancelProtection(.blocked); |
| 218 | // https://codeberg.org/ziglang/zig/issues/30049 | 230 | defer _ = io.swapCancelProtection(old_prot); |
| 219 | group.async(io, sleep, .{ io, &results[0] }); | 231 | // Short sleep interval, because this one won't be canceled (that's the point!). |
| 220 | group.async(io, sleep, .{ io, &results[1] }); | 232 | io.sleep(.fromMilliseconds(50), .awake) catch {}; |
| 221 | group.async(io, sleepUncancelable, .{ io, &results[2] }); | 233 | result.* = 1; |
| 222 | group.async(io, sleepRecancel, .{ io, &results[3] }); | 234 | } |
| | 235 | }; |
| 223 | | 236 | |
| 224 | group.cancel(io); | 237 | const io = testing.io; |
| 225 | | 238 | |
| 226 | try testing.expectEqualSlices(usize, &.{ 1, 1, 1, 1 }, &results); | 239 | var group: Io.Group = .init; |
| 227 | } | 240 | var results: [5]usize = @splat(0); |
| 228 | | 241 | |
| 229 | fn sleep(io: Io, result: *usize) error{Canceled}!void { | 242 | group.concurrent(io, global.sleep, .{ io, &results[0] }) catch |err| switch (err) { |
| 230 | defer result.* = 1; | 243 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| 231 | io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) { | | |
| 232 | error.Canceled => |e| return e, | | |
| 233 | else => {}, | | |
| 234 | }; | 244 | }; |
| 235 | } | 245 | try group.concurrent(io, global.sleep, .{ io, &results[1] }); |
| | 246 | try group.concurrent(io, global.sleepRecancel, .{ io, &results[2] }); |
| | 247 | try group.concurrent(io, global.sleepUncancelable, .{ io, &results[3] }); |
| | 248 | // Because this one doesn't block until canceled, it is safe to run asynchronously. |
| | 249 | group.async(io, global.sleepUncancelable, .{ io, &results[4] }); |
| 236 | | 250 | |
| 237 | fn sleepUncancelable(io: Io, result: *usize) void { | 251 | group.cancel(io); |
| 238 | const old_prot = io.swapCancelProtection(.blocked); | | |
| 239 | defer _ = io.swapCancelProtection(old_prot); | | |
| 240 | io.sleep(.fromMilliseconds(1), .awake) catch {}; | | |
| 241 | result.* = 1; | | |
| 242 | } | | |
| 243 | | 252 | |
| 244 | fn sleepRecancel(io: Io, result: *usize) void { | 253 | try testing.expectEqualSlices(usize, &.{ 1, 1, 1, 1, 1 }, &results); |
| 245 | io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) { | | |
| 246 | error.Canceled => io.recancel(), | | |
| 247 | else => {}, | | |
| 248 | }; | | |
| 249 | result.* = 1; | | |
| 250 | } | 254 | } |
| 251 | | 255 | |
| 252 | test "Group concurrent" { | 256 | test "Group.concurrent" { |
| 253 | const io = testing.io; | 257 | const io = testing.io; |
| 254 | | 258 | |
| 255 | var group: Io.Group = .init; | 259 | var group: Io.Group = .init; |
| ... | @@ -488,3 +492,75 @@ test "swapCancelProtection" { | ... | @@ -488,3 +492,75 @@ test "swapCancelProtection" { |
| 488 | // Because it reached the `set`, it should be too late for `sleepThenSet` to see `error.Canceled`. | 492 | // Because it reached the `set`, it should be too late for `sleepThenSet` to see `error.Canceled`. |
| 489 | try set_future.cancel(io); | 493 | try set_future.cancel(io); |
| 490 | } | 494 | } |
| | 495 | |
| | 496 | test "cancel futex wait" { |
| | 497 | const global = struct { |
| | 498 | fn blockUntilCanceled(io: Io) void { |
| | 499 | while (true) io.futexWait(u32, &0, 0) catch |err| switch (err) { |
| | 500 | error.Canceled => return, |
| | 501 | }; |
| | 502 | } |
| | 503 | }; |
| | 504 | |
| | 505 | const io = std.testing.io; |
| | 506 | |
| | 507 | var future = io.concurrent(global.blockUntilCanceled, .{io}) catch |err| switch (err) { |
| | 508 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| | 509 | }; |
| | 510 | defer future.cancel(io); |
| | 511 | |
| | 512 | // Give the task some time to start so that we cancel while it is blocked. |
| | 513 | try io.sleep(.fromMilliseconds(20), .awake); |
| | 514 | } |
| | 515 | |
| | 516 | test "cancel sleep" { |
| | 517 | const global = struct { |
| | 518 | fn blockUntilCanceled(io: Io) void { |
| | 519 | while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { |
| | 520 | error.Canceled => return, |
| | 521 | error.UnsupportedClock => @panic("unsupported clock"), |
| | 522 | error.Unexpected => @panic("unexpected"), |
| | 523 | }; |
| | 524 | } |
| | 525 | }; |
| | 526 | |
| | 527 | const io = std.testing.io; |
| | 528 | |
| | 529 | var future = io.concurrent(global.blockUntilCanceled, .{io}) catch |err| switch (err) { |
| | 530 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| | 531 | }; |
| | 532 | defer future.cancel(io); |
| | 533 | |
| | 534 | // Give the task some time to start so that we cancel while it is blocked. |
| | 535 | try io.sleep(.fromMilliseconds(20), .awake); |
| | 536 | } |
| | 537 | |
| | 538 | test "tasks spawned in group after Group.cancel are canceled" { |
| | 539 | const global = struct { |
| | 540 | fn waitThenSpawn(io: Io, group: *Io.Group) void { |
| | 541 | _ = io.swapCancelProtection(.blocked); |
| | 542 | group.concurrent(io, blockUntilCanceled, .{io}) catch {}; |
| | 543 | io.sleep(.fromMilliseconds(10), .awake) catch unreachable; |
| | 544 | group.concurrent(io, blockUntilCanceled, .{io}) catch {}; |
| | 545 | group.async(io, blockUntilCanceled, .{io}); |
| | 546 | } |
| | 547 | fn blockUntilCanceled(io: Io) void { |
| | 548 | while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { |
| | 549 | error.Canceled => return, |
| | 550 | error.UnsupportedClock => @panic("unsupported clock"), |
| | 551 | error.Unexpected => @panic("unexpected"), |
| | 552 | }; |
| | 553 | } |
| | 554 | }; |
| | 555 | |
| | 556 | const io = std.testing.io; |
| | 557 | |
| | 558 | var group: Io.Group = .init; |
| | 559 | defer group.cancel(io); |
| | 560 | |
| | 561 | group.concurrent(io, global.blockUntilCanceled, .{io}) catch |err| switch (err) { |
| | 562 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| | 563 | }; |
| | 564 | try io.sleep(.fromMilliseconds(10), .awake); // let that first sleep start up |
| | 565 | try group.concurrent(io, global.waitThenSpawn, .{ io, &group }); |
| | 566 | } |