| ... | ... | @@ -207,49 +207,53 @@ fn count(a: usize, b: usize, result: *usize) void { |
| 207 | 207 | result.* = sum; |
| 208 | 208 | } |
| 209 | 209 | |
| 210 | | test "Group cancelation" { |
| 211 | | const io = testing.io; |
| 210 | test "Group.cancel" { |
| 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; |
| 214 | | var results: [4]usize = .{ 0, 0, 0, 0 }; |
| 220 | fn sleepRecancel(io: Io, result: *usize) void { |
| 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 |
| 217 | | // longer so that it causes the unit test to be failed if not canceled. |
| 218 | | // https://codeberg.org/ziglang/zig/issues/30049 |
| 219 | | group.async(io, sleep, .{ io, &results[0] }); |
| 220 | | group.async(io, sleep, .{ io, &results[1] }); |
| 221 | | group.async(io, sleepUncancelable, .{ io, &results[2] }); |
| 222 | | group.async(io, sleepRecancel, .{ io, &results[3] }); |
| 228 | fn sleepUncancelable(io: Io, result: *usize) void { |
| 229 | const old_prot = io.swapCancelProtection(.blocked); |
| 230 | defer _ = io.swapCancelProtection(old_prot); |
| 231 | // Short sleep interval, because this one won't be canceled (that's the point!). |
| 232 | io.sleep(.fromMilliseconds(50), .awake) catch {}; |
| 233 | result.* = 1; |
| 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); |
| 227 | | } |
| 239 | var group: Io.Group = .init; |
| 240 | var results: [5]usize = @splat(0); |
| 228 | 241 | |
| 229 | | fn sleep(io: Io, result: *usize) error{Canceled}!void { |
| 230 | | defer result.* = 1; |
| 231 | | io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) { |
| 232 | | error.Canceled => |e| return e, |
| 233 | | else => {}, |
| 242 | group.concurrent(io, global.sleep, .{ io, &results[0] }) catch |err| switch (err) { |
| 243 | error.ConcurrencyUnavailable => return error.SkipZigTest, |
| 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 { |
| 238 | | const old_prot = io.swapCancelProtection(.blocked); |
| 239 | | defer _ = io.swapCancelProtection(old_prot); |
| 240 | | io.sleep(.fromMilliseconds(1), .awake) catch {}; |
| 241 | | result.* = 1; |
| 242 | | } |
| 251 | group.cancel(io); |
| 243 | 252 | |
| 244 | | fn sleepRecancel(io: Io, result: *usize) void { |
| 245 | | io.sleep(.fromMilliseconds(1), .awake) catch |err| switch (err) { |
| 246 | | error.Canceled => io.recancel(), |
| 247 | | else => {}, |
| 248 | | }; |
| 249 | | result.* = 1; |
| 253 | try testing.expectEqualSlices(usize, &.{ 1, 1, 1, 1, 1 }, &results); |
| 250 | 254 | } |
| 251 | 255 | |
| 252 | | test "Group concurrent" { |
| 256 | test "Group.concurrent" { |
| 253 | 257 | const io = testing.io; |
| 254 | 258 | |
| 255 | 259 | var group: Io.Group = .init; |
| ... | ... | @@ -488,3 +492,75 @@ test "swapCancelProtection" { |
| 488 | 492 | // Because it reached the `set`, it should be too late for `sleepThenSet` to see `error.Canceled`. |
| 489 | 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 | } |