| ... | ... | @@ -11,7 +11,7 @@ gpa: Allocator, |
| 11 | 11 | mutex: std.Thread.Mutex, |
| 12 | 12 | queue: std.DoublyLinkedList(void), |
| 13 | 13 | /// Atomic copy of queue.len |
| 14 | | queue_len: usize, |
| 14 | queue_len: u32, |
| 15 | 15 | free: std.DoublyLinkedList(void), |
| 16 | 16 | main_fiber: Fiber, |
| 17 | 17 | idle_count: usize, |
| ... | ... | @@ -20,8 +20,8 @@ exiting: bool, |
| 20 | 20 | |
| 21 | 21 | threadlocal var thread_index: u32 = undefined; |
| 22 | 22 | |
| 23 | | /// Empirically saw 10KB being used by the self-hosted backend for logging. |
| 24 | | const idle_stack_size = 64 * 1024; |
| 23 | /// Empirically saw >128KB being used by the self-hosted backend to panic. |
| 24 | const idle_stack_size = 256 * 1024; |
| 25 | 25 | |
| 26 | 26 | const io_uring_entries = 64; |
| 27 | 27 | |
| ... | ... | @@ -143,6 +143,7 @@ pub fn deinit(el: *EventLoop) void { |
| 143 | 143 | const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @alignCast(@ptrCast(el.threads.items.ptr)); |
| 144 | 144 | for (el.threads.items[1..]) |*thread| thread.thread.join(); |
| 145 | 145 | el.gpa.free(allocated_ptr[0..idle_stack_end_offset]); |
| 146 | el.* = undefined; |
| 146 | 147 | } |
| 147 | 148 | |
| 148 | 149 | fn yield(el: *EventLoop, optional_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void { |
| ... | ... | @@ -151,8 +152,9 @@ fn yield(el: *EventLoop, optional_fiber: ?*Fiber, pending_task: SwitchMessage.Pe |
| 151 | 152 | const ready_fiber: *Fiber = optional_fiber orelse if (ready_node: { |
| 152 | 153 | el.mutex.lock(); |
| 153 | 154 | defer el.mutex.unlock(); |
| 155 | const expected_queue_len = std.math.lossyCast(u32, el.queue.len); |
| 154 | 156 | const ready_node = el.queue.pop(); |
| 155 | | @atomicStore(usize, &el.queue_len, el.queue.len, .unordered); |
| 157 | _ = @cmpxchgStrong(u32, &el.queue_len, expected_queue_len, std.math.lossyCast(u32, el.queue.len), .monotonic, .monotonic); |
| 156 | 158 | break :ready_node ready_node; |
| 157 | 159 | }) |ready_node| |
| 158 | 160 | @alignCast(@fieldParentPtr("queue_node", ready_node)) |
| ... | ... | @@ -172,20 +174,16 @@ fn yield(el: *EventLoop, optional_fiber: ?*Fiber, pending_task: SwitchMessage.Pe |
| 172 | 174 | } |
| 173 | 175 | |
| 174 | 176 | fn schedule(el: *EventLoop, fiber: *Fiber) void { |
| 177 | std.log.debug("scheduling {*}", .{fiber}); |
| 175 | 178 | if (idle_count: { |
| 176 | 179 | el.mutex.lock(); |
| 177 | 180 | defer el.mutex.unlock(); |
| 181 | const expected_queue_len = std.math.lossyCast(u32, el.queue.len); |
| 178 | 182 | el.queue.append(&fiber.queue_node); |
| 179 | | @atomicStore(usize, &el.queue_len, el.queue.len, .unordered); |
| 183 | _ = @cmpxchgStrong(u32, &el.queue_len, expected_queue_len, std.math.lossyCast(u32, el.queue.len), .monotonic, .monotonic); |
| 180 | 184 | break :idle_count el.idle_count; |
| 181 | 185 | } > 0) { |
| 182 | | _ = std.os.linux.futex2_wake(&el.queue_len, std.math.maxInt(usize), 1, switch (@bitSizeOf(usize)) { |
| 183 | | 8 => std.os.linux.FUTEX2.SIZE_U8, |
| 184 | | 16 => std.os.linux.FUTEX2.SIZE_U16, |
| 185 | | 32 => std.os.linux.FUTEX2.SIZE_U32, |
| 186 | | 64 => std.os.linux.FUTEX2.SIZE_U64, |
| 187 | | else => @compileError("unsupported @sizeOf(usize)"), |
| 188 | | } | std.os.linux.FUTEX2.PRIVATE); // TODO: io_uring |
| 186 | _ = std.os.linux.futex2_wake(&el.queue_len, std.math.maxInt(u32), 1, std.os.linux.FUTEX2.SIZE_U32 | std.os.linux.FUTEX2.PRIVATE); // TODO: io_uring |
| 189 | 187 | return; |
| 190 | 188 | } |
| 191 | 189 | if (el.threads.items.len == el.threads.capacity) return; |
| ... | ... | @@ -226,8 +224,8 @@ fn threadEntry(el: *EventLoop, index: usize) void { |
| 226 | 224 | el.idle(); |
| 227 | 225 | } |
| 228 | 226 | |
| 229 | | const UserData = enum(u64) { |
| 230 | | queue_len_futex_wait, |
| 227 | const CompletionKey = enum(u64) { |
| 228 | queue_len_futex_wait = 1, |
| 231 | 229 | _, |
| 232 | 230 | }; |
| 233 | 231 | |
| ... | ... | @@ -235,33 +233,44 @@ fn idle(el: *EventLoop) void { |
| 235 | 233 | const thread: *Thread = &el.threads.items[thread_index]; |
| 236 | 234 | const iou = &thread.io_uring; |
| 237 | 235 | var cqes_buffer: [io_uring_entries]std.os.linux.io_uring_cqe = undefined; |
| 238 | | var futex_is_scheduled: bool = false; |
| 236 | var queue_len_futex_is_scheduled: bool = false; |
| 239 | 237 | |
| 240 | 238 | while (true) { |
| 241 | 239 | el.yield(null, .nothing); |
| 242 | 240 | if (@atomicLoad(bool, &el.exiting, .acquire)) return; |
| 243 | | if (!futex_is_scheduled) { |
| 241 | if (!queue_len_futex_is_scheduled) { |
| 244 | 242 | const sqe = getSqe(&thread.io_uring); |
| 245 | | sqe.prep_rw(.FUTEX_WAIT, switch (@bitSizeOf(usize)) { |
| 246 | | 8 => std.os.linux.FUTEX2.SIZE_U8, |
| 247 | | 16 => std.os.linux.FUTEX2.SIZE_U16, |
| 248 | | 32 => std.os.linux.FUTEX2.SIZE_U32, |
| 249 | | 64 => std.os.linux.FUTEX2.SIZE_U64, |
| 250 | | else => @compileError("unsupported @sizeOf(usize)"), |
| 251 | | } | std.os.linux.FUTEX2.PRIVATE, @intFromPtr(&el.queue_len), 0, 0); |
| 252 | | sqe.addr3 = std.math.maxInt(u64); |
| 253 | | sqe.user_data = @intFromEnum(UserData.queue_len_futex_wait); |
| 254 | | futex_is_scheduled = true; |
| 243 | sqe.prep_rw(.FUTEX_WAIT, std.os.linux.FUTEX2.SIZE_U32 | std.os.linux.FUTEX2.PRIVATE, @intFromPtr(&el.queue_len), 0, 0); |
| 244 | sqe.addr3 = std.math.maxInt(u32); |
| 245 | sqe.user_data = @intFromEnum(CompletionKey.queue_len_futex_wait); |
| 246 | queue_len_futex_is_scheduled = true; |
| 255 | 247 | } |
| 256 | 248 | _ = iou.submit_and_wait(1) catch |err| switch (err) { |
| 257 | | error.SignalInterrupt => 0, |
| 249 | error.SignalInterrupt => std.log.debug("submit_and_wait: SignalInterrupt", .{}), |
| 258 | 250 | else => @panic(@errorName(err)), |
| 259 | 251 | }; |
| 260 | 252 | for (cqes_buffer[0 .. iou.copy_cqes(&cqes_buffer, 1) catch |err| switch (err) { |
| 261 | | error.SignalInterrupt => 0, |
| 253 | error.SignalInterrupt => cqes_len: { |
| 254 | std.log.debug("copy_cqes: SignalInterrupt", .{}); |
| 255 | break :cqes_len 0; |
| 256 | }, |
| 262 | 257 | else => @panic(@errorName(err)), |
| 263 | | }]) |cqe| switch (@as(UserData, @enumFromInt(cqe.user_data))) { |
| 264 | | .queue_len_futex_wait => futex_is_scheduled = false, |
| 258 | }]) |cqe| switch (@as(CompletionKey, @enumFromInt(cqe.user_data))) { |
| 259 | .queue_len_futex_wait => { |
| 260 | switch (errno(cqe.res)) { |
| 261 | .SUCCESS, .AGAIN => {}, |
| 262 | .INVAL => unreachable, |
| 263 | else => |err| { |
| 264 | std.posix.unexpectedErrno(err) catch {}; |
| 265 | @panic("unexpected"); |
| 266 | }, |
| 267 | } |
| 268 | std.log.debug("{*} woken up with queue size of {d}", .{ |
| 269 | &thread.idle_context, |
| 270 | @atomicLoad(u32, &el.queue_len, .unordered), |
| 271 | }); |
| 272 | queue_len_futex_is_scheduled = false; |
| 273 | }, |
| 265 | 274 | _ => { |
| 266 | 275 | const fiber: *Fiber = @ptrFromInt(cqe.user_data); |
| 267 | 276 | const res: *i32 = @ptrCast(@alignCast(fiber.resultPointer())); |
| ... | ... | @@ -296,14 +305,8 @@ const SwitchMessage = struct { |
| 296 | 305 | }, |
| 297 | 306 | .exit => { |
| 298 | 307 | @atomicStore(bool, &el.exiting, true, .unordered); |
| 299 | | @atomicStore(usize, &el.queue_len, std.math.maxInt(usize), .release); |
| 300 | | _ = std.os.linux.futex2_wake(&el.queue_len, std.math.maxInt(usize), std.math.maxInt(i32), switch (@bitSizeOf(usize)) { |
| 301 | | 8 => std.os.linux.FUTEX2.SIZE_U8, |
| 302 | | 16 => std.os.linux.FUTEX2.SIZE_U16, |
| 303 | | 32 => std.os.linux.FUTEX2.SIZE_U32, |
| 304 | | 64 => std.os.linux.FUTEX2.SIZE_U64, |
| 305 | | else => @compileError("unsupported @sizeOf(usize)"), |
| 306 | | } | std.os.linux.FUTEX2.PRIVATE); // TODO: use io_uring |
| 308 | @atomicStore(u32, &el.queue_len, std.math.maxInt(u32), .release); |
| 309 | _ = std.os.linux.futex2_wake(&el.queue_len, std.math.maxInt(u32), std.math.maxInt(i32), std.os.linux.FUTEX2.SIZE_U32 | std.os.linux.FUTEX2.PRIVATE); // TODO: use io_uring |
| 307 | 310 | }, |
| 308 | 311 | } |
| 309 | 312 | } |