| ... | @@ -10,6 +10,7 @@ const Allocator = std.mem.Allocator; | ... | @@ -10,6 +10,7 @@ const Allocator = std.mem.Allocator; |
| 10 | const assert = std.debug.assert; | 10 | const assert = std.debug.assert; |
| 11 | const posix = std.posix; | 11 | const posix = std.posix; |
| 12 | const Io = std.Io; | 12 | const Io = std.Io; |
| | 13 | const ResetEvent = std.Thread.ResetEvent; |
| 13 | | 14 | |
| 14 | /// Thread-safe. | 15 | /// Thread-safe. |
| 15 | allocator: Allocator, | 16 | allocator: Allocator, |
| ... | @@ -20,9 +21,9 @@ join_requested: bool = false, | ... | @@ -20,9 +21,9 @@ join_requested: bool = false, |
| 20 | threads: std.ArrayListUnmanaged(std.Thread), | 21 | threads: std.ArrayListUnmanaged(std.Thread), |
| 21 | stack_size: usize, | 22 | stack_size: usize, |
| 22 | cpu_count: std.Thread.CpuCountError!usize, | 23 | cpu_count: std.Thread.CpuCountError!usize, |
| 23 | parallel_count: usize, | 24 | concurrent_count: usize, |
| 24 | | 25 | |
| 25 | threadlocal var current_closure: ?*AsyncClosure = null; | 26 | threadlocal var current_closure: ?*Closure = null; |
| 26 | | 27 | |
| 27 | const max_iovecs_len = 8; | 28 | const max_iovecs_len = 8; |
| 28 | const splat_buffer_size = 64; | 29 | const splat_buffer_size = 64; |
| ... | @@ -31,12 +32,33 @@ comptime { | ... | @@ -31,12 +32,33 @@ comptime { |
| 31 | assert(max_iovecs_len <= posix.IOV_MAX); | 32 | assert(max_iovecs_len <= posix.IOV_MAX); |
| 32 | } | 33 | } |
| 33 | | 34 | |
| 34 | pub const Runnable = struct { | 35 | const Closure = struct { |
| 35 | start: Start, | 36 | start: Start, |
| 36 | node: std.SinglyLinkedList.Node = .{}, | 37 | 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 | }; |
| 38 | | 52 | |
| 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 | } |
| 40 | }; | 62 | }; |
| 41 | | 63 | |
| 42 | pub const InitError = std.Thread.CpuCountError || Allocator.Error; | 64 | pub const InitError = std.Thread.CpuCountError || Allocator.Error; |
| ... | @@ -47,7 +69,7 @@ pub fn init(gpa: Allocator) Pool { | ... | @@ -47,7 +69,7 @@ pub fn init(gpa: Allocator) Pool { |
| 47 | .threads = .empty, | 69 | .threads = .empty, |
| 48 | .stack_size = std.Thread.SpawnConfig.default_stack_size, | 70 | .stack_size = std.Thread.SpawnConfig.default_stack_size, |
| 49 | .cpu_count = std.Thread.getCpuCount(), | 71 | .cpu_count = std.Thread.getCpuCount(), |
| 50 | .parallel_count = 0, | 72 | .concurrent_count = 0, |
| 51 | }; | 73 | }; |
| 52 | if (pool.cpu_count) |n| { | 74 | if (pool.cpu_count) |n| { |
| 53 | pool.threads.ensureTotalCapacityPrecise(gpa, n - 1) catch {}; | 75 | pool.threads.ensureTotalCapacityPrecise(gpa, n - 1) catch {}; |
| ... | @@ -78,14 +100,15 @@ fn worker(pool: *Pool) void { | ... | @@ -78,14 +100,15 @@ fn worker(pool: *Pool) void { |
| 78 | defer pool.mutex.unlock(); | 100 | defer pool.mutex.unlock(); |
| 79 | | 101 | |
| 80 | while (true) { | 102 | while (true) { |
| 81 | while (pool.run_queue.popFirst()) |run_node| { | 103 | while (pool.run_queue.popFirst()) |closure_node| { |
| 82 | pool.mutex.unlock(); | 104 | pool.mutex.unlock(); |
| 83 | const runnable: *Runnable = @fieldParentPtr("node", run_node); | 105 | const closure: *Closure = @fieldParentPtr("node", closure_node); |
| 84 | runnable.start(runnable); | 106 | const is_concurrent = closure.is_concurrent; |
| | 107 | closure.start(closure); |
| 85 | pool.mutex.lock(); | 108 | pool.mutex.lock(); |
| 86 | if (runnable.is_parallel) { | 109 | if (is_concurrent) { |
| 87 | // TODO also pop thread and join sometimes | 110 | // TODO also pop thread and join sometimes |
| 88 | pool.parallel_count -= 1; | 111 | pool.concurrent_count -= 1; |
| 89 | } | 112 | } |
| 90 | } | 113 | } |
| 91 | if (pool.join_requested) break; | 114 | if (pool.join_requested) break; |
| ... | @@ -154,97 +177,71 @@ pub fn io(pool: *Pool) Io { | ... | @@ -154,97 +177,71 @@ pub fn io(pool: *Pool) Io { |
| 154 | }; | 177 | }; |
| 155 | } | 178 | } |
| 156 | | 179 | |
| | 180 | /// Trailing data: |
| | 181 | /// 1. context |
| | 182 | /// 2. result |
| 157 | const AsyncClosure = struct { | 183 | const AsyncClosure = struct { |
| | 184 | closure: Closure, |
| 158 | func: *const fn (context: *anyopaque, result: *anyopaque) void, | 185 | func: *const fn (context: *anyopaque, result: *anyopaque) void, |
| 159 | runnable: Runnable, | 186 | reset_event: ResetEvent, |
| 160 | reset_event: std.Thread.ResetEvent, | 187 | select_condition: ?*ResetEvent, |
| 161 | select_condition: ?*std.Thread.ResetEvent, | 188 | context_alignment: std.mem.Alignment, |
| 162 | cancel_tid: std.Thread.Id, | | |
| 163 | context_offset: usize, | | |
| 164 | result_offset: usize, | 189 | result_offset: usize, |
| | 190 | /// Whether the task has a return type with nonzero bits. |
| | 191 | has_result: bool, |
| 165 | | 192 | |
| 166 | const done_reset_event: *std.Thread.ResetEvent = @ptrFromInt(@alignOf(std.Thread.ResetEvent)); | 193 | const done_reset_event: *ResetEvent = @ptrFromInt(@alignOf(ResetEvent)); |
| 167 | | 194 | |
| 168 | const canceling_tid: std.Thread.Id = switch (@typeInfo(std.Thread.Id)) { | 195 | fn start(closure: *Closure) void { |
| 169 | .int => |int_info| switch (int_info.signedness) { | 196 | const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure)); |
| 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)); | | |
| 179 | const tid = std.Thread.getCurrentId(); | 197 | const tid = std.Thread.getCurrentId(); |
| 180 | if (@cmpxchgStrong( | 198 | if (@cmpxchgStrong(std.Thread.Id, &closure.cancel_tid, 0, tid, .acq_rel, .acquire)) |cancel_tid| { |
| 181 | std.Thread.Id, | 199 | assert(cancel_tid == Closure.canceling_tid); |
| 182 | &closure.cancel_tid, | 200 | // Even though we already know the task is canceled, we must still |
| 183 | 0, | 201 | // run the closure in order to make the return value valid - that |
| 184 | tid, | 202 | // is, unless the result is zero bytes! |
| 185 | .acq_rel, | 203 | if (!ac.has_result) { |
| 186 | .acquire, | 204 | ac.reset_event.set(); |
| 187 | )) |cancel_tid| { | 205 | return; |
| 188 | assert(cancel_tid == canceling_tid); | 206 | } |
| 189 | closure.reset_event.set(); | | |
| 190 | return; | | |
| 191 | } | 207 | } |
| 192 | current_closure = closure; | 208 | current_closure = closure; |
| 193 | closure.func(closure.contextPointer(), closure.resultPointer()); | 209 | ac.func(ac.contextPointer(), ac.resultPointer()); |
| 194 | current_closure = null; | 210 | current_closure = null; |
| 195 | if (@cmpxchgStrong( | 211 | |
| 196 | std.Thread.Id, | 212 | // In case a cancel happens after successful task completion, prevents |
| 197 | &closure.cancel_tid, | 213 | // signal from being delivered to the thread in `requestCancel`. |
| 198 | tid, | 214 | if (@cmpxchgStrong(std.Thread.Id, &closure.cancel_tid, tid, 0, .acq_rel, .acquire)) |cancel_tid| { |
| 199 | 0, | 215 | assert(cancel_tid == Closure.canceling_tid); |
| 200 | .acq_rel, | 216 | } |
| 201 | .acquire, | 217 | |
| 202 | )) |cancel_tid| assert(cancel_tid == canceling_tid); | 218 | if (@atomicRmw(?*ResetEvent, &ac.select_condition, .Xchg, done_reset_event, .release)) |select_reset| { |
| 203 | | | |
| 204 | if (@atomicRmw( | | |
| 205 | ?*std.Thread.ResetEvent, | | |
| 206 | &closure.select_condition, | | |
| 207 | .Xchg, | | |
| 208 | done_reset_event, | | |
| 209 | .release, | | |
| 210 | )) |select_reset| { | | |
| 211 | assert(select_reset != done_reset_event); | 219 | assert(select_reset != done_reset_event); |
| 212 | select_reset.set(); | 220 | select_reset.set(); |
| 213 | } | 221 | } |
| 214 | closure.reset_event.set(); | 222 | ac.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); | | |
| 227 | } | 223 | } |
| 228 | | 224 | |
| 229 | fn resultPointer(closure: *AsyncClosure) [*]u8 { | 225 | fn resultPointer(ac: *AsyncClosure) [*]u8 { |
| 230 | const base: [*]u8 = @ptrCast(closure); | 226 | const base: [*]u8 = @ptrCast(ac); |
| 231 | return base + closure.result_offset; | 227 | return base + ac.result_offset; |
| 232 | } | 228 | } |
| 233 | | 229 | |
| 234 | fn contextPointer(closure: *AsyncClosure) [*]u8 { | 230 | fn contextPointer(ac: *AsyncClosure) [*]u8 { |
| 235 | const base: [*]u8 = @ptrCast(closure); | 231 | const base: [*]u8 = @ptrCast(ac); |
| 236 | return base + closure.context_offset; | 232 | return base + ac.context_alignment.forward(@sizeOf(AsyncClosure)); |
| 237 | } | 233 | } |
| 238 | | 234 | |
| 239 | fn waitAndFree(closure: *AsyncClosure, gpa: Allocator, result: []u8) void { | 235 | fn waitAndFree(ac: *AsyncClosure, gpa: Allocator, result: []u8) void { |
| 240 | closure.reset_event.wait(); | 236 | ac.reset_event.wait(); |
| 241 | @memcpy(result, closure.resultPointer()[0..result.len]); | 237 | @memcpy(result, ac.resultPointer()[0..result.len]); |
| 242 | free(closure, gpa, result.len); | 238 | free(ac, gpa, result.len); |
| 243 | } | 239 | } |
| 244 | | 240 | |
| 245 | fn free(closure: *AsyncClosure, gpa: Allocator, result_len: usize) void { | 241 | fn free(ac: *AsyncClosure, gpa: Allocator, result_len: usize) void { |
| 246 | const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(closure); | 242 | if (!ac.has_result) assert(result_len == 0); |
| 247 | gpa.free(base[0 .. closure.result_offset + result_len]); | 243 | const base: [*]align(@alignOf(AsyncClosure)) u8 = @ptrCast(ac); |
| | 244 | gpa.free(base[0 .. ac.result_offset + result_len]); |
| 248 | } | 245 | } |
| 249 | }; | 246 | }; |
| 250 | | 247 | |
| ... | @@ -271,59 +268,60 @@ fn async( | ... | @@ -271,59 +268,60 @@ fn async( |
| 271 | const context_offset = context_alignment.forward(@sizeOf(AsyncClosure)); | 268 | const context_offset = context_alignment.forward(@sizeOf(AsyncClosure)); |
| 272 | const result_offset = result_alignment.forward(context_offset + context.len); | 269 | const result_offset = result_alignment.forward(context_offset + context.len); |
| 273 | const n = result_offset + result.len; | 270 | 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 { |
| 275 | start(context.ptr, result.ptr); | 272 | start(context.ptr, result.ptr); |
| 276 | return null; | 273 | return null; |
| 277 | })); | 274 | })); |
| 278 | | 275 | |
| 279 | closure.* = .{ | 276 | ac.* = .{ |
| | 277 | .closure = .{ |
| | 278 | .cancel_tid = 0, |
| | 279 | .start = AsyncClosure.start, |
| | 280 | .is_concurrent = false, |
| | 281 | }, |
| 280 | .func = start, | 282 | .func = start, |
| 281 | .context_offset = context_offset, | 283 | .context_alignment = context_alignment, |
| 282 | .result_offset = result_offset, | 284 | .result_offset = result_offset, |
| | 285 | .has_result = result.len != 0, |
| 283 | .reset_event = .unset, | 286 | .reset_event = .unset, |
| 284 | .cancel_tid = 0, | | |
| 285 | .select_condition = null, | 287 | .select_condition = null, |
| 286 | .runnable = .{ | | |
| 287 | .start = AsyncClosure.start, | | |
| 288 | .is_parallel = false, | | |
| 289 | }, | | |
| 290 | }; | 288 | }; |
| 291 | | 289 | |
| 292 | @memcpy(closure.contextPointer()[0..context.len], context); | 290 | @memcpy(ac.contextPointer()[0..context.len], context); |
| 293 | | 291 | |
| 294 | pool.mutex.lock(); | 292 | pool.mutex.lock(); |
| 295 | | 293 | |
| 296 | const thread_capacity = cpu_count - 1 + pool.parallel_count; | 294 | const thread_capacity = cpu_count - 1 + pool.concurrent_count; |
| 297 | | 295 | |
| 298 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { | 296 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { |
| 299 | pool.mutex.unlock(); | 297 | pool.mutex.unlock(); |
| 300 | closure.free(gpa, result.len); | 298 | ac.free(gpa, result.len); |
| 301 | start(context.ptr, result.ptr); | 299 | start(context.ptr, result.ptr); |
| 302 | return null; | 300 | return null; |
| 303 | }; | 301 | }; |
| 304 | | 302 | |
| 305 | pool.run_queue.prepend(&closure.runnable.node); | 303 | pool.run_queue.prepend(&ac.closure.node); |
| 306 | | 304 | |
| 307 | if (pool.threads.items.len < thread_capacity) { | 305 | if (pool.threads.items.len < thread_capacity) { |
| 308 | const thread = std.Thread.spawn(.{ .stack_size = pool.stack_size }, worker, .{pool}) catch { | 306 | const thread = std.Thread.spawn(.{ .stack_size = pool.stack_size }, worker, .{pool}) catch { |
| 309 | if (pool.threads.items.len == 0) { | 307 | if (pool.threads.items.len == 0) { |
| 310 | assert(pool.run_queue.popFirst() == &closure.runnable.node); | 308 | assert(pool.run_queue.popFirst() == &ac.closure.node); |
| 311 | pool.mutex.unlock(); | 309 | pool.mutex.unlock(); |
| 312 | closure.free(gpa, result.len); | 310 | ac.free(gpa, result.len); |
| 313 | start(context.ptr, result.ptr); | 311 | start(context.ptr, result.ptr); |
| 314 | return null; | 312 | return null; |
| 315 | } | 313 | } |
| 316 | // Rely on other workers to do it. | 314 | // Rely on other workers to do it. |
| 317 | pool.mutex.unlock(); | 315 | pool.mutex.unlock(); |
| 318 | pool.cond.signal(); | 316 | pool.cond.signal(); |
| 319 | return @ptrCast(closure); | 317 | return @ptrCast(ac); |
| 320 | }; | 318 | }; |
| 321 | pool.threads.appendAssumeCapacity(thread); | 319 | pool.threads.appendAssumeCapacity(thread); |
| 322 | } | 320 | } |
| 323 | | 321 | |
| 324 | pool.mutex.unlock(); | 322 | pool.mutex.unlock(); |
| 325 | pool.cond.signal(); | 323 | pool.cond.signal(); |
| 326 | return @ptrCast(closure); | 324 | return @ptrCast(ac); |
| 327 | } | 325 | } |
| 328 | | 326 | |
| 329 | fn concurrent( | 327 | fn concurrent( |
| ... | @@ -342,40 +340,41 @@ fn concurrent( | ... | @@ -342,40 +340,41 @@ fn concurrent( |
| 342 | const context_offset = context_alignment.forward(@sizeOf(AsyncClosure)); | 340 | const context_offset = context_alignment.forward(@sizeOf(AsyncClosure)); |
| 343 | const result_offset = result_alignment.forward(context_offset + context.len); | 341 | const result_offset = result_alignment.forward(context_offset + context.len); |
| 344 | const n = result_offset + result_len; | 342 | 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))); |
| 346 | | 344 | |
| 347 | closure.* = .{ | 345 | ac.* = .{ |
| | 346 | .closure = .{ |
| | 347 | .cancel_tid = 0, |
| | 348 | .start = AsyncClosure.start, |
| | 349 | .is_concurrent = true, |
| | 350 | }, |
| 348 | .func = start, | 351 | .func = start, |
| 349 | .context_offset = context_offset, | 352 | .context_alignment = context_alignment, |
| 350 | .result_offset = result_offset, | 353 | .result_offset = result_offset, |
| | 354 | .has_result = result_len != 0, |
| 351 | .reset_event = .unset, | 355 | .reset_event = .unset, |
| 352 | .cancel_tid = 0, | | |
| 353 | .select_condition = null, | 356 | .select_condition = null, |
| 354 | .runnable = .{ | | |
| 355 | .start = AsyncClosure.start, | | |
| 356 | .is_parallel = true, | | |
| 357 | }, | | |
| 358 | }; | 357 | }; |
| 359 | @memcpy(closure.contextPointer()[0..context.len], context); | 358 | @memcpy(ac.contextPointer()[0..context.len], context); |
| 360 | | 359 | |
| 361 | pool.mutex.lock(); | 360 | pool.mutex.lock(); |
| 362 | | 361 | |
| 363 | pool.parallel_count += 1; | 362 | pool.concurrent_count += 1; |
| 364 | const thread_capacity = cpu_count - 1 + pool.parallel_count; | 363 | const thread_capacity = cpu_count - 1 + pool.concurrent_count; |
| 365 | | 364 | |
| 366 | pool.threads.ensureTotalCapacity(gpa, thread_capacity) catch { | 365 | pool.threads.ensureTotalCapacity(gpa, thread_capacity) catch { |
| 367 | pool.mutex.unlock(); | 366 | pool.mutex.unlock(); |
| 368 | closure.free(gpa, result_len); | 367 | ac.free(gpa, result_len); |
| 369 | return error.OutOfMemory; | 368 | return error.OutOfMemory; |
| 370 | }; | 369 | }; |
| 371 | | 370 | |
| 372 | pool.run_queue.prepend(&closure.runnable.node); | 371 | pool.run_queue.prepend(&ac.closure.node); |
| 373 | | 372 | |
| 374 | if (pool.threads.items.len < thread_capacity) { | 373 | if (pool.threads.items.len < thread_capacity) { |
| 375 | const thread = std.Thread.spawn(.{ .stack_size = pool.stack_size }, worker, .{pool}) catch { | 374 | 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); |
| 377 | pool.mutex.unlock(); | 376 | pool.mutex.unlock(); |
| 378 | closure.free(gpa, result_len); | 377 | ac.free(gpa, result_len); |
| 379 | return error.OutOfMemory; | 378 | return error.OutOfMemory; |
| 380 | }; | 379 | }; |
| 381 | pool.threads.appendAssumeCapacity(thread); | 380 | pool.threads.appendAssumeCapacity(thread); |
| ... | @@ -383,31 +382,48 @@ fn concurrent( | ... | @@ -383,31 +382,48 @@ fn concurrent( |
| 383 | | 382 | |
| 384 | pool.mutex.unlock(); | 383 | pool.mutex.unlock(); |
| 385 | pool.cond.signal(); | 384 | pool.cond.signal(); |
| 386 | return @ptrCast(closure); | 385 | return @ptrCast(ac); |
| 387 | } | 386 | } |
| 388 | | 387 | |
| 389 | const GroupClosure = struct { | 388 | const GroupClosure = struct { |
| | 389 | closure: Closure, |
| 390 | pool: *Pool, | 390 | pool: *Pool, |
| 391 | group: *Io.Group, | 391 | group: *Io.Group, |
| | 392 | /// Points to sibling `GroupClosure`. Used for walking the group to cancel all. |
| | 393 | node: std.SinglyLinkedList.Node, |
| 392 | func: *const fn (context: *anyopaque) void, | 394 | func: *const fn (context: *anyopaque) void, |
| 393 | runnable: Runnable, | | |
| 394 | context_alignment: std.mem.Alignment, | 395 | context_alignment: std.mem.Alignment, |
| 395 | context_len: usize, | 396 | context_len: usize, |
| 396 | | 397 | |
| 397 | fn start(runnable: *Runnable) void { | 398 | fn start(closure: *Closure) void { |
| 398 | const closure: *GroupClosure = @alignCast(@fieldParentPtr("runnable", runnable)); | 399 | const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure)); |
| 399 | closure.func(closure.contextPointer()); | 400 | const tid = std.Thread.getCurrentId(); |
| 400 | const group = closure.group; | 401 | const group = gc.group; |
| 401 | const gpa = closure.pool.allocator; | | |
| 402 | free(closure, gpa); | | |
| 403 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); | 402 | 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 | |
| 405 | std.Thread.WaitGroup.finishStateless(group_state, reset_event); | 421 | std.Thread.WaitGroup.finishStateless(group_state, reset_event); |
| 406 | } | 422 | } |
| 407 | | 423 | |
| 408 | fn free(closure: *GroupClosure, gpa: Allocator) void { | 424 | fn free(gc: *GroupClosure, gpa: Allocator) void { |
| 409 | const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(closure); | 425 | const base: [*]align(@alignOf(GroupClosure)) u8 = @ptrCast(gc); |
| 410 | gpa.free(base[0..contextEnd(closure.context_alignment, closure.context_len)]); | 426 | gpa.free(base[0..contextEnd(gc.context_alignment, gc.context_len)]); |
| 411 | } | 427 | } |
| 412 | | 428 | |
| 413 | fn contextOffset(context_alignment: std.mem.Alignment) usize { | 429 | fn contextOffset(context_alignment: std.mem.Alignment) usize { |
| ... | @@ -418,9 +434,9 @@ const GroupClosure = struct { | ... | @@ -418,9 +434,9 @@ const GroupClosure = struct { |
| 418 | return contextOffset(context_alignment) + context_len; | 434 | return contextOffset(context_alignment) + context_len; |
| 419 | } | 435 | } |
| 420 | | 436 | |
| 421 | fn contextPointer(closure: *GroupClosure) [*]u8 { | 437 | fn contextPointer(gc: *GroupClosure) [*]u8 { |
| 422 | const base: [*]u8 = @ptrCast(closure); | 438 | const base: [*]u8 = @ptrCast(gc); |
| 423 | return base + contextOffset(closure.context_alignment); | 439 | return base + contextOffset(gc.context_alignment); |
| 424 | } | 440 | } |
| 425 | }; | 441 | }; |
| 426 | | 442 | |
| ... | @@ -436,39 +452,42 @@ fn groupAsync( | ... | @@ -436,39 +452,42 @@ fn groupAsync( |
| 436 | const cpu_count = pool.cpu_count catch 1; | 452 | const cpu_count = pool.cpu_count catch 1; |
| 437 | const gpa = pool.allocator; | 453 | const gpa = pool.allocator; |
| 438 | const n = GroupClosure.contextEnd(context_alignment, context.len); | 454 | 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 { |
| 440 | return start(context.ptr); | 456 | return start(context.ptr); |
| 441 | })); | 457 | })); |
| 442 | closure.* = .{ | 458 | gc.* = .{ |
| | 459 | .closure = .{ |
| | 460 | .cancel_tid = 0, |
| | 461 | .start = GroupClosure.start, |
| | 462 | .is_concurrent = false, |
| | 463 | }, |
| 443 | .pool = pool, | 464 | .pool = pool, |
| 444 | .group = group, | 465 | .group = group, |
| | 466 | .node = .{ .next = @ptrCast(@alignCast(group.token)) }, |
| 445 | .func = start, | 467 | .func = start, |
| 446 | .context_alignment = context_alignment, | 468 | .context_alignment = context_alignment, |
| 447 | .context_len = context.len, | 469 | .context_len = context.len, |
| 448 | .runnable = .{ | | |
| 449 | .start = GroupClosure.start, | | |
| 450 | .is_parallel = false, | | |
| 451 | }, | | |
| 452 | }; | 470 | }; |
| 453 | @memcpy(closure.contextPointer()[0..context.len], context); | 471 | group.token = &gc.node; |
| | 472 | @memcpy(gc.contextPointer()[0..context.len], context); |
| 454 | | 473 | |
| 455 | pool.mutex.lock(); | 474 | pool.mutex.lock(); |
| 456 | | 475 | |
| 457 | const thread_capacity = cpu_count - 1 + pool.parallel_count; | 476 | const thread_capacity = cpu_count - 1 + pool.concurrent_count; |
| 458 | | 477 | |
| 459 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { | 478 | pool.threads.ensureTotalCapacityPrecise(gpa, thread_capacity) catch { |
| 460 | pool.mutex.unlock(); | 479 | pool.mutex.unlock(); |
| 461 | closure.free(gpa); | 480 | gc.free(gpa); |
| 462 | return start(context.ptr); | 481 | return start(context.ptr); |
| 463 | }; | 482 | }; |
| 464 | | 483 | |
| 465 | pool.run_queue.prepend(&closure.runnable.node); | 484 | pool.run_queue.prepend(&gc.closure.node); |
| 466 | | 485 | |
| 467 | if (pool.threads.items.len < thread_capacity) { | 486 | if (pool.threads.items.len < thread_capacity) { |
| 468 | const thread = std.Thread.spawn(.{ .stack_size = pool.stack_size }, worker, .{pool}) catch { | 487 | 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); |
| 470 | pool.mutex.unlock(); | 489 | pool.mutex.unlock(); |
| 471 | closure.free(gpa); | 490 | gc.free(gpa); |
| 472 | return start(context.ptr); | 491 | return start(context.ptr); |
| 473 | }; | 492 | }; |
| 474 | pool.threads.appendAssumeCapacity(thread); | 493 | pool.threads.appendAssumeCapacity(thread); |
| ... | @@ -486,7 +505,7 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group) void { | ... | @@ -486,7 +505,7 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group) void { |
| 486 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 505 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 487 | _ = pool; | 506 | _ = pool; |
| 488 | const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); | 507 | 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); |
| 490 | std.Thread.WaitGroup.waitStateless(group_state, reset_event); | 509 | std.Thread.WaitGroup.waitStateless(group_state, reset_event); |
| 491 | } | 510 | } |
| 492 | | 511 | |
| ... | @@ -494,8 +513,14 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void { | ... | @@ -494,8 +513,14 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group) void { |
| 494 | if (builtin.single_threaded) return; | 513 | if (builtin.single_threaded) return; |
| 495 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 514 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 496 | _ = pool; | 515 | _ = pool; |
| 497 | _ = group; | 516 | const token = group.token.?; |
| 498 | @panic("TODO threaded group cancel"); | 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 | } |
| 499 | } | 524 | } |
| 500 | | 525 | |
| 501 | fn await( | 526 | fn await( |
| ... | @@ -518,32 +543,16 @@ fn cancel( | ... | @@ -518,32 +543,16 @@ fn cancel( |
| 518 | ) void { | 543 | ) void { |
| 519 | _ = result_alignment; | 544 | _ = result_alignment; |
| 520 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 545 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 521 | const closure: *AsyncClosure = @ptrCast(@alignCast(any_future)); | 546 | const ac: *AsyncClosure = @ptrCast(@alignCast(any_future)); |
| 522 | switch (@atomicRmw( | 547 | ac.closure.requestCancel(); |
| 523 | std.Thread.Id, | 548 | ac.waitAndFree(pool.allocator, result); |
| 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); | | |
| 540 | } | 549 | } |
| 541 | | 550 | |
| 542 | fn cancelRequested(userdata: ?*anyopaque) bool { | 551 | fn cancelRequested(userdata: ?*anyopaque) bool { |
| 543 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 552 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 544 | _ = pool; | 553 | _ = pool; |
| 545 | const closure = current_closure orelse return false; | 554 | 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; |
| 547 | } | 556 | } |
| 548 | | 557 | |
| 549 | fn checkCancel(pool: *Pool) error{Canceled}!void { | 558 | fn checkCancel(pool: *Pool) error{Canceled}!void { |
| ... | @@ -996,14 +1005,14 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | ... | @@ -996,14 +1005,14 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { |
| 996 | const pool: *Pool = @ptrCast(@alignCast(userdata)); | 1005 | const pool: *Pool = @ptrCast(@alignCast(userdata)); |
| 997 | _ = pool; | 1006 | _ = pool; |
| 998 | | 1007 | |
| 999 | var reset_event: std.Thread.ResetEvent = .unset; | 1008 | var reset_event: ResetEvent = .unset; |
| 1000 | | 1009 | |
| 1001 | for (futures, 0..) |future, i| { | 1010 | for (futures, 0..) |future, i| { |
| 1002 | const closure: *AsyncClosure = @ptrCast(@alignCast(future)); | 1011 | 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) { |
| 1004 | for (futures[0..i]) |cleanup_future| { | 1013 | for (futures[0..i]) |cleanup_future| { |
| 1005 | const cleanup_closure: *AsyncClosure = @ptrCast(@alignCast(cleanup_future)); | 1014 | 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) { |
| 1007 | cleanup_closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. | 1016 | cleanup_closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. |
| 1008 | } | 1017 | } |
| 1009 | } | 1018 | } |
| ... | @@ -1016,7 +1025,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | ... | @@ -1016,7 +1025,7 @@ fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { |
| 1016 | var result: ?usize = null; | 1025 | var result: ?usize = null; |
| 1017 | for (futures, 0..) |future, i| { | 1026 | for (futures, 0..) |future, i| { |
| 1018 | const closure: *AsyncClosure = @ptrCast(@alignCast(future)); | 1027 | 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) { |
| 1020 | closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. | 1029 | closure.reset_event.wait(); // Ensure no reference to our stack-allocated reset_event. |
| 1021 | if (result == null) result = i; // In case multiple are ready, return first. | 1030 | if (result == null) result = i; // In case multiple are ready, return first. |
| 1022 | } | 1031 | } |