| ... | @@ -10,38 +10,50 @@ const IoUring = std.os.linux.IoUring; | ... | @@ -10,38 +10,50 @@ const IoUring = std.os.linux.IoUring; |
| 10 | /// Must be a thread-safe allocator. | 10 | /// Must be a thread-safe allocator. |
| 11 | gpa: Allocator, | 11 | gpa: Allocator, |
| 12 | mutex: std.Thread.Mutex, | 12 | mutex: std.Thread.Mutex, |
| 13 | queue: std.DoublyLinkedList, | | |
| 14 | /// Atomic copy of queue.len | | |
| 15 | queue_len: u32, | | |
| 16 | free: std.DoublyLinkedList, | | |
| 17 | main_fiber: Fiber, | 13 | main_fiber: Fiber, |
| 18 | idle_count: usize, | 14 | threads: Thread.List, |
| 19 | threads: std.ArrayListUnmanaged(Thread), | | |
| 20 | exiting: bool, | | |
| 21 | | | |
| 22 | threadlocal var thread_index: u32 = undefined; | | |
| 23 | | 15 | |
| 24 | /// Empirically saw >128KB being used by the self-hosted backend to panic. | 16 | /// Empirically saw >128KB being used by the self-hosted backend to panic. |
| 25 | const idle_stack_size = 256 * 1024; | 17 | const idle_stack_size = 256 * 1024; |
| 26 | | 18 | |
| | 19 | const max_idle_search = 4; |
| | 20 | const max_steal_ready_search = 4; |
| | 21 | |
| 27 | const io_uring_entries = 64; | 22 | const io_uring_entries = 64; |
| 28 | | 23 | |
| 29 | const Thread = struct { | 24 | const Thread = struct { |
| 30 | thread: std.Thread, | 25 | thread: std.Thread, |
| 31 | idle_context: Context, | 26 | idle_context: Context, |
| 32 | current_context: *Context, | 27 | current_context: *Context, |
| | 28 | ready_queue: ?*Fiber, |
| | 29 | free_queue: ?*Fiber, |
| 33 | io_uring: IoUring, | 30 | io_uring: IoUring, |
| | 31 | idle_search_index: u32, |
| | 32 | steal_ready_search_index: u32, |
| | 33 | |
| | 34 | threadlocal var index: u32 = undefined; |
| | 35 | |
| | 36 | fn current(el: *EventLoop) *Thread { |
| | 37 | return &el.threads.allocated[index]; |
| | 38 | } |
| 34 | | 39 | |
| 35 | fn currentFiber(thread: *Thread) *Fiber { | 40 | fn currentFiber(thread: *Thread) *Fiber { |
| 36 | return @fieldParentPtr("context", thread.current_context); | 41 | return @fieldParentPtr("context", thread.current_context); |
| 37 | } | 42 | } |
| | 43 | |
| | 44 | const List = struct { |
| | 45 | allocated: []Thread, |
| | 46 | reserved: u32, |
| | 47 | active: u32, |
| | 48 | }; |
| 38 | }; | 49 | }; |
| 39 | | 50 | |
| 40 | const Fiber = struct { | 51 | const Fiber = struct { |
| 41 | context: Context, | 52 | context: Context, |
| 42 | awaiter: ?*Fiber, | 53 | awaiter: ?*Fiber, |
| 43 | queue_node: std.DoublyLinkedList.Node, | 54 | queue_next: ?*Fiber, |
| 44 | result_align: Alignment, | 55 | can_cancel: bool, |
| | 56 | canceled: bool, |
| 45 | | 57 | |
| 46 | const finished: ?*Fiber = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), @alignOf(Fiber))); | 58 | const finished: ?*Fiber = @ptrFromInt(std.mem.alignBackward(usize, std.math.maxInt(usize), @alignOf(Fiber))); |
| 47 | | 59 | |
| ... | @@ -63,14 +75,13 @@ const Fiber = struct { | ... | @@ -63,14 +75,13 @@ const Fiber = struct { |
| 63 | ); | 75 | ); |
| 64 | | 76 | |
| 65 | fn allocate(el: *EventLoop) error{OutOfMemory}!*Fiber { | 77 | fn allocate(el: *EventLoop) error{OutOfMemory}!*Fiber { |
| 66 | return if (free_node: { | 78 | const thread: *Thread = .current(el); |
| 67 | el.mutex.lock(); | 79 | if (thread.free_queue) |free_fiber| { |
| 68 | defer el.mutex.unlock(); | 80 | thread.free_queue = free_fiber.queue_next; |
| 69 | break :free_node el.free.pop(); | 81 | free_fiber.queue_next = null; |
| 70 | }) |free_node| | 82 | return free_fiber; |
| 71 | @alignCast(@fieldParentPtr("queue_node", free_node)) | 83 | } |
| 72 | else | 84 | return @ptrCast(try el.gpa.alignedAlloc(u8, @alignOf(Fiber), allocation_size)); |
| 73 | @ptrCast(try el.gpa.alignedAlloc(u8, @alignOf(Fiber), allocation_size)); | | |
| 74 | } | 85 | } |
| 75 | | 86 | |
| 76 | fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 { | 87 | fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 { |
| ... | @@ -82,9 +93,15 @@ const Fiber = struct { | ... | @@ -82,9 +93,15 @@ const Fiber = struct { |
| 82 | return allocated_slice[allocated_slice.len..].ptr; | 93 | return allocated_slice[allocated_slice.len..].ptr; |
| 83 | } | 94 | } |
| 84 | | 95 | |
| 85 | fn resultPointer(f: *Fiber) [*]u8 { | 96 | fn resultPointer(f: *Fiber, comptime Result: type) *Result { |
| 86 | return @ptrFromInt(f.result_align.forward(@intFromPtr(f) + @sizeOf(Fiber))); | 97 | return @alignCast(@ptrCast(f.resultBytes(.of(Result)))); |
| | 98 | } |
| | 99 | |
| | 100 | fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 { |
| | 101 | return @ptrFromInt(alignment.forward(@intFromPtr(f) + @sizeOf(Fiber))); |
| 87 | } | 102 | } |
| | 103 | |
| | 104 | const Queue = struct { head: *Fiber, tail: *Fiber }; |
| 88 | }; | 105 | }; |
| 89 | | 106 | |
| 90 | pub fn io(el: *EventLoop) Io { | 107 | pub fn io(el: *EventLoop) Io { |
| ... | @@ -93,6 +110,8 @@ pub fn io(el: *EventLoop) Io { | ... | @@ -93,6 +110,8 @@ pub fn io(el: *EventLoop) Io { |
| 93 | .vtable = &.{ | 110 | .vtable = &.{ |
| 94 | .@"async" = @"async", | 111 | .@"async" = @"async", |
| 95 | .@"await" = @"await", | 112 | .@"await" = @"await", |
| | 113 | .cancel = cancel, |
| | 114 | .cancelRequested = cancelRequested, |
| 96 | .createFile = createFile, | 115 | .createFile = createFile, |
| 97 | .openFile = openFile, | 116 | .openFile = openFile, |
| 98 | .closeFile = closeFile, | 117 | .closeFile = closeFile, |
| ... | @@ -110,58 +129,86 @@ pub fn init(el: *EventLoop, gpa: Allocator) !void { | ... | @@ -110,58 +129,86 @@ pub fn init(el: *EventLoop, gpa: Allocator) !void { |
| 110 | el.* = .{ | 129 | el.* = .{ |
| 111 | .gpa = gpa, | 130 | .gpa = gpa, |
| 112 | .mutex = .{}, | 131 | .mutex = .{}, |
| 113 | .queue = .{}, | 132 | .main_fiber = .{ |
| 114 | .queue_len = 0, | 133 | .context = undefined, |
| 115 | .free = .{}, | 134 | .awaiter = null, |
| 116 | .main_fiber = undefined, | 135 | .queue_next = null, |
| 117 | .idle_count = 0, | 136 | .can_cancel = false, |
| 118 | .threads = .initBuffer(@ptrCast(allocated_slice[0..threads_size])), | 137 | .canceled = false, |
| 119 | .exiting = false, | 138 | }, |
| | 139 | .threads = .{ |
| | 140 | .allocated = @ptrCast(allocated_slice[0..threads_size]), |
| | 141 | .reserved = 1, |
| | 142 | .active = 1, |
| | 143 | }, |
| 120 | }; | 144 | }; |
| 121 | thread_index = 0; | 145 | Thread.index = 0; |
| 122 | const main_thread = el.threads.addOneAssumeCapacity(); | 146 | const main_thread = &el.threads.allocated[0]; |
| 123 | main_thread.io_uring = try IoUring.init(io_uring_entries, 0); | | |
| 124 | const idle_stack_end: [*]usize = @alignCast(@ptrCast(allocated_slice[idle_stack_end_offset..].ptr)); | 147 | const idle_stack_end: [*]usize = @alignCast(@ptrCast(allocated_slice[idle_stack_end_offset..].ptr)); |
| 125 | (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)}; | 148 | (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)}; |
| 126 | main_thread.idle_context = .{ | 149 | main_thread.* = .{ |
| 127 | .rsp = @intFromPtr(idle_stack_end - 1), | 150 | .thread = undefined, |
| 128 | .rbp = 0, | 151 | .idle_context = .{ |
| 129 | .rip = @intFromPtr(&mainIdleEntry), | 152 | .rsp = @intFromPtr(idle_stack_end - 1), |
| | 153 | .rbp = 0, |
| | 154 | .rip = @intFromPtr(&mainIdleEntry), |
| | 155 | }, |
| | 156 | .current_context = &el.main_fiber.context, |
| | 157 | .ready_queue = null, |
| | 158 | .free_queue = null, |
| | 159 | .io_uring = try IoUring.init(io_uring_entries, 0), |
| | 160 | .idle_search_index = 1, |
| | 161 | .steal_ready_search_index = 1, |
| 130 | }; | 162 | }; |
| | 163 | errdefer main_thread.io_uring.deinit(); |
| 131 | std.log.debug("created main idle {*}", .{&main_thread.idle_context}); | 164 | std.log.debug("created main idle {*}", .{&main_thread.idle_context}); |
| 132 | std.log.debug("created main {*}", .{&el.main_fiber}); | 165 | std.log.debug("created main {*}", .{&el.main_fiber}); |
| 133 | main_thread.current_context = &el.main_fiber.context; | | |
| 134 | } | 166 | } |
| 135 | | 167 | |
| 136 | pub fn deinit(el: *EventLoop) void { | 168 | pub fn deinit(el: *EventLoop) void { |
| 137 | assert(el.queue.len == 0); // pending async | 169 | const active_threads = @atomicLoad(u32, &el.threads.active, .acquire); |
| | 170 | for (el.threads.allocated[0..active_threads]) |*thread| |
| | 171 | assert(@atomicLoad(?*Fiber, &thread.ready_queue, .unordered) == null); // pending async |
| 138 | el.yield(null, .exit); | 172 | el.yield(null, .exit); |
| 139 | while (el.free.pop()) |free_node| { | 173 | const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @alignCast(@ptrCast(el.threads.allocated.ptr)); |
| 140 | const free_fiber: *Fiber = @alignCast(@fieldParentPtr("queue_node", free_node)); | 174 | const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max); |
| 141 | el.gpa.free(free_fiber.allocatedSlice()); | 175 | for (el.threads.allocated[1..active_threads]) |*thread| { |
| | 176 | thread.thread.join(); |
| | 177 | while (thread.free_queue) |free_fiber| { |
| | 178 | thread.free_queue = free_fiber.queue_next; |
| | 179 | free_fiber.queue_next = null; |
| | 180 | el.gpa.free(free_fiber.allocatedSlice()); |
| | 181 | } |
| 142 | } | 182 | } |
| 143 | const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.capacity * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max); | | |
| 144 | const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @alignCast(@ptrCast(el.threads.items.ptr)); | | |
| 145 | for (el.threads.items[1..]) |*thread| thread.thread.join(); | | |
| 146 | el.gpa.free(allocated_ptr[0..idle_stack_end_offset]); | 183 | el.gpa.free(allocated_ptr[0..idle_stack_end_offset]); |
| 147 | el.* = undefined; | 184 | el.* = undefined; |
| 148 | } | 185 | } |
| 149 | | 186 | |
| 150 | fn yield(el: *EventLoop, optional_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void { | 187 | fn yield(el: *EventLoop, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void { |
| 151 | const thread: *Thread = &el.threads.items[thread_index]; | 188 | const thread: *Thread = .current(el); |
| 152 | const ready_context: *Context = ready_context: { | 189 | const ready_context: *Context = if (maybe_ready_fiber) |ready_fiber| |
| 153 | const ready_fiber: *Fiber = optional_fiber orelse if (ready_node: { | 190 | &ready_fiber.context |
| 154 | el.mutex.lock(); | 191 | else if (thread.ready_queue) |ready_fiber| ready_context: { |
| 155 | defer el.mutex.unlock(); | 192 | thread.ready_queue = ready_fiber.queue_next; |
| 156 | const expected_queue_len = std.math.lossyCast(u32, el.queue.len); | 193 | ready_fiber.queue_next = null; |
| 157 | const ready_node = el.queue.pop(); | | |
| 158 | _ = @cmpxchgStrong(u32, &el.queue_len, expected_queue_len, std.math.lossyCast(u32, el.queue.len), .monotonic, .monotonic); | | |
| 159 | break :ready_node ready_node; | | |
| 160 | }) |ready_node| | | |
| 161 | @alignCast(@fieldParentPtr("queue_node", ready_node)) | | |
| 162 | else | | |
| 163 | break :ready_context &thread.idle_context; | | |
| 164 | break :ready_context &ready_fiber.context; | 194 | break :ready_context &ready_fiber.context; |
| | 195 | } else ready_context: { |
| | 196 | const ready_threads = @atomicLoad(u32, &el.threads.active, .acquire); |
| | 197 | break :ready_context for (0..max_steal_ready_search) |_| { |
| | 198 | defer thread.steal_ready_search_index += 1; |
| | 199 | if (thread.steal_ready_search_index == ready_threads) thread.steal_ready_search_index = 0; |
| | 200 | const steal_ready_search_thread = &el.threads.allocated[thread.steal_ready_search_index]; |
| | 201 | const ready_fiber = @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .acquire) orelse continue; |
| | 202 | if (@cmpxchgWeak( |
| | 203 | ?*Fiber, |
| | 204 | &steal_ready_search_thread.ready_queue, |
| | 205 | ready_fiber, |
| | 206 | @atomicLoad(?*Fiber, &ready_fiber.queue_next, .acquire), |
| | 207 | .acq_rel, |
| | 208 | .monotonic, |
| | 209 | )) |_| continue; |
| | 210 | break &ready_fiber.context; |
| | 211 | } else &thread.idle_context; |
| 165 | }; | 212 | }; |
| 166 | const message: SwitchMessage = .{ | 213 | const message: SwitchMessage = .{ |
| 167 | .contexts = .{ | 214 | .contexts = .{ |
| ... | @@ -174,111 +221,177 @@ fn yield(el: *EventLoop, optional_fiber: ?*Fiber, pending_task: SwitchMessage.Pe | ... | @@ -174,111 +221,177 @@ fn yield(el: *EventLoop, optional_fiber: ?*Fiber, pending_task: SwitchMessage.Pe |
| 174 | contextSwitch(&message).handle(el); | 221 | contextSwitch(&message).handle(el); |
| 175 | } | 222 | } |
| 176 | | 223 | |
| 177 | fn schedule(el: *EventLoop, fiber: *Fiber) void { | 224 | fn schedule(el: *EventLoop, thread: *Thread, ready_queue: Fiber.Queue) void { |
| 178 | std.log.debug("scheduling {*}", .{fiber}); | 225 | { |
| 179 | if (idle_count: { | 226 | var fiber = ready_queue.head; |
| 180 | el.mutex.lock(); | 227 | while (true) { |
| 181 | defer el.mutex.unlock(); | 228 | std.log.debug("scheduling {*}", .{fiber}); |
| 182 | const expected_queue_len = std.math.lossyCast(u32, el.queue.len); | 229 | fiber = fiber.queue_next orelse break; |
| 183 | el.queue.append(&fiber.queue_node); | 230 | } |
| 184 | _ = @cmpxchgStrong(u32, &el.queue_len, expected_queue_len, std.math.lossyCast(u32, el.queue.len), .monotonic, .monotonic); | 231 | assert(fiber == ready_queue.tail); |
| 185 | break :idle_count el.idle_count; | 232 | } |
| 186 | } > 0) { | 233 | // shared fields of previous `Thread` must be initialized before later ones are marked as active |
| 187 | _ = 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 | 234 | const new_thread_index = @atomicLoad(u32, &el.threads.active, .acquire); |
| | 235 | for (0..max_idle_search) |_| { |
| | 236 | defer thread.idle_search_index += 1; |
| | 237 | if (thread.idle_search_index == new_thread_index) thread.idle_search_index = 0; |
| | 238 | const idle_search_thread = &el.threads.allocated[thread.idle_search_index]; |
| | 239 | if (@cmpxchgWeak( |
| | 240 | ?*Fiber, |
| | 241 | &idle_search_thread.ready_queue, |
| | 242 | null, |
| | 243 | ready_queue.head, |
| | 244 | .acq_rel, |
| | 245 | .monotonic, |
| | 246 | )) |_| continue; |
| | 247 | getSqe(&thread.io_uring).* = .{ |
| | 248 | .opcode = .MSG_RING, |
| | 249 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, |
| | 250 | .ioprio = 0, |
| | 251 | .fd = idle_search_thread.io_uring.fd, |
| | 252 | .off = @intFromEnum(Completion.Key.wakeup), |
| | 253 | .addr = 0, |
| | 254 | .len = 0, |
| | 255 | .rw_flags = 0, |
| | 256 | .user_data = @intFromEnum(Completion.Key.wakeup), |
| | 257 | .buf_index = 0, |
| | 258 | .personality = 0, |
| | 259 | .splice_fd_in = 0, |
| | 260 | .addr3 = 0, |
| | 261 | .resv = 0, |
| | 262 | }; |
| 188 | return; | 263 | return; |
| 189 | } | 264 | } |
| 190 | if (el.threads.items.len == el.threads.capacity) return; | 265 | spawn_thread: { |
| 191 | const thread = el.threads.addOneAssumeCapacity(); | 266 | // previous failed reservations must have completed before retrying |
| 192 | thread.thread = std.Thread.spawn(.{ | 267 | if (new_thread_index == el.threads.allocated.len or @cmpxchgWeak( |
| 193 | .stack_size = idle_stack_size, | 268 | u32, |
| 194 | .allocator = el.gpa, | 269 | &el.threads.reserved, |
| 195 | }, threadEntry, .{ el, el.threads.items.len - 1 }) catch { | 270 | new_thread_index, |
| 196 | el.threads.items.len -= 1; | 271 | new_thread_index + 1, |
| | 272 | .acquire, |
| | 273 | .monotonic, |
| | 274 | ) != null) break :spawn_thread; |
| | 275 | const new_thread = &el.threads.allocated[new_thread_index]; |
| | 276 | const next_thread_index = new_thread_index + 1; |
| | 277 | new_thread.* = .{ |
| | 278 | .thread = undefined, |
| | 279 | .idle_context = undefined, |
| | 280 | .current_context = &new_thread.idle_context, |
| | 281 | .ready_queue = ready_queue.head, |
| | 282 | .free_queue = null, |
| | 283 | .io_uring = IoUring.init(io_uring_entries, 0) catch |err| { |
| | 284 | @atomicStore(u32, &el.threads.reserved, new_thread_index, .release); |
| | 285 | // no more access to `thread` after giving up reservation |
| | 286 | std.log.warn("unable to create worker thread due to io_uring init failure: {s}", .{@errorName(err)}); |
| | 287 | break :spawn_thread; |
| | 288 | }, |
| | 289 | .idle_search_index = next_thread_index, |
| | 290 | .steal_ready_search_index = next_thread_index, |
| | 291 | }; |
| | 292 | new_thread.thread = std.Thread.spawn(.{ |
| | 293 | .stack_size = idle_stack_size, |
| | 294 | .allocator = el.gpa, |
| | 295 | }, threadEntry, .{ el, new_thread_index }) catch |err| { |
| | 296 | new_thread.io_uring.deinit(); |
| | 297 | @atomicStore(u32, &el.threads.reserved, new_thread_index, .release); |
| | 298 | // no more access to `thread` after giving up reservation |
| | 299 | std.log.warn("unable to create worker thread due spawn failure: {s}", .{@errorName(err)}); |
| | 300 | break :spawn_thread; |
| | 301 | }; |
| | 302 | // shared fields of `Thread` must be initialized before being marked active |
| | 303 | @atomicStore(u32, &el.threads.active, next_thread_index, .release); |
| 197 | return; | 304 | return; |
| 198 | }; | 305 | } |
| | 306 | // nobody wanted it, so just queue it on ourselves |
| | 307 | while (@cmpxchgWeak( |
| | 308 | ?*Fiber, |
| | 309 | &thread.ready_queue, |
| | 310 | ready_queue.tail.queue_next, |
| | 311 | ready_queue.head, |
| | 312 | .acq_rel, |
| | 313 | .acquire, |
| | 314 | )) |old_head| ready_queue.tail.queue_next = old_head; |
| 199 | } | 315 | } |
| 200 | | 316 | |
| 201 | fn recycle(el: *EventLoop, fiber: *Fiber) void { | 317 | fn recycle(el: *EventLoop, fiber: *Fiber) void { |
| | 318 | const thread: *Thread = .current(el); |
| 202 | std.log.debug("recyling {*}", .{fiber}); | 319 | std.log.debug("recyling {*}", .{fiber}); |
| | 320 | assert(fiber.queue_next == null); |
| 203 | @memset(fiber.allocatedSlice(), undefined); | 321 | @memset(fiber.allocatedSlice(), undefined); |
| 204 | el.mutex.lock(); | 322 | fiber.queue_next = thread.free_queue; |
| 205 | defer el.mutex.unlock(); | 323 | thread.free_queue = fiber; |
| 206 | el.free.append(&fiber.queue_node); | | |
| 207 | } | 324 | } |
| 208 | | 325 | |
| 209 | fn mainIdle(el: *EventLoop, message: *const SwitchMessage) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Context)))) noreturn { | 326 | fn mainIdle(el: *EventLoop, message: *const SwitchMessage) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Context)))) noreturn { |
| 210 | message.handle(el); | 327 | message.handle(el); |
| 211 | el.idle(); | 328 | const thread: *Thread = &el.threads.allocated[0]; |
| | 329 | el.idle(thread); |
| 212 | el.yield(&el.main_fiber, .nothing); | 330 | el.yield(&el.main_fiber, .nothing); |
| 213 | unreachable; // switched to dead fiber | 331 | unreachable; // switched to dead fiber |
| 214 | } | 332 | } |
| 215 | | 333 | |
| 216 | fn threadEntry(el: *EventLoop, index: usize) void { | 334 | fn threadEntry(el: *EventLoop, index: u32) void { |
| 217 | thread_index = @intCast(index); | 335 | Thread.index = index; |
| 218 | const thread: *Thread = &el.threads.items[index]; | 336 | const thread: *Thread = &el.threads.allocated[index]; |
| 219 | std.log.debug("created thread idle {*}", .{&thread.idle_context}); | 337 | std.log.debug("created thread idle {*}", .{&thread.idle_context}); |
| 220 | thread.io_uring = IoUring.init(io_uring_entries, 0) catch |err| { | 338 | el.idle(thread); |
| 221 | std.log.warn("exiting worker thread during init due to io_uring init failure: {s}", .{@errorName(err)}); | | |
| 222 | return; | | |
| 223 | }; | | |
| 224 | thread.current_context = &thread.idle_context; | | |
| 225 | el.idle(); | | |
| 226 | } | 339 | } |
| 227 | | 340 | |
| 228 | const CompletionKey = enum(u64) { | 341 | const Completion = struct { |
| 229 | queue_len_futex_wait = 1, | 342 | const Key = enum(usize) { |
| 230 | _, | 343 | unused, |
| | 344 | wakeup, |
| | 345 | cancel, |
| | 346 | cleanup, |
| | 347 | exit, |
| | 348 | /// *Fiber |
| | 349 | _, |
| | 350 | }; |
| | 351 | result: i32, |
| | 352 | flags: u32, |
| 231 | }; | 353 | }; |
| 232 | | 354 | |
| 233 | fn idle(el: *EventLoop) void { | 355 | fn idle(el: *EventLoop, thread: *Thread) void { |
| 234 | const thread: *Thread = &el.threads.items[thread_index]; | 356 | var maybe_ready_fiber: ?*Fiber = null; |
| 235 | const iou = &thread.io_uring; | | |
| 236 | var cqes_buffer: [io_uring_entries]std.os.linux.io_uring_cqe = undefined; | | |
| 237 | var queue_len_futex_is_scheduled: bool = false; | | |
| 238 | | | |
| 239 | while (true) { | 357 | while (true) { |
| 240 | el.yield(null, .nothing); | 358 | el.yield(maybe_ready_fiber, .nothing); |
| 241 | if (@atomicLoad(bool, &el.exiting, .acquire)) return; | 359 | maybe_ready_fiber = null; |
| 242 | if (!queue_len_futex_is_scheduled) { | 360 | _ = thread.io_uring.submit_and_wait(1) catch |err| switch (err) { |
| 243 | const sqe = getSqe(&thread.io_uring); | 361 | error.SignalInterrupt => std.log.warn("submit_and_wait failed with SignalInterrupt", .{}), |
| 244 | sqe.prep_rw(.FUTEX_WAIT, std.os.linux.FUTEX2.SIZE_U32 | std.os.linux.FUTEX2.PRIVATE, @intFromPtr(&el.queue_len), 0, 0); | 362 | else => |e| @panic(@errorName(e)), |
| 245 | sqe.addr3 = std.math.maxInt(u32); | | |
| 246 | sqe.user_data = @intFromEnum(CompletionKey.queue_len_futex_wait); | | |
| 247 | queue_len_futex_is_scheduled = true; | | |
| 248 | } | | |
| 249 | _ = iou.submit_and_wait(1) catch |err| switch (err) { | | |
| 250 | error.SignalInterrupt => std.log.debug("submit_and_wait: SignalInterrupt", .{}), | | |
| 251 | else => @panic(@errorName(err)), | | |
| 252 | }; | 363 | }; |
| 253 | for (cqes_buffer[0 .. iou.copy_cqes(&cqes_buffer, 1) catch |err| switch (err) { | 364 | var cqes_buffer: [io_uring_entries]std.os.linux.io_uring_cqe = undefined; |
| | 365 | var maybe_ready_queue: ?Fiber.Queue = null; |
| | 366 | for (cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) { |
| 254 | error.SignalInterrupt => cqes_len: { | 367 | error.SignalInterrupt => cqes_len: { |
| 255 | std.log.debug("copy_cqes: SignalInterrupt", .{}); | 368 | std.log.warn("copy_cqes failed with SignalInterrupt", .{}); |
| 256 | break :cqes_len 0; | 369 | break :cqes_len 0; |
| 257 | }, | 370 | }, |
| 258 | else => @panic(@errorName(err)), | 371 | else => |e| @panic(@errorName(e)), |
| 259 | }]) |cqe| switch (@as(CompletionKey, @enumFromInt(cqe.user_data))) { | 372 | }]) |cqe| switch (@as(Completion.Key, @enumFromInt(cqe.user_data))) { |
| 260 | .queue_len_futex_wait => { | 373 | .unused => unreachable, // bad submission queued? |
| 261 | switch (errno(cqe.res)) { | 374 | .wakeup => {}, |
| 262 | .SUCCESS, .AGAIN => {}, | 375 | .cancel => {}, |
| 263 | .INVAL => unreachable, | 376 | .cleanup => @panic("failed to notify other threads that we are exiting"), |
| 264 | else => |err| { | 377 | .exit => { |
| 265 | std.posix.unexpectedErrno(err) catch {}; | 378 | assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async |
| 266 | @panic("unexpected"); | 379 | return; |
| 267 | }, | | |
| 268 | } | | |
| 269 | std.log.debug("{*} woken up with queue size of {d}", .{ | | |
| 270 | &thread.idle_context, | | |
| 271 | @atomicLoad(u32, &el.queue_len, .unordered), | | |
| 272 | }); | | |
| 273 | queue_len_futex_is_scheduled = false; | | |
| 274 | }, | 380 | }, |
| 275 | _ => { | 381 | _ => { |
| 276 | const fiber: *Fiber = @ptrFromInt(cqe.user_data); | 382 | const fiber: *Fiber = @ptrFromInt(cqe.user_data); |
| 277 | const res: *i32 = @ptrCast(@alignCast(fiber.resultPointer())); | 383 | assert(fiber.queue_next == null); |
| 278 | res.* = cqe.res; | 384 | fiber.resultPointer(Completion).* = .{ |
| 279 | el.schedule(fiber); | 385 | .result = cqe.res, |
| | 386 | .flags = cqe.flags, |
| | 387 | }; |
| | 388 | if (maybe_ready_fiber == null) maybe_ready_fiber = fiber else if (maybe_ready_queue) |*ready_queue| { |
| | 389 | ready_queue.tail.queue_next = fiber; |
| | 390 | ready_queue.tail = fiber; |
| | 391 | } else maybe_ready_queue = .{ .head = fiber, .tail = fiber }; |
| 280 | }, | 392 | }, |
| 281 | }; | 393 | }; |
| | 394 | if (maybe_ready_queue) |ready_queue| el.schedule(thread, ready_queue); |
| 282 | } | 395 | } |
| 283 | } | 396 | } |
| 284 | | 397 | |
| ... | @@ -296,18 +409,37 @@ const SwitchMessage = struct { | ... | @@ -296,18 +409,37 @@ const SwitchMessage = struct { |
| 296 | }; | 409 | }; |
| 297 | | 410 | |
| 298 | fn handle(message: *const SwitchMessage, el: *EventLoop) void { | 411 | fn handle(message: *const SwitchMessage, el: *EventLoop) void { |
| 299 | const thread: *Thread = &el.threads.items[thread_index]; | 412 | const thread: *Thread = .current(el); |
| 300 | thread.current_context = message.contexts.ready; | 413 | thread.current_context = message.contexts.ready; |
| 301 | switch (message.pending_task) { | 414 | switch (message.pending_task) { |
| 302 | .nothing => {}, | 415 | .nothing => {}, |
| 303 | .register_awaiter => |awaiter| { | 416 | .register_awaiter => |awaiter| { |
| 304 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | 417 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); |
| 305 | if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) el.schedule(prev_fiber); | 418 | if (@atomicRmw( |
| | 419 | ?*Fiber, |
| | 420 | awaiter, |
| | 421 | .Xchg, |
| | 422 | prev_fiber, |
| | 423 | .acq_rel, |
| | 424 | ) == Fiber.finished) el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); |
| 306 | }, | 425 | }, |
| 307 | .exit => { | 426 | .exit => for (el.threads.allocated[0..@atomicLoad(u32, &el.threads.active, .acquire)]) |*each_thread| { |
| 308 | @atomicStore(bool, &el.exiting, true, .unordered); | 427 | getSqe(&thread.io_uring).* = .{ |
| 309 | @atomicStore(u32, &el.queue_len, std.math.maxInt(u32), .release); | 428 | .opcode = .MSG_RING, |
| 310 | _ = 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 | 429 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, |
| | 430 | .ioprio = 0, |
| | 431 | .fd = each_thread.io_uring.fd, |
| | 432 | .off = @intFromEnum(Completion.Key.exit), |
| | 433 | .addr = 0, |
| | 434 | .len = 0, |
| | 435 | .rw_flags = 0, |
| | 436 | .user_data = @intFromEnum(Completion.Key.cleanup), |
| | 437 | .buf_index = 0, |
| | 438 | .personality = 0, |
| | 439 | .splice_fd_in = 0, |
| | 440 | .addr3 = 0, |
| | 441 | .resv = 0, |
| | 442 | }; |
| 311 | }, | 443 | }, |
| 312 | } | 444 | } |
| 313 | } | 445 | } |
| ... | @@ -374,7 +506,27 @@ fn fiberEntry() callconv(.naked) void { | ... | @@ -374,7 +506,27 @@ fn fiberEntry() callconv(.naked) void { |
| 374 | } | 506 | } |
| 375 | } | 507 | } |
| 376 | | 508 | |
| 377 | pub fn @"async"( | 509 | const AsyncClosure = struct { |
| | 510 | event_loop: *EventLoop, |
| | 511 | fiber: *Fiber, |
| | 512 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, |
| | 513 | result_align: Alignment, |
| | 514 | |
| | 515 | fn contextPointer(closure: *AsyncClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 { |
| | 516 | return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(AsyncClosure)); |
| | 517 | } |
| | 518 | |
| | 519 | fn call(closure: *AsyncClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn { |
| | 520 | message.handle(closure.event_loop); |
| | 521 | std.log.debug("{*} performing async", .{closure.fiber}); |
| | 522 | closure.start(closure.contextPointer(), closure.fiber.resultBytes(closure.result_align)); |
| | 523 | const awaiter = @atomicRmw(?*Fiber, &closure.fiber.awaiter, .Xchg, Fiber.finished, .acq_rel); |
| | 524 | closure.event_loop.yield(awaiter, .nothing); |
| | 525 | unreachable; // switched to dead fiber |
| | 526 | } |
| | 527 | }; |
| | 528 | |
| | 529 | fn @"async"( |
| 378 | userdata: ?*anyopaque, | 530 | userdata: ?*anyopaque, |
| 379 | result: []u8, | 531 | result: []u8, |
| 380 | result_alignment: Alignment, | 532 | result_alignment: Alignment, |
| ... | @@ -407,58 +559,79 @@ pub fn @"async"( | ... | @@ -407,58 +559,79 @@ pub fn @"async"( |
| 407 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | 559 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), |
| 408 | }, | 560 | }, |
| 409 | .awaiter = null, | 561 | .awaiter = null, |
| 410 | .queue_node = undefined, | 562 | .queue_next = null, |
| 411 | .result_align = result_alignment, | 563 | .can_cancel = false, |
| | 564 | .canceled = false, |
| 412 | }; | 565 | }; |
| 413 | closure.* = .{ | 566 | closure.* = .{ |
| 414 | .event_loop = event_loop, | 567 | .event_loop = event_loop, |
| 415 | .fiber = fiber, | 568 | .fiber = fiber, |
| 416 | .start = start, | 569 | .start = start, |
| | 570 | .result_align = result_alignment, |
| 417 | }; | 571 | }; |
| 418 | @memcpy(closure.contextPointer(), context); | 572 | @memcpy(closure.contextPointer(), context); |
| 419 | | 573 | |
| 420 | event_loop.schedule(fiber); | 574 | event_loop.schedule(.current(event_loop), .{ .head = fiber, .tail = fiber }); |
| 421 | return @ptrCast(fiber); | 575 | return @ptrCast(fiber); |
| 422 | } | 576 | } |
| 423 | | 577 | |
| 424 | const AsyncClosure = struct { | 578 | fn @"await"( |
| 425 | event_loop: *EventLoop, | 579 | userdata: ?*anyopaque, |
| 426 | fiber: *Fiber, | 580 | any_future: *std.Io.AnyFuture, |
| 427 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, | 581 | result: []u8, |
| 428 | | 582 | result_alignment: Alignment, |
| 429 | fn contextPointer(closure: *AsyncClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 { | 583 | ) void { |
| 430 | return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(AsyncClosure)); | | |
| 431 | } | | |
| 432 | | | |
| 433 | fn call(closure: *AsyncClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn { | | |
| 434 | message.handle(closure.event_loop); | | |
| 435 | std.log.debug("{*} performing async", .{closure.fiber}); | | |
| 436 | closure.start(closure.contextPointer(), closure.fiber.resultPointer()); | | |
| 437 | const awaiter = @atomicRmw(?*Fiber, &closure.fiber.awaiter, .Xchg, Fiber.finished, .acq_rel); | | |
| 438 | closure.event_loop.yield(awaiter, .nothing); | | |
| 439 | unreachable; // switched to dead fiber | | |
| 440 | } | | |
| 441 | }; | | |
| 442 | | | |
| 443 | pub fn @"await"(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []u8) void { | | |
| 444 | const event_loop: *EventLoop = @alignCast(@ptrCast(userdata)); | 584 | const event_loop: *EventLoop = @alignCast(@ptrCast(userdata)); |
| 445 | const future_fiber: *Fiber = @alignCast(@ptrCast(any_future)); | 585 | const future_fiber: *Fiber = @alignCast(@ptrCast(any_future)); |
| 446 | if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished) event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter }); | 586 | if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished) event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter }); |
| 447 | @memcpy(result, future_fiber.resultPointer()); | 587 | @memcpy(result, future_fiber.resultBytes(result_alignment)); |
| 448 | event_loop.recycle(future_fiber); | 588 | event_loop.recycle(future_fiber); |
| 449 | } | 589 | } |
| 450 | | 590 | |
| 451 | pub fn cancel(userdata: ?*anyopaque, any_future: *std.Io.AnyFuture, result: []u8) void { | 591 | fn cancel( |
| | 592 | userdata: ?*anyopaque, |
| | 593 | any_future: *std.Io.AnyFuture, |
| | 594 | result: []u8, |
| | 595 | result_alignment: Alignment, |
| | 596 | ) void { |
| 452 | const event_loop: *EventLoop = @alignCast(@ptrCast(userdata)); | 597 | const event_loop: *EventLoop = @alignCast(@ptrCast(userdata)); |
| 453 | const future_fiber: *Fiber = @alignCast(@ptrCast(any_future)); | 598 | const future_fiber: *Fiber = @alignCast(@ptrCast(any_future)); |
| 454 | // TODO set a flag that makes all IO operations for this fiber return error.Canceled | 599 | @atomicStore(bool, &future_fiber.canceled, true, .release); |
| 455 | if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished) event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter }); | 600 | if (@atomicLoad(bool, &future_fiber.can_cancel, .acquire)) { |
| 456 | @memcpy(result, future_fiber.resultPointer()); | 601 | const thread: *Thread = .current(event_loop); |
| 457 | event_loop.recycle(future_fiber); | 602 | getSqe(&thread.io_uring).* = .{ |
| | 603 | .opcode = .ASYNC_CANCEL, |
| | 604 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, |
| | 605 | .ioprio = 0, |
| | 606 | .fd = 0, |
| | 607 | .off = 0, |
| | 608 | .addr = @intFromPtr(future_fiber), |
| | 609 | .len = 0, |
| | 610 | .rw_flags = 0, |
| | 611 | .user_data = @intFromEnum(Completion.Key.cancel), |
| | 612 | .buf_index = 0, |
| | 613 | .personality = 0, |
| | 614 | .splice_fd_in = 0, |
| | 615 | .addr3 = 0, |
| | 616 | .resv = 0, |
| | 617 | }; |
| | 618 | } |
| | 619 | @"await"(userdata, any_future, result, result_alignment); |
| | 620 | } |
| | 621 | |
| | 622 | fn cancelRequested(userdata: ?*anyopaque) bool { |
| | 623 | const event_loop: *EventLoop = @alignCast(@ptrCast(userdata)); |
| | 624 | const thread: *Thread = .current(event_loop); |
| | 625 | return thread.currentFiber().canceled; |
| 458 | } | 626 | } |
| 459 | | 627 | |
| 460 | pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.CreateFlags) std.fs.File.OpenError!std.fs.File { | 628 | pub fn createFile( |
| 461 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | 629 | userdata: ?*anyopaque, |
| | 630 | dir: std.fs.Dir, |
| | 631 | sub_path: []const u8, |
| | 632 | flags: Io.CreateFlags, |
| | 633 | ) Io.FileOpenError!std.fs.File { |
| | 634 | const el: *EventLoop = @alignCast(@ptrCast(userdata)); |
| 462 | | 635 | |
| 463 | const posix = std.posix; | 636 | const posix = std.posix; |
| 464 | const sub_path_c = try posix.toPosixPath(sub_path); | 637 | const sub_path_c = try posix.toPosixPath(sub_path); |
| ... | @@ -497,22 +670,24 @@ pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, | ... | @@ -497,22 +670,24 @@ pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, |
| 497 | @panic("TODO"); | 670 | @panic("TODO"); |
| 498 | } | 671 | } |
| 499 | | 672 | |
| 500 | const thread: *Thread = &el.threads.items[thread_index]; | 673 | const thread: *Thread = .current(el); |
| 501 | const iou = &thread.io_uring; | 674 | const iou = &thread.io_uring; |
| 502 | const sqe = getSqe(iou); | | |
| 503 | const fiber = thread.currentFiber(); | 675 | const fiber = thread.currentFiber(); |
| | 676 | if (@atomicLoad(bool, &fiber.canceled, .acquire)) return error.AsyncCancel; |
| 504 | | 677 | |
| | 678 | const sqe = getSqe(iou); |
| 505 | sqe.prep_openat(dir.fd, &sub_path_c, os_flags, flags.mode); | 679 | sqe.prep_openat(dir.fd, &sub_path_c, os_flags, flags.mode); |
| 506 | sqe.user_data = @intFromPtr(fiber); | 680 | sqe.user_data = @intFromPtr(fiber); |
| 507 | | 681 | |
| | 682 | @atomicStore(bool, &fiber.can_cancel, true, .release); |
| 508 | el.yield(null, .nothing); | 683 | el.yield(null, .nothing); |
| | 684 | @atomicStore(bool, &fiber.can_cancel, false, .release); |
| 509 | | 685 | |
| 510 | const result: *i32 = @alignCast(@ptrCast(fiber.resultPointer()[0..@sizeOf(posix.fd_t)])); | 686 | const completion = fiber.resultPointer(Completion); |
| 511 | const rc = result.*; | 687 | switch (errno(completion.result)) { |
| 512 | switch (errno(rc)) { | 688 | .SUCCESS => return .{ .handle = completion.result }, |
| 513 | .SUCCESS => return .{ .handle = rc }, | | |
| 514 | .INTR => @panic("TODO is this reachable?"), | 689 | .INTR => @panic("TODO is this reachable?"), |
| 515 | .CANCELED => @panic("TODO figure out how this error code fits into things"), | 690 | .CANCELED => return error.AsyncCancel, |
| 516 | | 691 | |
| 517 | .FAULT => unreachable, | 692 | .FAULT => unreachable, |
| 518 | .INVAL => return error.BadPathName, | 693 | .INVAL => return error.BadPathName, |
| ... | @@ -541,8 +716,17 @@ pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, | ... | @@ -541,8 +716,17 @@ pub fn createFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, |
| 541 | } | 716 | } |
| 542 | } | 717 | } |
| 543 | | 718 | |
| 544 | pub fn openFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, flags: std.fs.File.OpenFlags) std.fs.File.OpenError!std.fs.File { | 719 | pub fn openFile( |
| 545 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | 720 | userdata: ?*anyopaque, |
| | 721 | dir: std.fs.Dir, |
| | 722 | sub_path: []const u8, |
| | 723 | flags: Io.OpenFlags, |
| | 724 | ) Io.FileOpenError!std.fs.File { |
| | 725 | const el: *EventLoop = @alignCast(@ptrCast(userdata)); |
| | 726 | const thread: *Thread = .current(el); |
| | 727 | const iou = &thread.io_uring; |
| | 728 | const fiber = thread.currentFiber(); |
| | 729 | if (@atomicLoad(bool, &fiber.canceled, .acquire)) return error.AsyncCancel; |
| 546 | | 730 | |
| 547 | const posix = std.posix; | 731 | const posix = std.posix; |
| 548 | const sub_path_c = try posix.toPosixPath(sub_path); | 732 | const sub_path_c = try posix.toPosixPath(sub_path); |
| ... | @@ -587,22 +771,19 @@ pub fn openFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, fl | ... | @@ -587,22 +771,19 @@ pub fn openFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, fl |
| 587 | @panic("TODO"); | 771 | @panic("TODO"); |
| 588 | } | 772 | } |
| 589 | | 773 | |
| 590 | const thread: *Thread = &el.threads.items[thread_index]; | | |
| 591 | const iou = &thread.io_uring; | | |
| 592 | const sqe = getSqe(iou); | 774 | const sqe = getSqe(iou); |
| 593 | const fiber = thread.currentFiber(); | | |
| 594 | | | |
| 595 | sqe.prep_openat(dir.fd, &sub_path_c, os_flags, 0); | 775 | sqe.prep_openat(dir.fd, &sub_path_c, os_flags, 0); |
| 596 | sqe.user_data = @intFromPtr(fiber); | 776 | sqe.user_data = @intFromPtr(fiber); |
| 597 | | 777 | |
| | 778 | @atomicStore(bool, &fiber.can_cancel, true, .release); |
| 598 | el.yield(null, .nothing); | 779 | el.yield(null, .nothing); |
| | 780 | @atomicStore(bool, &fiber.can_cancel, false, .release); |
| 599 | | 781 | |
| 600 | const result: *i32 = @alignCast(@ptrCast(fiber.resultPointer()[0..@sizeOf(posix.fd_t)])); | 782 | const completion = fiber.resultPointer(Completion); |
| 601 | const rc = result.*; | 783 | switch (errno(completion.result)) { |
| 602 | switch (errno(rc)) { | 784 | .SUCCESS => return .{ .handle = completion.result }, |
| 603 | .SUCCESS => return .{ .handle = rc }, | | |
| 604 | .INTR => @panic("TODO is this reachable?"), | 785 | .INTR => @panic("TODO is this reachable?"), |
| 605 | .CANCELED => @panic("TODO figure out how this error code fits into things"), | 786 | .CANCELED => return error.AsyncCancel, |
| 606 | | 787 | |
| 607 | .FAULT => unreachable, | 788 | .FAULT => unreachable, |
| 608 | .INVAL => return error.BadPathName, | 789 | .INVAL => return error.BadPathName, |
| ... | @@ -631,63 +812,49 @@ pub fn openFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, fl | ... | @@ -631,63 +812,49 @@ pub fn openFile(userdata: ?*anyopaque, dir: std.fs.Dir, sub_path: []const u8, fl |
| 631 | } | 812 | } |
| 632 | } | 813 | } |
| 633 | | 814 | |
| 634 | fn errno(signed: i32) std.posix.E { | | |
| 635 | const int = if (signed > -4096 and signed < 0) -signed else 0; | | |
| 636 | return @enumFromInt(int); | | |
| 637 | } | | |
| 638 | | | |
| 639 | fn getSqe(iou: *IoUring) *std.os.linux.io_uring_sqe { | | |
| 640 | return iou.get_sqe() catch @panic("TODO: handle submission queue full"); | | |
| 641 | } | | |
| 642 | | | |
| 643 | pub fn closeFile(userdata: ?*anyopaque, file: std.fs.File) void { | 815 | pub fn closeFile(userdata: ?*anyopaque, file: std.fs.File) void { |
| 644 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | 816 | const el: *EventLoop = @alignCast(@ptrCast(userdata)); |
| 645 | | 817 | const thread: *Thread = .current(el); |
| 646 | const posix = std.posix; | | |
| 647 | | | |
| 648 | const thread: *Thread = &el.threads.items[thread_index]; | | |
| 649 | const iou = &thread.io_uring; | 818 | const iou = &thread.io_uring; |
| 650 | const sqe = getSqe(iou); | | |
| 651 | const fiber = thread.currentFiber(); | 819 | const fiber = thread.currentFiber(); |
| 652 | | 820 | |
| | 821 | const sqe = getSqe(iou); |
| 653 | sqe.prep_close(file.handle); | 822 | sqe.prep_close(file.handle); |
| 654 | sqe.user_data = @intFromPtr(fiber); | 823 | sqe.user_data = @intFromPtr(fiber); |
| 655 | | 824 | |
| 656 | el.yield(null, .nothing); | 825 | el.yield(null, .nothing); |
| 657 | | 826 | |
| 658 | const result: *i32 = @alignCast(@ptrCast(fiber.resultPointer()[0..@sizeOf(posix.fd_t)])); | 827 | const completion = fiber.resultPointer(Completion); |
| 659 | const rc = result.*; | 828 | switch (errno(completion.result)) { |
| 660 | switch (errno(rc)) { | | |
| 661 | .SUCCESS => return, | 829 | .SUCCESS => return, |
| 662 | .INTR => @panic("TODO is this reachable?"), | 830 | .INTR => @panic("TODO is this reachable?"), |
| 663 | .CANCELED => @panic("TODO figure out how this error code fits into things"), | 831 | .CANCELED => return, |
| 664 | | 832 | |
| 665 | .BADF => unreachable, // Always a race condition. | 833 | .BADF => unreachable, // Always a race condition. |
| 666 | else => return, | 834 | else => return, |
| 667 | } | 835 | } |
| 668 | } | 836 | } |
| 669 | | 837 | |
| 670 | pub fn read(userdata: ?*anyopaque, file: std.fs.File, buffer: []u8) std.fs.File.ReadError!usize { | 838 | pub fn read(userdata: ?*anyopaque, file: std.fs.File, buffer: []u8) Io.FileReadError!usize { |
| 671 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | 839 | const el: *EventLoop = @alignCast(@ptrCast(userdata)); |
| 672 | | 840 | const thread: *Thread = .current(el); |
| 673 | const posix = std.posix; | | |
| 674 | | | |
| 675 | const thread: *Thread = &el.threads.items[thread_index]; | | |
| 676 | const iou = &thread.io_uring; | 841 | const iou = &thread.io_uring; |
| 677 | const sqe = getSqe(iou); | | |
| 678 | const fiber = thread.currentFiber(); | 842 | const fiber = thread.currentFiber(); |
| | 843 | if (@atomicLoad(bool, &fiber.canceled, .acquire)) return error.AsyncCancel; |
| 679 | | 844 | |
| | 845 | const sqe = getSqe(iou); |
| 680 | sqe.prep_read(file.handle, buffer, std.math.maxInt(u64)); | 846 | sqe.prep_read(file.handle, buffer, std.math.maxInt(u64)); |
| 681 | sqe.user_data = @intFromPtr(fiber); | 847 | sqe.user_data = @intFromPtr(fiber); |
| 682 | | 848 | |
| | 849 | @atomicStore(bool, &fiber.can_cancel, true, .release); |
| 683 | el.yield(null, .nothing); | 850 | el.yield(null, .nothing); |
| | 851 | @atomicStore(bool, &fiber.can_cancel, false, .release); |
| 684 | | 852 | |
| 685 | const result: *i32 = @alignCast(@ptrCast(fiber.resultPointer()[0..@sizeOf(posix.fd_t)])); | 853 | const completion = fiber.resultPointer(Completion); |
| 686 | const rc = result.*; | 854 | switch (errno(completion.result)) { |
| 687 | switch (errno(rc)) { | 855 | .SUCCESS => return @as(u32, @bitCast(completion.result)), |
| 688 | .SUCCESS => return @as(u32, @bitCast(rc)), | | |
| 689 | .INTR => @panic("TODO is this reachable?"), | 856 | .INTR => @panic("TODO is this reachable?"), |
| 690 | .CANCELED => @panic("TODO figure out how this error code fits into things"), | 857 | .CANCELED => return error.AsyncCancel, |
| 691 | | 858 | |
| 692 | .INVAL => unreachable, | 859 | .INVAL => unreachable, |
| 693 | .FAULT => unreachable, | 860 | .FAULT => unreachable, |
| ... | @@ -701,31 +868,31 @@ pub fn read(userdata: ?*anyopaque, file: std.fs.File, buffer: []u8) std.fs.File. | ... | @@ -701,31 +868,31 @@ pub fn read(userdata: ?*anyopaque, file: std.fs.File, buffer: []u8) std.fs.File. |
| 701 | .NOTCONN => return error.SocketNotConnected, | 868 | .NOTCONN => return error.SocketNotConnected, |
| 702 | .CONNRESET => return error.ConnectionResetByPeer, | 869 | .CONNRESET => return error.ConnectionResetByPeer, |
| 703 | .TIMEDOUT => return error.ConnectionTimedOut, | 870 | .TIMEDOUT => return error.ConnectionTimedOut, |
| 704 | else => |err| return posix.unexpectedErrno(err), | 871 | else => |err| return std.posix.unexpectedErrno(err), |
| 705 | } | 872 | } |
| 706 | } | 873 | } |
| 707 | | 874 | |
| 708 | pub fn write(userdata: ?*anyopaque, file: std.fs.File, buffer: []const u8) std.fs.File.WriteError!usize { | 875 | pub fn write(userdata: ?*anyopaque, file: std.fs.File, buffer: []const u8) Io.FileWriteError!usize { |
| 709 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | 876 | const el: *EventLoop = @alignCast(@ptrCast(userdata)); |
| 710 | | | |
| 711 | const posix = std.posix; | | |
| 712 | | 877 | |
| 713 | const thread: *Thread = &el.threads.items[thread_index]; | 878 | const thread: *Thread = .current(el); |
| 714 | const iou = &thread.io_uring; | 879 | const iou = &thread.io_uring; |
| 715 | const sqe = getSqe(iou); | | |
| 716 | const fiber = thread.currentFiber(); | 880 | const fiber = thread.currentFiber(); |
| | 881 | if (@atomicLoad(bool, &fiber.canceled, .acquire)) return error.AsyncCancel; |
| 717 | | 882 | |
| | 883 | const sqe = getSqe(iou); |
| 718 | sqe.prep_write(file.handle, buffer, std.math.maxInt(u64)); | 884 | sqe.prep_write(file.handle, buffer, std.math.maxInt(u64)); |
| 719 | sqe.user_data = @intFromPtr(fiber); | 885 | sqe.user_data = @intFromPtr(fiber); |
| 720 | | 886 | |
| | 887 | @atomicStore(bool, &fiber.can_cancel, true, .release); |
| 721 | el.yield(null, .nothing); | 888 | el.yield(null, .nothing); |
| | 889 | @atomicStore(bool, &fiber.can_cancel, false, .release); |
| 722 | | 890 | |
| 723 | const result: *i32 = @alignCast(@ptrCast(fiber.resultPointer()[0..@sizeOf(posix.fd_t)])); | 891 | const completion = fiber.resultPointer(Completion); |
| 724 | const rc = result.*; | 892 | switch (errno(completion.result)) { |
| 725 | switch (errno(rc)) { | 893 | .SUCCESS => return @as(u32, @bitCast(completion.result)), |
| 726 | .SUCCESS => return @as(u32, @bitCast(rc)), | | |
| 727 | .INTR => @panic("TODO is this reachable?"), | 894 | .INTR => @panic("TODO is this reachable?"), |
| 728 | .CANCELED => @panic("TODO figure out how this error code fits into things"), | 895 | .CANCELED => return error.AsyncCancel, |
| 729 | | 896 | |
| 730 | .INVAL => return error.InvalidArgument, | 897 | .INVAL => return error.InvalidArgument, |
| 731 | .FAULT => unreachable, | 898 | .FAULT => unreachable, |
| ... | @@ -744,6 +911,15 @@ pub fn write(userdata: ?*anyopaque, file: std.fs.File, buffer: []const u8) std.f | ... | @@ -744,6 +911,15 @@ pub fn write(userdata: ?*anyopaque, file: std.fs.File, buffer: []const u8) std.f |
| 744 | .BUSY => return error.DeviceBusy, | 911 | .BUSY => return error.DeviceBusy, |
| 745 | .NXIO => return error.NoDevice, | 912 | .NXIO => return error.NoDevice, |
| 746 | .MSGSIZE => return error.MessageTooBig, | 913 | .MSGSIZE => return error.MessageTooBig, |
| 747 | else => |err| return posix.unexpectedErrno(err), | 914 | else => |err| return std.posix.unexpectedErrno(err), |
| 748 | } | 915 | } |
| 749 | } | 916 | } |
| | 917 | |
| | 918 | fn errno(signed: i32) std.posix.E { |
| | 919 | const int = if (signed > -4096 and signed < 0) -signed else 0; |
| | 920 | return @enumFromInt(int); |
| | 921 | } |
| | 922 | |
| | 923 | fn getSqe(iou: *IoUring) *std.os.linux.io_uring_sqe { |
| | 924 | return iou.get_sqe() catch @panic("TODO: handle submission queue full"); |
| | 925 | } |