| ... | ... | @@ -9,7 +9,7 @@ cond: std.Thread.Condition, |
| 9 | 9 | run_queue: RunQueue, |
| 10 | 10 | end_flag: bool, |
| 11 | 11 | allocator: std.mem.Allocator, |
| 12 | | threads: []std.Thread, |
| 12 | workers: []Worker, |
| 13 | 13 | job_server_options: Options.JobServer, |
| 14 | 14 | job_server: ?*JobServer, |
| 15 | 15 | |
| ... | ... | @@ -20,6 +20,20 @@ const Runnable = struct { |
| 20 | 20 | |
| 21 | 21 | const RunProto = *const fn (*Runnable) void; |
| 22 | 22 | |
| 23 | pub const Worker = struct { |
| 24 | thread: std.Thread, |
| 25 | /// This data is shared with the thread pool for deinitialization purposes: |
| 26 | /// calling shutdown on this file descriptor will wake up any workers |
| 27 | /// blocking on acquiring a thread token. |
| 28 | /// |
| 29 | /// Pointers to file descriptors are used so that the actual loads and |
| 30 | /// stores of file descriptors take place in thread-local data, avoiding |
| 31 | /// false sharing. |
| 32 | /// |
| 33 | /// Protected by the pool mutex. |
| 34 | connection: *const std.posix.fd_t, |
| 35 | }; |
| 36 | |
| 23 | 37 | pub const Options = struct { |
| 24 | 38 | /// Not required to be thread-safe; protected by the pool's mutex. |
| 25 | 39 | allocator: std.mem.Allocator, |
| ... | ... | @@ -59,7 +73,7 @@ pub fn init(pool: *Pool, options: Options) !void { |
| 59 | 73 | .run_queue = .{}, |
| 60 | 74 | .end_flag = false, |
| 61 | 75 | .allocator = allocator, |
| 62 | | .threads = &.{}, |
| 76 | .workers = &.{}, |
| 63 | 77 | .job_server_options = options.job_server, |
| 64 | 78 | .job_server = null, |
| 65 | 79 | }; |
| ... | ... | @@ -71,19 +85,26 @@ pub fn init(pool: *Pool, options: Options) !void { |
| 71 | 85 | assert(thread_count > 0); |
| 72 | 86 | |
| 73 | 87 | // Kill and join any threads we spawned and free memory on error. |
| 74 | | pool.threads = try allocator.alloc(std.Thread, thread_count); |
| 88 | pool.workers = try allocator.alloc(Worker, thread_count); |
| 75 | 89 | var spawned: usize = 0; |
| 76 | 90 | errdefer pool.join(spawned); |
| 77 | 91 | |
| 78 | | for (pool.threads) |*thread| { |
| 79 | | thread.* = try std.Thread.spawn(.{}, worker, .{pool}); |
| 92 | const temporary_connection_memory: std.posix.fd_t = -1; |
| 93 | |
| 94 | for (pool.workers) |*worker| { |
| 95 | worker.* = .{ |
| 96 | .connection = &temporary_connection_memory, |
| 97 | .thread = try std.Thread.spawn(.{}, workerRun, .{ pool, spawned }), |
| 98 | }; |
| 80 | 99 | spawned += 1; |
| 81 | 100 | } |
| 82 | 101 | |
| 83 | 102 | switch (options.job_server) { |
| 84 | 103 | .abstain, .connect => {}, |
| 85 | 104 | .host => |addr| { |
| 86 | | var server = try addr.listen(.{}); |
| 105 | var server = try addr.listen(.{ |
| 106 | .kernel_backlog = std.math.maxInt(u31), |
| 107 | }); |
| 87 | 108 | errdefer server.deinit(); |
| 88 | 109 | |
| 89 | 110 | const pollfds = try allocator.alloc(std.posix.pollfd, thread_count + 1); |
| ... | ... | @@ -104,7 +125,7 @@ pub fn init(pool: *Pool, options: Options) !void { |
| 104 | 125 | } |
| 105 | 126 | |
| 106 | 127 | pub fn deinit(pool: *Pool) void { |
| 107 | | pool.join(pool.threads.len); |
| 128 | pool.join(pool.workers.len); |
| 108 | 129 | pool.* = undefined; |
| 109 | 130 | } |
| 110 | 131 | |
| ... | ... | @@ -118,6 +139,12 @@ fn join(pool: *Pool, spawned: usize) void { |
| 118 | 139 | |
| 119 | 140 | // Ensure future worker threads exit the dequeue loop. |
| 120 | 141 | pool.end_flag = true; |
| 142 | |
| 143 | // Wake up any workers blocking on connect or read. |
| 144 | for (pool.workers[0..spawned]) |worker| { |
| 145 | const fd = worker.connection.*; |
| 146 | if (fd >= 0) std.posix.shutdown(fd, .both) catch {}; |
| 147 | } |
| 121 | 148 | } |
| 122 | 149 | |
| 123 | 150 | // Wake up any sleeping threads (this can be done outside the mutex) then |
| ... | ... | @@ -132,10 +159,10 @@ fn join(pool: *Pool, spawned: usize) void { |
| 132 | 159 | job_server.thread.join(); |
| 133 | 160 | } |
| 134 | 161 | |
| 135 | | for (pool.threads[0..spawned]) |thread| |
| 136 | | thread.join(); |
| 162 | for (pool.workers[0..spawned]) |worker| |
| 163 | worker.thread.join(); |
| 137 | 164 | |
| 138 | | pool.allocator.free(pool.threads); |
| 165 | pool.allocator.free(pool.workers); |
| 139 | 166 | } |
| 140 | 167 | |
| 141 | 168 | pub const JobServer = struct { |
| ... | ... | @@ -310,36 +337,6 @@ pub fn spawn(pool: *Pool, comptime func: anytype, args: anytype) void { |
| 310 | 337 | pool.cond.signal(); |
| 311 | 338 | } |
| 312 | 339 | |
| 313 | | fn acquireThreadToken(job_server_options: Options.JobServer, fd_ptr: *std.posix.fd_t) void { |
| 314 | | if (fd_ptr.* >= 0) return; |
| 315 | | |
| 316 | | switch (job_server_options) { |
| 317 | | .abstain => {}, |
| 318 | | .connect, .host => |addr| { |
| 319 | | const sockfd = std.posix.socket( |
| 320 | | std.posix.AF.UNIX, |
| 321 | | std.posix.SOCK.STREAM | std.posix.SOCK.CLOEXEC, |
| 322 | | 0, |
| 323 | | ) catch |err| { |
| 324 | | std.log.debug("failed to make socket: {s}", .{@errorName(err)}); |
| 325 | | return; |
| 326 | | }; |
| 327 | | fd_ptr.* = sockfd; |
| 328 | | |
| 329 | | std.posix.connect(sockfd, &addr.any, addr.getOsSockLen()) catch |err| { |
| 330 | | std.log.debug("failed to connect: {s}", .{@errorName(err)}); |
| 331 | | return; |
| 332 | | }; |
| 333 | | |
| 334 | | var trash_buf: [1]u8 = undefined; |
| 335 | | _ = std.posix.read(sockfd, &trash_buf) catch |err| { |
| 336 | | std.log.debug("failed to read: {s}", .{@errorName(err)}); |
| 337 | | return; |
| 338 | | }; |
| 339 | | }, |
| 340 | | } |
| 341 | | } |
| 342 | | |
| 343 | 340 | fn releaseThreadToken(fd_ptr: *std.posix.fd_t) void { |
| 344 | 341 | const fd = fd_ptr.*; |
| 345 | 342 | if (fd >= 0) { |
| ... | ... | @@ -348,29 +345,65 @@ fn releaseThreadToken(fd_ptr: *std.posix.fd_t) void { |
| 348 | 345 | } |
| 349 | 346 | } |
| 350 | 347 | |
| 351 | | fn worker(pool: *Pool) void { |
| 348 | fn workerRun(pool: *Pool, worker_index: usize) void { |
| 352 | 349 | var connection: std.posix.fd_t = -1; |
| 353 | | defer releaseThreadToken(&connection); |
| 354 | 350 | |
| 355 | 351 | pool.mutex.lock(); |
| 356 | | defer pool.mutex.unlock(); |
| 357 | | |
| 358 | | while (true) { |
| 359 | | const work_available = pool.run_queue.first != null; |
| 360 | | if (work_available) { |
| 361 | | pool.mutex.unlock(); |
| 362 | | defer pool.mutex.lock(); |
| 363 | | acquireThreadToken(pool.job_server_options, &connection); |
| 352 | pool.workers[worker_index].connection = &connection; |
| 353 | |
| 354 | while (!pool.end_flag) { |
| 355 | if (connection == -1 and pool.run_queue.first != null) token: { |
| 356 | switch (pool.job_server_options) { |
| 357 | .abstain => {}, |
| 358 | .connect, .host => |addr| { |
| 359 | pool.mutex.unlock(); |
| 360 | const sockfd = std.posix.socket( |
| 361 | std.posix.AF.UNIX, |
| 362 | std.posix.SOCK.STREAM | std.posix.SOCK.CLOEXEC, |
| 363 | 0, |
| 364 | ) catch |err| { |
| 365 | std.log.debug("failed to make socket: {s}", .{@errorName(err)}); |
| 366 | pool.mutex.lock(); |
| 367 | if (pool.end_flag) break; |
| 368 | break :token; |
| 369 | }; |
| 370 | pool.mutex.lock(); |
| 371 | connection = sockfd; |
| 372 | if (pool.end_flag) break; |
| 373 | pool.mutex.unlock(); |
| 374 | |
| 375 | std.posix.connect(sockfd, &addr.any, addr.getOsSockLen()) catch |err| { |
| 376 | std.log.debug("failed to connect: {s}", .{@errorName(err)}); |
| 377 | pool.mutex.lock(); |
| 378 | if (pool.end_flag) break; |
| 379 | break :token; |
| 380 | }; |
| 381 | |
| 382 | var trash_buf: [1]u8 = undefined; |
| 383 | _ = std.posix.read(sockfd, &trash_buf) catch |err| { |
| 384 | std.log.debug("failed to read: {s}", .{@errorName(err)}); |
| 385 | pool.mutex.lock(); |
| 386 | if (pool.end_flag) break; |
| 387 | break :token; |
| 388 | }; |
| 389 | |
| 390 | pool.mutex.lock(); |
| 391 | if (pool.end_flag) break; |
| 392 | }, |
| 393 | } |
| 364 | 394 | } |
| 365 | 395 | while (pool.run_queue.popFirst()) |run_node| { |
| 366 | 396 | pool.mutex.unlock(); |
| 367 | | defer pool.mutex.lock(); |
| 368 | 397 | run_node.data.runFn(&run_node.data); |
| 398 | pool.mutex.lock(); |
| 399 | if (pool.end_flag) break; |
| 369 | 400 | } |
| 370 | | if (pool.end_flag) return; |
| 371 | 401 | releaseThreadToken(&connection); |
| 372 | 402 | pool.cond.wait(&pool.mutex); |
| 373 | 403 | } |
| 404 | |
| 405 | releaseThreadToken(&connection); |
| 406 | pool.mutex.unlock(); |
| 374 | 407 | } |
| 375 | 408 | |
| 376 | 409 | pub fn waitAndWork(pool: *Pool, wait_group: *WaitGroup) void { |