authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-09-29 14:04:07-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-02 16:30:59-07:00
loga37c0bca2248e5d2e18c4855ee8d8d17bf26aa26
treeead0450f58070aa43a6fa3953976ff2e6a8c4f52
parent3c9fdf810f6f1517193786998ac5efe9b2b2c276

std.Io.Threaded: implement Group.cancel


2 files changed, 191 insertions(+), 174 deletions(-)

lib/std/Io.zig+15-7
......@@ -736,10 +736,9 @@ pub fn Future(Result: type) type {
736736 any_future: ?*AnyFuture,
737737 result: Result,
738738
739 /// Equivalent to `await` but sets a flag observable to application
740 /// code that cancellation has been requested.
739 /// Equivalent to `await` but places a cancellation request.
741740 ///
742 /// Idempotent.
741 /// Idempotent. Not threadsafe.
743742 pub fn cancel(f: *@This(), io: Io) Result {
744743 const any_future = f.any_future orelse return f.result;
745744 io.vtable.cancel(io.userdata, any_future, @ptrCast((&f.result)[0..1]), .of(Result));
......@@ -747,6 +746,7 @@ pub fn Future(Result: type) type {
747746 return f.result;
748747 }
749748
749 /// Idempotent. Not threadsafe.
750750 pub fn await(f: *@This(), io: Io) Result {
751751 const any_future = f.any_future orelse return f.result;
752752 io.vtable.await(io.userdata, any_future, @ptrCast((&f.result)[0..1]), .of(Result));
......@@ -759,8 +759,9 @@ pub fn Future(Result: type) type {
759759pub const Group = struct {
760760 state: usize,
761761 context: ?*anyopaque,
762 token: ?*anyopaque,
762763
763 pub const init: Group = .{ .state = 0, .context = null };
764 pub const init: Group = .{ .state = 0, .context = null, .token = null };
764765
765766 /// Calls `function` with `args` asynchronously. The resource spawned is
766767 /// owned by the group.
......@@ -771,7 +772,7 @@ pub const Group = struct {
771772 /// deinitialized.
772773 ///
773774 /// See also:
774 /// * `async`
775 /// * `Io.async`
775776 /// * `concurrent`
776777 pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void {
777778 const Args = @TypeOf(args);
......@@ -784,14 +785,21 @@ pub const Group = struct {
784785 io.vtable.groupAsync(io.userdata, g, @ptrCast((&args)[0..1]), .of(Args), TypeErased.start);
785786 }
786787
787 /// Idempotent.
788 /// Blocks until all tasks of the group finish.
789 ///
790 /// Idempotent. Not threadsafe.
788791 pub fn wait(g: *Group, io: Io) void {
789792 io.vtable.groupWait(io.userdata, g);
790793 }
791794
792 /// Idempotent.
795 /// Equivalent to `wait` but requests cancellation on all tasks owned by
796 /// the group.
797 ///
798 /// Idempotent. Not threadsafe.
793799 pub fn cancel(g: *Group, io: Io) void {
800 if (g.token == null) return;
794801 io.vtable.groupCancel(io.userdata, g);
802 assert(g.token == null);
795803 }
796804};
797805
lib/std/Io/Threaded.zig+176-167
......@@ -10,6 +10,7 @@ const Allocator = std.mem.Allocator;
1010const assert = std.debug.assert;
1111const posix = std.posix;
1212const Io = std.Io;
13const ResetEvent = std.Thread.ResetEvent;
1314
1415/// Thread-safe.
1516allocator: Allocator,
......@@ -20,9 +21,9 @@ join_requested: bool = false,
2021threads: std.ArrayListUnmanaged(std.Thread),
2122stack_size: usize,
2223cpu_count: std.Thread.CpuCountError!usize,
23parallel_count: usize,
24concurrent_count: usize,
2425
25threadlocal var current_closure: ?*AsyncClosure = null;
26threadlocal var current_closure: ?*Closure = null;
2627
2728const max_iovecs_len = 8;
2829const splat_buffer_size = 64;
......@@ -31,12 +32,33 @@ comptime {
3132 assert(max_iovecs_len <= posix.IOV_MAX);
3233}
3334
34pub const Runnable = struct {
35const Closure = struct {
3536 start: Start,
3637 node: std.SinglyLinkedList.Node = .{},
37 is_parallel: bool,
38 cancel_tid: std.Thread.Id,
39 /// Whether this task bumps minimum number of threads in the pool.
40 is_concurrent: bool,
41
42 const Start = *const fn (*Closure) void;
43
44 const canceling_tid: std.Thread.Id = switch (@typeInfo(std.Thread.Id)) {
45 .int => |int_info| switch (int_info.signedness) {
46 .signed => -1,
47 .unsigned => std.math.maxInt(std.Thread.Id),
48 },
49 .pointer => @ptrFromInt(std.math.maxInt(usize)),
50 else => @compileError("unsupported std.Thread.Id: " ++ @typeName(std.Thread.Id)),
51 };
3852
39 pub const Start = *const fn (*Runnable) void;
53 fn requestCancel(closure: *Closure) void {
54 switch (@atomicRmw(std.Thread.Id, &closure.cancel_tid, .Xchg, canceling_tid, .acq_rel)) {
55 0, canceling_tid => {},
56 else => |tid| switch (builtin.os.tag) {
57 .linux => _ = std.os.linux.tgkill(std.os.linux.getpid(), @bitCast(tid), posix.SIG.IO),
58 else => {},
59 },
60 }
61 }
4062};
4163
4264pub const InitError = std.Thread.CpuCountError || Allocator.Error;
......@@ -47,7 +69,7 @@ pub fn init(gpa: Allocator) Pool {
4769 .threads = .empty,
4870 .stack_size = std.Thread.SpawnConfig.default_stack_size,
4971 .cpu_count = std.Thread.getCpuCount(),
50 .parallel_count = 0,
72 .concurrent_count = 0,
5173 };
5274 if (pool.cpu_count) |n| {
5375 pool.threads.ensureTotalCapacityPrecise(gpa, n - 1) catch {};
......@@ -78,14 +100,15 @@ fn worker(pool: *Pool) void {
78100 defer pool.mutex.unlock();
79101
80102 while (true) {
81 while (pool.run_queue.popFirst()) |run_node| {
103 while (pool.run_queue.popFirst()) |closure_node| {
82104 pool.mutex.unlock();
83 const runnable: *Runnable = @fieldParentPtr("node", run_node);
84 runnable.start(runnable);
105 const closure: *Closure = @fieldParentPtr("node", closure_node);
106 const is_concurrent = closure.is_concurrent;
107 closure.start(closure);
85108 pool.mutex.lock();
86 if (runnable.is_parallel) {
109 if (is_concurrent) {
87110 // TODO also pop thread and join sometimes
88 pool.parallel_count -= 1;
111 pool.concurrent_count -= 1;
89112 }
90113 }
91114 if (pool.join_requested) break;
......@@ -154,97 +177,71 @@ pub fn io(pool: *Pool) Io {
154177 };
155178}
156179
180/// Trailing data:
181/// 1. context
182/// 2. result
157183const AsyncClosure = struct {
184 closure: Closure,
158185 func: *const fn (context: *anyopaque, result: *anyopaque) void,
159 runnable: Runnable,
160 reset_event: std.Thread.ResetEvent,
161 select_condition: ?*std.Thread.ResetEvent,
162 cancel_tid: std.Thread.Id,
163 context_offset: usize,
186 reset_event: ResetEvent,
187 select_condition: ?*ResetEvent,
188 context_alignment: std.mem.Alignment,
164189 result_offset: usize,
190 /// Whether the task has a return type with nonzero bits.
191 has_result: bool,
165192
166 const done_reset_event: *std.Thread.ResetEvent = @ptrFromInt(@alignOf(std.Thread.ResetEvent));
193 const done_reset_event: *ResetEvent = @ptrFromInt(@alignOf(ResetEvent));
167194
168 const canceling_tid: std.Thread.Id = switch (@typeInfo(std.Thread.Id)) {
169 .int => |int_info| switch (int_info.signedness) {
170 .signed => -1,
171 .unsigned => std.math.maxInt(std.Thread.Id),
172 },
173 .pointer => @ptrFromInt(std.math.maxInt(usize)),
174 else => @compileError("unsupported std.Thread.Id: " ++ @typeName(std.Thread.Id)),
175 };
176
177 fn start(runnable: *Runnable) void {
178 const closure: *AsyncClosure = @alignCast(@fieldParentPtr("runnable", runnable));
195 fn start(closure: *Closure) void {
196 const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure));
179197 const tid = std.Thread.getCurrentId();
180 if (@cmpxchgStrong(
181 std.Thread.Id,
182 &closure.cancel_tid,
183 0,
184 tid,
185 .acq_rel,
186 .acquire,
187 )) |cancel_tid| {
188 assert(cancel_tid == canceling_tid);
189 closure.reset_event.set();
190 return;
198 if (@cmpxchgStrong(std.Thread.Id, &closure.cancel_tid, 0, tid, .acq_rel, .acquire)) |cancel_tid| {
199 assert(cancel_tid == Closure.canceling_tid);
200 // Even though we already know the task is canceled, we must still
201 // run the closure in order to make the return value valid - that
202 // is, unless the result is zero bytes!
203 if (!ac.has_result) {
204 ac.reset_event.set();
205 return;
206 }
191207 }
192208 current_closure = closure;
193 closure.func(closure.contextPointer(), closure.resultPointer());
209 ac.func(ac.contextPointer(), ac.resultPointer());
194210 current_closure = null;
195 if (@cmpxchgStrong(
196 std.Thread.Id,
197 &closure.cancel_tid,
198 tid,
199 0,
200 .acq_rel,
201 .acquire,
202 )) |cancel_tid| assert(cancel_tid == canceling_tid);
203
204 if (@atomicRmw(
205 ?*std.Thread.ResetEvent,
206 &closure.select_condition,
207 .Xchg,
208 done_reset_event,
209 .release,
210 )) |select_reset| {
211
212 // In case a cancel happens after successful task completion, prevents
213 // signal from being delivered to the thread in `requestCancel`.
214 if (@cmpxchgStrong(std.Thread.Id, &closure.cancel_tid, tid, 0, .acq_rel, .acquire)) |cancel_tid| {
215 assert(cancel_tid == Closure.canceling_tid);
216 }
217
218 if (@atomicRmw(?*ResetEvent, &ac.select_condition, .Xchg, done_reset_event, .release)) |select_reset| {
211219 assert(select_reset != done_reset_event);
212220 select_reset.set();
213221 }
214 closure.reset_event.set();
215 }
216
217 fn contextOffset(context_alignment: std.mem.Alignment) usize {
218 return context_alignment.forward(@sizeOf(AsyncClosure));
219 }
220
221 fn resultOffset(
222 context_alignment: std.mem.Alignment,
223 context_len: usize,
224 result_alignment: std.mem.Alignment,
225 ) usize {
226 return result_alignment.forward(contextOffset(context_alignment) + context_len);
222 ac.reset_event.set();
227223 }
228224
229 fn resultPointer(closure: *AsyncClosure) [*]u8 {
230 const base: [*]u8 = @ptrCast(closure);
231 return base + closure.result_offset;
225 fn resultPointer(ac: *AsyncClosure) [*]u8 {
226 const base: [*]u8 = @ptrCast(ac);
227 return base + ac.result_offset;
232228 }
233229
234 fn contextPointer(closure: *AsyncClosure) [*]u8 {
235 const base: [*]u8 = @ptrCast(closure);
236 return base + closure.context_offset;
230 fn contextPointer(ac: *AsyncClosure) [*]u8 {
231 const base: [*]u8 = @ptrCast(ac);
232 return base + ac.context_alignment.forward(@sizeOf(AsyncClosure));
237233 }
238234
239 fn waitAndFree(closure: *AsyncClosure, gpa: Allocator, result: []u8) void {
240 closure.reset_event.wait();
241 @memcpy(result, closure.resultPointer()[0..result.len]);
242 free(closure, gpa, result.len);
235 fn waitAndFree(ac: *AsyncClosure, gpa: Allocator, result: []u8) void {
236 ac.reset_event.wait();
237 @memcpy(result, ac.resultPointer()[0..result.len]);
238 free(ac, gpa, result.len);
243239 }
244240
245 fn free(closure: *AsyncClosure, gpa: Allocator, result_len: usize) void {
246 const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(closure);
247 gpa.free(base[0 .. closure.result_offset + result_len]);
241 fn free(ac: *AsyncClosure, gpa: Allocator, result_len: usize) void {
242 if (!ac.has_result) assert(result_len == 0);
243 const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(ac);
244 gpa.free(base[0 .. ac.result_offset + result_len]);
248245 }
249246};
250247
......@@ -271,59 +268,60 @@ fn async(
271268 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
272269 const result_offset = result_alignment.forward(context_offset + context.len);
273270 const n = result_offset + result.len;
274 const closure: *AsyncClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {
271 const ac: *AsyncClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(AsyncClosure), n) catch {
275272 start(context.ptr, result.ptr);
276273 return null;
277274 }));
278275
279 closure.* = .{
276 ac.* = .{
277 .closure = .{
278 .cancel_tid = 0,
279 .start = AsyncClosure.start,
280 .is_concurrent = false,
281 },
280282 .func = start,
281 .context_offset = context_offset,
283 .context_alignment = context_alignment,
282284 .result_offset = result_offset,
285 .has_result = result.len != 0,
283286 .reset_event = .unset,
284 .cancel_tid = 0,
285287 .select_condition = null,
286 .runnable = .{
287 .start = AsyncClosure.start,
288 .is_parallel = false,
289 },
290288 };
291289
292 @memcpy(closure.contextPointer()[0..context.len], context);
290 @memcpy(ac.contextPointer()[0..context.len], context);
293291
294292 pool.mutex.lock();
295293
296 const thread_capacity = cpu_count - 1 + pool.parallel_count;
294 const thread_capacity = cpu_count - 1 + pool.concurrent_count;
297295
298296 pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch {
299297 pool.mutex.unlock();
300 closure.free(gpa, result.len);
298 ac.free(gpa, result.len);
301299 start(context.ptr, result.ptr);
302300 return null;
303301 };
304302
305 pool.run_queue.prepend(&closure.runnable.node);
303 pool.run_queue.prepend(&ac.closure.node);
306304
307305 if (pool.threads.items.len < thread_capacity) {
308306 const thread = std.Thread.spawn(.{ .stack_size = pool.stack_size }, worker, .{pool}) catch {
309307 if (pool.threads.items.len == 0) {
310 assert(pool.run_queue.popFirst() == &closure.runnable.node);
308 assert(pool.run_queue.popFirst() == &ac.closure.node);
311309 pool.mutex.unlock();
312 closure.free(gpa, result.len);
310 ac.free(gpa, result.len);
313311 start(context.ptr, result.ptr);
314312 return null;
315313 }
316314 // Rely on other workers to do it.
317315 pool.mutex.unlock();
318316 pool.cond.signal();
319 return @ptrCast(closure);
317 return @ptrCast(ac);
320318 };
321319 pool.threads.appendAssumeCapacity(thread);
322320 }
323321
324322 pool.mutex.unlock();
325323 pool.cond.signal();
326 return @ptrCast(closure);
324 return @ptrCast(ac);
327325}
328326
329327fn concurrent(
......@@ -342,40 +340,41 @@ fn concurrent(
342340 const context_offset = context_alignment.forward(@sizeOf(AsyncClosure));
343341 const result_offset = result_alignment.forward(context_offset + context.len);
344342 const n = result_offset + result_len;
345 const closure: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), n)));
343 const ac: *AsyncClosure = @ptrCast(@alignCast(try gpa.alignedAlloc(u8, .of(AsyncClosure), n)));
346344
347 closure.* = .{
345 ac.* = .{
346 .closure = .{
347 .cancel_tid = 0,
348 .start = AsyncClosure.start,
349 .is_concurrent = true,
350 },
348351 .func = start,
349 .context_offset = context_offset,
352 .context_alignment = context_alignment,
350353 .result_offset = result_offset,
354 .has_result = result_len != 0,
351355 .reset_event = .unset,
352 .cancel_tid = 0,
353356 .select_condition = null,
354 .runnable = .{
355 .start = AsyncClosure.start,
356 .is_parallel = true,
357 },
358357 };
359 @memcpy(closure.contextPointer()[0..context.len], context);
358 @memcpy(ac.contextPointer()[0..context.len], context);
360359
361360 pool.mutex.lock();
362361
363 pool.parallel_count += 1;
364 const thread_capacity = cpu_count - 1 + pool.parallel_count;
362 pool.concurrent_count += 1;
363 const thread_capacity = cpu_count - 1 + pool.concurrent_count;
365364
366365 pool.threads.ensureTotalCapacity(gpa, thread_capacity) catch {
367366 pool.mutex.unlock();
368 closure.free(gpa, result_len);
367 ac.free(gpa, result_len);
369368 return error.OutOfMemory;
370369 };
371370
372 pool.run_queue.prepend(&closure.runnable.node);
371 pool.run_queue.prepend(&ac.closure.node);
373372
374373 if (pool.threads.items.len < thread_capacity) {
375374 const thread = std.Thread.spawn(.{ .stack_size = pool.stack_size }, worker, .{pool}) catch {
376 assert(pool.run_queue.popFirst() == &closure.runnable.node);
375 assert(pool.run_queue.popFirst() == &ac.closure.node);
377376 pool.mutex.unlock();
378 closure.free(gpa, result_len);
377 ac.free(gpa, result_len);
379378 return error.OutOfMemory;
380379 };
381380 pool.threads.appendAssumeCapacity(thread);
......@@ -383,31 +382,48 @@ fn concurrent(
383382
384383 pool.mutex.unlock();
385384 pool.cond.signal();
386 return @ptrCast(closure);
385 return @ptrCast(ac);
387386}
388387
389388const GroupClosure = struct {
389 closure: Closure,
390390 pool: *Pool,
391391 group: *Io.Group,
392 /// Points to sibling `GroupClosure`. Used for walking the group to cancel all.
393 node: std.SinglyLinkedList.Node,
392394 func: *const fn (context: *anyopaque) void,
393 runnable: Runnable,
394395 context_alignment: std.mem.Alignment,
395396 context_len: usize,
396397
397 fn start(runnable: *Runnable) void {
398 const closure: *GroupClosure = @alignCast(@fieldParentPtr("runnable", runnable));
399 closure.func(closure.contextPointer());
400 const group = closure.group;
401 const gpa = closure.pool.allocator;
402 free(closure, gpa);
398 fn start(closure: *Closure) void {
399 const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure));
400 const tid = std.Thread.getCurrentId();
401 const group = gc.group;
403402 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
404 const reset_event: *std.Thread.ResetEvent = @ptrCast(&group.context);
403 const reset_event: *ResetEvent = @ptrCast(&group.context);
404 if (@cmpxchgStrong(std.Thread.Id, &closure.cancel_tid, 0, tid, .acq_rel, .acquire)) |cancel_tid| {
405 assert(cancel_tid == Closure.canceling_tid);
406 // We already know the task is canceled before running the callback. Since all closures
407 // in a Group have void return type, we can return early.
408 std.Thread.WaitGroup.finishStateless(group_state, reset_event);
409 return;
410 }
411 current_closure = closure;
412 gc.func(gc.contextPointer());
413 current_closure = null;
414
415 // In case a cancel happens after successful task completion, prevents
416 // signal from being delivered to the thread in `requestCancel`.
417 if (@cmpxchgStrong(std.Thread.Id, &closure.cancel_tid, tid, 0, .acq_rel, .acquire)) |cancel_tid| {
418 assert(cancel_tid == Closure.canceling_tid);
419 }
420
405421 std.Thread.WaitGroup.finishStateless(group_state, reset_event);
406422 }
407423
408 fn free(closure: *GroupClosure, gpa: Allocator) void {
409 const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(closure);
410 gpa.free(base[0..contextEnd(closure.context_alignment, closure.context_len)]);
424 fn free(gc: *GroupClosure, gpa: Allocator) void {
425 const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(gc);
426 gpa.free(base[0..contextEnd(gc.context_alignment, gc.context_len)]);
411427 }
412428
413429 fn contextOffset(context_alignment: std.mem.Alignment) usize {
......@@ -418,9 +434,9 @@ const GroupClosure = struct {
418434 return contextOffset(context_alignment) + context_len;
419435 }
420436
421 fn contextPointer(closure: *GroupClosure) [*]u8 {
422 const base: [*]u8 = @ptrCast(closure);
423 return base + contextOffset(closure.context_alignment);
437 fn contextPointer(gc: *GroupClosure) [*]u8 {
438 const base: [*]u8 = @ptrCast(gc);
439 return base + contextOffset(gc.context_alignment);
424440 }
425441};
426442
......@@ -436,39 +452,42 @@ fn groupAsync(
436452 const cpu_count = pool.cpu_count catch 1;
437453 const gpa = pool.allocator;
438454 const n = GroupClosure.contextEnd(context_alignment, context.len);
439 const closure: *GroupClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(GroupClosure), n) catch {
455 const gc: *GroupClosure = @ptrCast(@alignCast(gpa.alignedAlloc(u8, .of(GroupClosure), n) catch {
440456 return start(context.ptr);
441457 }));
442 closure.* = .{
458 gc.* = .{
459 .closure = .{
460 .cancel_tid = 0,
461 .start = GroupClosure.start,
462 .is_concurrent = false,
463 },
443464 .pool = pool,
444465 .group = group,
466 .node = .{ .next = @ptrCast(@alignCast(group.token)) },
445467 .func = start,
446468 .context_alignment = context_alignment,
447469 .context_len = context.len,
448 .runnable = .{
449 .start = GroupClosure.start,
450 .is_parallel = false,
451 },
452470 };
453 @memcpy(closure.contextPointer()[0..context.len], context);
471 group.token = &gc.node;
472 @memcpy(gc.contextPointer()[0..context.len], context);
454473
455474 pool.mutex.lock();
456475
457 const thread_capacity = cpu_count - 1 + pool.parallel_count;
476 const thread_capacity = cpu_count - 1 + pool.concurrent_count;
458477
459478 pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch {
460479 pool.mutex.unlock();
461 closure.free(gpa);
480 gc.free(gpa);
462481 return start(context.ptr);
463482 };
464483
465 pool.run_queue.prepend(&closure.runnable.node);
484 pool.run_queue.prepend(&gc.closure.node);
466485
467486 if (pool.threads.items.len < thread_capacity) {
468487 const thread = std.Thread.spawn(.{ .stack_size = pool.stack_size }, worker, .{pool}) catch {
469 assert(pool.run_queue.popFirst() == &closure.runnable.node);
488 assert(pool.run_queue.popFirst() == &gc.closure.node);
470489 pool.mutex.unlock();
471 closure.free(gpa);
490 gc.free(gpa);
472491 return start(context.ptr);
473492 };
474493 pool.threads.appendAssumeCapacity(thread);
......@@ -486,7 +505,7 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group) void {
486505 const pool: *Pool = @ptrCast(@alignCast(userdata));
487506 _ = pool;
488507 const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state);
489 const reset_event: *std.Thread.ResetEvent = @ptrCast(&group.context);
508 const reset_event: *ResetEvent = @ptrCast(&group.context);
490509 std.Thread.WaitGroup.waitStateless(group_state, reset_event);
491510}
492511
......@@ -494,8 +513,14 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void {
494513 if (builtin.single_threaded) return;
495514 const pool: *Pool = @ptrCast(@alignCast(userdata));
496515 _ = pool;
497 _ = group;
498 @panic("TODO threaded group cancel");
516 const token = group.token.?;
517 group.token = null;
518 var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token));
519 while (true) {
520 const gc: *GroupClosure = @fieldParentPtr("node", node);
521 gc.closure.requestCancel();
522 node = node.next orelse break;
523 }
499524}
500525
501526fn await(
......@@ -518,32 +543,16 @@ fn cancel(
518543) void {
519544 _ = result_alignment;
520545 const pool: *Pool = @ptrCast(@alignCast(userdata));
521 const closure: *AsyncClosure = @ptrCast(@alignCast(any_future));
522 switch (@atomicRmw(
523 std.Thread.Id,
524 &closure.cancel_tid,
525 .Xchg,
526 AsyncClosure.canceling_tid,
527 .acq_rel,
528 )) {
529 0, AsyncClosure.canceling_tid => {},
530 else => |cancel_tid| switch (builtin.os.tag) {
531 .linux => _ = std.os.linux.tgkill(
532 std.os.linux.getpid(),
533 @bitCast(cancel_tid),
534 posix.SIG.IO,
535 ),
536 else => {},
537 },
538 }
539 closure.waitAndFree(pool.allocator, result);
546 const ac: *AsyncClosure = @ptrCast(@alignCast(any_future));
547 ac.closure.requestCancel();
548 ac.waitAndFree(pool.allocator, result);
540549}
541550
542551fn cancelRequested(userdata: ?*anyopaque) bool {
543552 const pool: *Pool = @ptrCast(@alignCast(userdata));
544553 _ = pool;
545554 const closure = current_closure orelse return false;
546 return @atomicLoad(std.Thread.Id, &closure.cancel_tid, .acquire) == AsyncClosure.canceling_tid;
555 return @atomicLoad(std.Thread.Id, &closure.cancel_tid, .acquire) == Closure.canceling_tid;
547556}
548557
549558fn checkCancel(pool: *Pool) error{Canceled}!void {
......@@ -996,14 +1005,14 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
9961005 const pool: *Pool = @ptrCast(@alignCast(userdata));
9971006 _ = pool;
9981007
999 var reset_event: std.Thread.ResetEvent = .unset;
1008 var reset_event: ResetEvent = .unset;
10001009
10011010 for (futures, 0..) |future, i| {
10021011 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
1003 if (@atomicRmw(?*std.Thread.ResetEvent, &closure.select_condition, .Xchg, &reset_event, .seq_cst) == AsyncClosure.done_reset_event) {
1012 if (@atomicRmw(?*ResetEvent, &closure.select_condition, .Xchg, &reset_event, .seq_cst) == AsyncClosure.done_reset_event) {
10041013 for (futures[0..i]) |cleanup_future| {
10051014 const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future));
1006 if (@atomicRmw(?*std.Thread.ResetEvent, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
1015 if (@atomicRmw(?*ResetEvent, &cleanup_closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
10071016 cleanup_closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event.
10081017 }
10091018 }
......@@ -1016,7 +1025,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize {
10161025 var result: ?usize = null;
10171026 for (futures, 0..) |future, i| {
10181027 const closure: *AsyncClosure = @ptrCast(@alignCast(future));
1019 if (@atomicRmw(?*std.Thread.ResetEvent, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
1028 if (@atomicRmw(?*ResetEvent, &closure.select_condition, .Xchg, null, .seq_cst) == AsyncClosure.done_reset_event) {
10201029 closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event.
10211030 if (result == null) result = i; // In case multiple are ready, return first.
10221031 }