ReviewOS

also looking at this

home-lang/home

fix(async): preserve cancellation and add revision-aware job gate

#914
Merged chris wants to merge feat/camera-jobs into main
6 files +217 -20
packages/async/src/job_state.zigadded+44-0
Changes to packages/async/src/job_state.zig
@@ -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.
3const std = ;
4pub const State = enum(u8) { Pending, Running, Completed, Failed, Cancelled };
5pub fn terminal(state: State) bool {
6 return switch (state) {
7 .Completed, .Failed, .Cancelled => true,
8 else => false,
9 };
10}
11pub 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.
19pub 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}
24test "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}
32test "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}