also looking at this
fix(async): preserve cancellation and add revision-aware job gate
#914
6 files
+217
-20
| @@ -0,0 +1,44 @@ | ||
| 1 | //! Shared atomic task transitions. Cancellation cannot be undone by an in-flight | |
| 2 | //! poll. Terminal states are immutable. Storage must outlive the polling owner. | |
| 3 | const std = ; | |
| 4 | pub const State = enum(u8) { Pending, Running, Completed, Failed, Cancelled }; | |
| 5 | pub fn terminal(state: State) bool { | |
| 6 | return switch (state) { | |
| 7 | .Completed, .Failed, .Cancelled => true, | |
| 8 | else => false, | |
| 9 | }; | |
| 10 | } | |
| 11 | pub fn cancel(state: *std.atomic.Value(State)) bool { | |
| 12 | var current = state.load(.acquire); | |
| 13 | while (!terminal(current)) { | |
| 14 | current = state.cmpxchgStrong(current, .Cancelled, .acq_rel, .acquire) orelse return true; | |
| 15 | } | |
| 16 | return false; | |
| 17 | } | |
| 18 | /// Called only by the poll owner, after publishing any result payload. | |
| 19 | pub fn finishPoll(state: *std.atomic.Value(State), ready: bool) bool { | |
| 20 | const next: State = if (ready) .Completed else .Pending; | |
| 21 | if (state.cmpxchgStrong(.Running, next, .release, .monotonic)) |actual| return terminal(actual); | |
| 22 | return ready; | |
| 23 | } | |
| 24 | test "cancel between poll reservation and completion cannot be overwritten" { | |
| 25 | for ([_]bool{ false, true }) |ready| { | |
| 26 | var state = std.atomic.Value(State).init(.Running); | |
| 27 | try std.testing.expect(cancel(&state)); | |
| 28 | try std.testing.expect(finishPoll(&state, ready)); | |
| 29 | try std.testing.expectEqual(State.Cancelled, state.load(.acquire)); | |
| 30 | } | |
| 31 | } | |
| 32 | test "completion and failure are immutable and pending polls remain resumable" { | |
| 33 | for ([_]State{ .Completed, .Failed, .Cancelled }) |terminal_state| { | |
| 34 | var state = std.atomic.Value(State).init(terminal_state); | |
| 35 | try std.testing.expect(!cancel(&state)); | |
| 36 | try std.testing.expectEqual(terminal_state, state.load(.acquire)); | |
| 37 | } | |
| 38 | var state = std.atomic.Value(State).init(.Running); | |
| 39 | try std.testing.expect(!finishPoll(&state, false)); | |
| 40 | try std.testing.expectEqual(State.Pending, state.load(.acquire)); | |
| 41 | state.store(.Running, .release); | |
| 42 | try std.testing.expect(finishPoll(&state, true)); | |
| 43 | try std.testing.expectEqual(State.Completed, state.load(.acquire)); | |
| 44 | } | |