| author | |
| committer | |
| log | 1e81c3a9259873fac197ed5dc6e2133f0a097243 |
| tree | aff7722e47948044694d90a752caa1c3654e689c |
| parent | 81b1bfbfbbf4f71bc79191ca36ac62c00c8ac92c |
`std.Io.Evented` is introduced to select an appropriate Io
implementation depending on OS3 files changed, 1622 insertions(+), 1619 deletions(-)
lib/std/Io.zig+4-1| ... | @@ -557,7 +557,10 @@ test { | ... | @@ -557,7 +557,10 @@ test { |
| 557 | 557 | ||
| 558 | const Io = @This(); | 558 | const Io = @This(); |
| 559 | 559 | ||
| 560 | pub const EventLoop = @import("Io/EventLoop.zig"); | 560 | pub const Evented = switch (builtin.os.tag) { |
| 561 | .linux => @import("Io/IoUring.zig"), | ||
| 562 | else => void, | ||
| 563 | }; | ||
| 561 | pub const Threaded = @import("Io/Threaded.zig"); | 564 | pub const Threaded = @import("Io/Threaded.zig"); |
| 562 | pub const net = @import("Io/net.zig"); | 565 | pub const net = @import("Io/net.zig"); |
| 563 | 566 |
lib/std/Io/EventLoop.zig deleted-1618| ... | @@ -1,1618 +0,0 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const assert = std.debug.assert; | ||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | const Io = std.Io; | ||
| 6 | const EventLoop = @This(); | ||
| 7 | const Alignment = std.mem.Alignment; | ||
| 8 | const IoUring = std.os.linux.IoUring; | ||
| 9 | |||
| 10 | /// Must be a thread-safe allocator. | ||
| 11 | gpa: Allocator, | ||
| 12 | main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)), | ||
| 13 | threads: Thread.List, | ||
| 14 | detached: struct { | ||
| 15 | mutex: std.Io.Mutex, | ||
| 16 | list: std.DoublyLinkedList, | ||
| 17 | }, | ||
| 18 | |||
| 19 | /// Empirically saw >128KB being used by the self-hosted backend to panic. | ||
| 20 | const idle_stack_size = 256 * 1024; | ||
| 21 | |||
| 22 | const max_idle_search = 4; | ||
| 23 | const max_steal_ready_search = 4; | ||
| 24 | |||
| 25 | const io_uring_entries = 64; | ||
| 26 | |||
| 27 | const Thread = struct { | ||
| 28 | thread: std.Thread, | ||
| 29 | idle_context: Context, | ||
| 30 | current_context: *Context, | ||
| 31 | ready_queue: ?*Fiber, | ||
| 32 | io_uring: IoUring, | ||
| 33 | idle_search_index: u32, | ||
| 34 | steal_ready_search_index: u32, | ||
| 35 | |||
| 36 | const canceling: ?*Thread = @ptrFromInt(@alignOf(Thread)); | ||
| 37 | |||
| 38 | threadlocal var self: *Thread = undefined; | ||
| 39 | |||
| 40 | fn current() *Thread { | ||
| 41 | return self; | ||
| 42 | } | ||
| 43 | |||
| 44 | fn currentFiber(thread: *Thread) *Fiber { | ||
| 45 | return @fieldParentPtr("context", thread.current_context); | ||
| 46 | } | ||
| 47 | |||
| 48 | const List = struct { | ||
| 49 | allocated: []Thread, | ||
| 50 | reserved: u32, | ||
| 51 | active: u32, | ||
| 52 | }; | ||
| 53 | }; | ||
| 54 | |||
| 55 | const Fiber = struct { | ||
| 56 | required_align: void align(4), | ||
| 57 | context: Context, | ||
| 58 | awaiter: ?*Fiber, | ||
| 59 | queue_next: ?*Fiber, | ||
| 60 | cancel_thread: ?*Thread, | ||
| 61 | awaiting_completions: std.StaticBitSet(3), | ||
| 62 | |||
| 63 | const finished: ?*Fiber = @ptrFromInt(@alignOf(Thread)); | ||
| 64 | |||
| 65 | const max_result_align: Alignment = .@"16"; | ||
| 66 | const max_result_size = max_result_align.forward(64); | ||
| 67 | /// This includes any stack realignments that need to happen, and also the | ||
| 68 | /// initial frame return address slot and argument frame, depending on target. | ||
| 69 | const min_stack_size = 4 * 1024 * 1024; | ||
| 70 | const max_context_align: Alignment = .@"16"; | ||
| 71 | const max_context_size = max_context_align.forward(1024); | ||
| 72 | const max_closure_size: usize = @max(@sizeOf(AsyncClosure), @sizeOf(DetachedClosure)); | ||
| 73 | const max_closure_align: Alignment = .max(.of(AsyncClosure), .of(DetachedClosure)); | ||
| 74 | const allocation_size = std.mem.alignForward( | ||
| 75 | usize, | ||
| 76 | max_closure_align.max(max_context_align).forward( | ||
| 77 | max_result_align.forward(@sizeOf(Fiber)) + max_result_size + min_stack_size, | ||
| 78 | ) + max_closure_size + max_context_size, | ||
| 79 | std.heap.page_size_max, | ||
| 80 | ); | ||
| 81 | |||
| 82 | fn allocate(el: *EventLoop) error{OutOfMemory}!*Fiber { | ||
| 83 | return @ptrCast(try el.gpa.alignedAlloc(u8, .of(Fiber), allocation_size)); | ||
| 84 | } | ||
| 85 | |||
| 86 | fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 { | ||
| 87 | return @as([*]align(@alignOf(Fiber)) u8, @ptrCast(f))[0..allocation_size]; | ||
| 88 | } | ||
| 89 | |||
| 90 | fn allocatedEnd(f: *Fiber) [*]u8 { | ||
| 91 | const allocated_slice = f.allocatedSlice(); | ||
| 92 | return allocated_slice[allocated_slice.len..].ptr; | ||
| 93 | } | ||
| 94 | |||
| 95 | fn resultPointer(f: *Fiber, comptime Result: type) *Result { | ||
| 96 | return @ptrCast(@alignCast(f.resultBytes(.of(Result)))); | ||
| 97 | } | ||
| 98 | |||
| 99 | fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 { | ||
| 100 | return @ptrFromInt(alignment.forward(@intFromPtr(f) + @sizeOf(Fiber))); | ||
| 101 | } | ||
| 102 | |||
| 103 | fn enterCancelRegion(fiber: *Fiber, thread: *Thread) error{Canceled}!void { | ||
| 104 | if (@cmpxchgStrong( | ||
| 105 | ?*Thread, | ||
| 106 | &fiber.cancel_thread, | ||
| 107 | null, | ||
| 108 | thread, | ||
| 109 | .acq_rel, | ||
| 110 | .acquire, | ||
| 111 | )) |cancel_thread| { | ||
| 112 | assert(cancel_thread == Thread.canceling); | ||
| 113 | return error.Canceled; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | fn exitCancelRegion(fiber: *Fiber, thread: *Thread) void { | ||
| 118 | if (@cmpxchgStrong( | ||
| 119 | ?*Thread, | ||
| 120 | &fiber.cancel_thread, | ||
| 121 | thread, | ||
| 122 | null, | ||
| 123 | .acq_rel, | ||
| 124 | .acquire, | ||
| 125 | )) |cancel_thread| assert(cancel_thread == Thread.canceling); | ||
| 126 | } | ||
| 127 | |||
| 128 | const Queue = struct { head: *Fiber, tail: *Fiber }; | ||
| 129 | }; | ||
| 130 | |||
| 131 | fn recycle(el: *EventLoop, fiber: *Fiber) void { | ||
| 132 | std.log.debug("recyling {*}", .{fiber}); | ||
| 133 | assert(fiber.queue_next == null); | ||
| 134 | el.gpa.free(fiber.allocatedSlice()); | ||
| 135 | } | ||
| 136 | |||
| 137 | pub fn io(el: *EventLoop) Io { | ||
| 138 | return .{ | ||
| 139 | .userdata = el, | ||
| 140 | .vtable = &.{ | ||
| 141 | .async = async, | ||
| 142 | .concurrent = concurrent, | ||
| 143 | .await = await, | ||
| 144 | .asyncDetached = asyncDetached, | ||
| 145 | .select = select, | ||
| 146 | .cancel = cancel, | ||
| 147 | .cancelRequested = cancelRequested, | ||
| 148 | |||
| 149 | .mutexLock = mutexLock, | ||
| 150 | .mutexUnlock = mutexUnlock, | ||
| 151 | |||
| 152 | .conditionWait = conditionWait, | ||
| 153 | .conditionWake = conditionWake, | ||
| 154 | |||
| 155 | .createFile = createFile, | ||
| 156 | .fileOpen = fileOpen, | ||
| 157 | .fileClose = fileClose, | ||
| 158 | .pread = pread, | ||
| 159 | .pwrite = pwrite, | ||
| 160 | |||
| 161 | .now = now, | ||
| 162 | .sleep = sleep, | ||
| 163 | }, | ||
| 164 | }; | ||
| 165 | } | ||
| 166 | |||
| 167 | pub fn init(el: *EventLoop, gpa: Allocator) !void { | ||
| 168 | const threads_size = @max(std.Thread.getCpuCount() catch 1, 1) * @sizeOf(Thread); | ||
| 169 | const idle_stack_end_offset = std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max); | ||
| 170 | const allocated_slice = try gpa.alignedAlloc(u8, .of(Thread), idle_stack_end_offset); | ||
| 171 | errdefer gpa.free(allocated_slice); | ||
| 172 | el.* = .{ | ||
| 173 | .gpa = gpa, | ||
| 174 | .main_fiber_buffer = undefined, | ||
| 175 | .threads = .{ | ||
| 176 | .allocated = @ptrCast(allocated_slice[0..threads_size]), | ||
| 177 | .reserved = 1, | ||
| 178 | .active = 1, | ||
| 179 | }, | ||
| 180 | .detached = .{ | ||
| 181 | .mutex = .init, | ||
| 182 | .list = .{}, | ||
| 183 | }, | ||
| 184 | }; | ||
| 185 | const main_fiber: *Fiber = @ptrCast(&el.main_fiber_buffer); | ||
| 186 | main_fiber.* = .{ | ||
| 187 | .required_align = {}, | ||
| 188 | .context = undefined, | ||
| 189 | .awaiter = null, | ||
| 190 | .queue_next = null, | ||
| 191 | .cancel_thread = null, | ||
| 192 | .awaiting_completions = .initEmpty(), | ||
| 193 | }; | ||
| 194 | const main_thread = &el.threads.allocated[0]; | ||
| 195 | Thread.self = main_thread; | ||
| 196 | const idle_stack_end: [*]align(16) usize = @ptrCast(@alignCast(allocated_slice[idle_stack_end_offset..].ptr)); | ||
| 197 | (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)}; | ||
| 198 | main_thread.* = .{ | ||
| 199 | .thread = undefined, | ||
| 200 | .idle_context = switch (builtin.cpu.arch) { | ||
| 201 | .aarch64 => .{ | ||
| 202 | .sp = @intFromPtr(idle_stack_end), | ||
| 203 | .fp = 0, | ||
| 204 | .pc = @intFromPtr(&mainIdleEntry), | ||
| 205 | }, | ||
| 206 | .x86_64 => .{ | ||
| 207 | .rsp = @intFromPtr(idle_stack_end - 1), | ||
| 208 | .rbp = 0, | ||
| 209 | .rip = @intFromPtr(&mainIdleEntry), | ||
| 210 | }, | ||
| 211 | else => @compileError("unimplemented architecture"), | ||
| 212 | }, | ||
| 213 | .current_context = &main_fiber.context, | ||
| 214 | .ready_queue = null, | ||
| 215 | .io_uring = try IoUring.init(io_uring_entries, 0), | ||
| 216 | .idle_search_index = 1, | ||
| 217 | .steal_ready_search_index = 1, | ||
| 218 | }; | ||
| 219 | errdefer main_thread.io_uring.deinit(); | ||
| 220 | std.log.debug("created main idle {*}", .{&main_thread.idle_context}); | ||
| 221 | std.log.debug("created main {*}", .{main_fiber}); | ||
| 222 | } | ||
| 223 | |||
| 224 | pub fn deinit(el: *EventLoop) void { | ||
| 225 | while (true) cancel(el, detached_future: { | ||
| 226 | el.detached.mutex.lock(el.io()) catch |err| switch (err) { | ||
| 227 | error.Canceled => unreachable, // main fiber cannot be canceled | ||
| 228 | }; | ||
| 229 | defer el.detached.mutex.unlock(el.io()); | ||
| 230 | const detached: *DetachedClosure = @fieldParentPtr( | ||
| 231 | "detached_queue_node", | ||
| 232 | el.detached.list.pop() orelse break, | ||
| 233 | ); | ||
| 234 | // notify the detached fiber that it is no longer allowed to recycle itself | ||
| 235 | detached.detached_queue_node = .{ | ||
| 236 | .prev = &detached.detached_queue_node, | ||
| 237 | .next = &detached.detached_queue_node, | ||
| 238 | }; | ||
| 239 | break :detached_future @ptrCast(detached.fiber); | ||
| 240 | }, &.{}, .@"1"); | ||
| 241 | const active_threads = @atomicLoad(u32, &el.threads.active, .acquire); | ||
| 242 | for (el.threads.allocated[0..active_threads]) |*thread| { | ||
| 243 | const ready_fiber = @atomicLoad(?*Fiber, &thread.ready_queue, .monotonic); | ||
| 244 | assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async | ||
| 245 | } | ||
| 246 | el.yield(null, .exit); | ||
| 247 | const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(el.threads.allocated.ptr)); | ||
| 248 | const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max); | ||
| 249 | for (el.threads.allocated[1..active_threads]) |*thread| thread.thread.join(); | ||
| 250 | el.gpa.free(allocated_ptr[0..idle_stack_end_offset]); | ||
| 251 | el.* = undefined; | ||
| 252 | } | ||
| 253 | |||
| 254 | fn findReadyFiber(el: *EventLoop, thread: *Thread) ?*Fiber { | ||
| 255 | if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| { | ||
| 256 | @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release); | ||
| 257 | ready_fiber.queue_next = null; | ||
| 258 | return ready_fiber; | ||
| 259 | } | ||
| 260 | const active_threads = @atomicLoad(u32, &el.threads.active, .acquire); | ||
| 261 | for (0..@min(max_steal_ready_search, active_threads)) |_| { | ||
| 262 | defer thread.steal_ready_search_index += 1; | ||
| 263 | if (thread.steal_ready_search_index == active_threads) thread.steal_ready_search_index = 0; | ||
| 264 | const steal_ready_search_thread = &el.threads.allocated[0..active_threads][thread.steal_ready_search_index]; | ||
| 265 | if (steal_ready_search_thread == thread) continue; | ||
| 266 | const ready_fiber = @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .acquire) orelse continue; | ||
| 267 | if (ready_fiber == Fiber.finished) continue; | ||
| 268 | if (@cmpxchgWeak( | ||
| 269 | ?*Fiber, | ||
| 270 | &steal_ready_search_thread.ready_queue, | ||
| 271 | ready_fiber, | ||
| 272 | null, | ||
| 273 | .acquire, | ||
| 274 | .monotonic, | ||
| 275 | )) |_| continue; | ||
| 276 | @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release); | ||
| 277 | ready_fiber.queue_next = null; | ||
| 278 | return ready_fiber; | ||
| 279 | } | ||
| 280 | // couldn't find anything to do, so we are now open for business | ||
| 281 | @atomicStore(?*Fiber, &thread.ready_queue, null, .monotonic); | ||
| 282 | return null; | ||
| 283 | } | ||
| 284 | |||
| 285 | fn yield(el: *EventLoop, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void { | ||
| 286 | const thread: *Thread = .current(); | ||
| 287 | const ready_context = if (maybe_ready_fiber orelse el.findReadyFiber(thread)) |ready_fiber| | ||
| 288 | &ready_fiber.context | ||
| 289 | else | ||
| 290 | &thread.idle_context; | ||
| 291 | const message: SwitchMessage = .{ | ||
| 292 | .contexts = .{ | ||
| 293 | .prev = thread.current_context, | ||
| 294 | .ready = ready_context, | ||
| 295 | }, | ||
| 296 | .pending_task = pending_task, | ||
| 297 | }; | ||
| 298 | std.log.debug("switching from {*} to {*}", .{ message.contexts.prev, message.contexts.ready }); | ||
| 299 | contextSwitch(&message).handle(el); | ||
| 300 | } | ||
| 301 | |||
| 302 | fn schedule(el: *EventLoop, thread: *Thread, ready_queue: Fiber.Queue) void { | ||
| 303 | { | ||
| 304 | var fiber = ready_queue.head; | ||
| 305 | while (true) { | ||
| 306 | std.log.debug("scheduling {*}", .{fiber}); | ||
| 307 | fiber = fiber.queue_next orelse break; | ||
| 308 | } | ||
| 309 | assert(fiber == ready_queue.tail); | ||
| 310 | } | ||
| 311 | // shared fields of previous `Thread` must be initialized before later ones are marked as active | ||
| 312 | const new_thread_index = @atomicLoad(u32, &el.threads.active, .acquire); | ||
| 313 | for (0..@min(max_idle_search, new_thread_index)) |_| { | ||
| 314 | defer thread.idle_search_index += 1; | ||
| 315 | if (thread.idle_search_index == new_thread_index) thread.idle_search_index = 0; | ||
| 316 | const idle_search_thread = &el.threads.allocated[0..new_thread_index][thread.idle_search_index]; | ||
| 317 | if (idle_search_thread == thread) continue; | ||
| 318 | if (@cmpxchgWeak( | ||
| 319 | ?*Fiber, | ||
| 320 | &idle_search_thread.ready_queue, | ||
| 321 | null, | ||
| 322 | ready_queue.head, | ||
| 323 | .release, | ||
| 324 | .monotonic, | ||
| 325 | )) |_| continue; | ||
| 326 | getSqe(&thread.io_uring).* = .{ | ||
| 327 | .opcode = .MSG_RING, | ||
| 328 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 329 | .ioprio = 0, | ||
| 330 | .fd = idle_search_thread.io_uring.fd, | ||
| 331 | .off = @intFromEnum(Completion.UserData.wakeup), | ||
| 332 | .addr = 0, | ||
| 333 | .len = 0, | ||
| 334 | .rw_flags = 0, | ||
| 335 | .user_data = @intFromEnum(Completion.UserData.wakeup), | ||
| 336 | .buf_index = 0, | ||
| 337 | .personality = 0, | ||
| 338 | .splice_fd_in = 0, | ||
| 339 | .addr3 = 0, | ||
| 340 | .resv = 0, | ||
| 341 | }; | ||
| 342 | return; | ||
| 343 | } | ||
| 344 | spawn_thread: { | ||
| 345 | // previous failed reservations must have completed before retrying | ||
| 346 | if (new_thread_index == el.threads.allocated.len or @cmpxchgWeak( | ||
| 347 | u32, | ||
| 348 | &el.threads.reserved, | ||
| 349 | new_thread_index, | ||
| 350 | new_thread_index + 1, | ||
| 351 | .acquire, | ||
| 352 | .monotonic, | ||
| 353 | ) != null) break :spawn_thread; | ||
| 354 | const new_thread = &el.threads.allocated[new_thread_index]; | ||
| 355 | const next_thread_index = new_thread_index + 1; | ||
| 356 | new_thread.* = .{ | ||
| 357 | .thread = undefined, | ||
| 358 | .idle_context = undefined, | ||
| 359 | .current_context = &new_thread.idle_context, | ||
| 360 | .ready_queue = ready_queue.head, | ||
| 361 | .io_uring = IoUring.init(io_uring_entries, 0) catch |err| { | ||
| 362 | @atomicStore(u32, &el.threads.reserved, new_thread_index, .release); | ||
| 363 | // no more access to `thread` after giving up reservation | ||
| 364 | std.log.warn("unable to create worker thread due to io_uring init failure: {s}", .{@errorName(err)}); | ||
| 365 | break :spawn_thread; | ||
| 366 | }, | ||
| 367 | .idle_search_index = 0, | ||
| 368 | .steal_ready_search_index = 0, | ||
| 369 | }; | ||
| 370 | new_thread.thread = std.Thread.spawn(.{ | ||
| 371 | .stack_size = idle_stack_size, | ||
| 372 | .allocator = el.gpa, | ||
| 373 | }, threadEntry, .{ el, new_thread_index }) catch |err| { | ||
| 374 | new_thread.io_uring.deinit(); | ||
| 375 | @atomicStore(u32, &el.threads.reserved, new_thread_index, .release); | ||
| 376 | // no more access to `thread` after giving up reservation | ||
| 377 | std.log.warn("unable to create worker thread due spawn failure: {s}", .{@errorName(err)}); | ||
| 378 | break :spawn_thread; | ||
| 379 | }; | ||
| 380 | // shared fields of `Thread` must be initialized before being marked active | ||
| 381 | @atomicStore(u32, &el.threads.active, next_thread_index, .release); | ||
| 382 | return; | ||
| 383 | } | ||
| 384 | // nobody wanted it, so just queue it on ourselves | ||
| 385 | while (@cmpxchgWeak( | ||
| 386 | ?*Fiber, | ||
| 387 | &thread.ready_queue, | ||
| 388 | ready_queue.tail.queue_next, | ||
| 389 | ready_queue.head, | ||
| 390 | .acq_rel, | ||
| 391 | .acquire, | ||
| 392 | )) |old_head| ready_queue.tail.queue_next = old_head; | ||
| 393 | } | ||
| 394 | |||
| 395 | fn mainIdle(el: *EventLoop, message: *const SwitchMessage) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Context)))) noreturn { | ||
| 396 | message.handle(el); | ||
| 397 | el.idle(&el.threads.allocated[0]); | ||
| 398 | el.yield(@ptrCast(&el.main_fiber_buffer), .nothing); | ||
| 399 | unreachable; // switched to dead fiber | ||
| 400 | } | ||
| 401 | |||
| 402 | fn threadEntry(el: *EventLoop, index: u32) void { | ||
| 403 | const thread: *Thread = &el.threads.allocated[index]; | ||
| 404 | Thread.self = thread; | ||
| 405 | std.log.debug("created thread idle {*}", .{&thread.idle_context}); | ||
| 406 | el.idle(thread); | ||
| 407 | } | ||
| 408 | |||
| 409 | const Completion = struct { | ||
| 410 | const UserData = enum(usize) { | ||
| 411 | unused, | ||
| 412 | wakeup, | ||
| 413 | cleanup, | ||
| 414 | exit, | ||
| 415 | /// *Fiber | ||
| 416 | _, | ||
| 417 | }; | ||
| 418 | result: i32, | ||
| 419 | flags: u32, | ||
| 420 | }; | ||
| 421 | |||
| 422 | fn idle(el: *EventLoop, thread: *Thread) void { | ||
| 423 | var maybe_ready_fiber: ?*Fiber = null; | ||
| 424 | while (true) { | ||
| 425 | while (maybe_ready_fiber orelse el.findReadyFiber(thread)) |ready_fiber| { | ||
| 426 | el.yield(ready_fiber, .nothing); | ||
| 427 | maybe_ready_fiber = null; | ||
| 428 | } | ||
| 429 | _ = thread.io_uring.submit_and_wait(1) catch |err| switch (err) { | ||
| 430 | error.SignalInterrupt => std.log.warn("submit_and_wait failed with SignalInterrupt", .{}), | ||
| 431 | else => |e| @panic(@errorName(e)), | ||
| 432 | }; | ||
| 433 | var cqes_buffer: [io_uring_entries]std.os.linux.io_uring_cqe = undefined; | ||
| 434 | var maybe_ready_queue: ?Fiber.Queue = null; | ||
| 435 | for (cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) { | ||
| 436 | error.SignalInterrupt => cqes_len: { | ||
| 437 | std.log.warn("copy_cqes failed with SignalInterrupt", .{}); | ||
| 438 | break :cqes_len 0; | ||
| 439 | }, | ||
| 440 | else => |e| @panic(@errorName(e)), | ||
| 441 | }]) |cqe| switch (@as(Completion.UserData, @enumFromInt(cqe.user_data))) { | ||
| 442 | .unused => unreachable, // bad submission queued? | ||
| 443 | .wakeup => {}, | ||
| 444 | .cleanup => @panic("failed to notify other threads that we are exiting"), | ||
| 445 | .exit => { | ||
| 446 | assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async | ||
| 447 | return; | ||
| 448 | }, | ||
| 449 | _ => switch (errno(cqe.res)) { | ||
| 450 | .INTR => getSqe(&thread.io_uring).* = .{ | ||
| 451 | .opcode = .ASYNC_CANCEL, | ||
| 452 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 453 | .ioprio = 0, | ||
| 454 | .fd = 0, | ||
| 455 | .off = 0, | ||
| 456 | .addr = cqe.user_data, | ||
| 457 | .len = 0, | ||
| 458 | .rw_flags = 0, | ||
| 459 | .user_data = @intFromEnum(Completion.UserData.wakeup), | ||
| 460 | .buf_index = 0, | ||
| 461 | .personality = 0, | ||
| 462 | .splice_fd_in = 0, | ||
| 463 | .addr3 = 0, | ||
| 464 | .resv = 0, | ||
| 465 | }, | ||
| 466 | else => { | ||
| 467 | const fiber: *Fiber = @ptrFromInt(cqe.user_data); | ||
| 468 | assert(fiber.queue_next == null); | ||
| 469 | fiber.resultPointer(Completion).* = .{ | ||
| 470 | .result = cqe.res, | ||
| 471 | .flags = cqe.flags, | ||
| 472 | }; | ||
| 473 | if (maybe_ready_fiber == null) maybe_ready_fiber = fiber else if (maybe_ready_queue) |*ready_queue| { | ||
| 474 | ready_queue.tail.queue_next = fiber; | ||
| 475 | ready_queue.tail = fiber; | ||
| 476 | } else maybe_ready_queue = .{ .head = fiber, .tail = fiber }; | ||
| 477 | }, | ||
| 478 | }, | ||
| 479 | }; | ||
| 480 | if (maybe_ready_queue) |ready_queue| el.schedule(thread, ready_queue); | ||
| 481 | } | ||
| 482 | } | ||
| 483 | |||
| 484 | const SwitchMessage = struct { | ||
| 485 | contexts: extern struct { | ||
| 486 | prev: *Context, | ||
| 487 | ready: *Context, | ||
| 488 | }, | ||
| 489 | pending_task: PendingTask, | ||
| 490 | |||
| 491 | const PendingTask = union(enum) { | ||
| 492 | nothing, | ||
| 493 | reschedule, | ||
| 494 | recycle, | ||
| 495 | register_awaiter: *?*Fiber, | ||
| 496 | register_select: []const *Io.AnyFuture, | ||
| 497 | mutex_lock: struct { | ||
| 498 | prev_state: Io.Mutex.State, | ||
| 499 | mutex: *Io.Mutex, | ||
| 500 | }, | ||
| 501 | condition_wait: struct { | ||
| 502 | cond: *Io.Condition, | ||
| 503 | mutex: *Io.Mutex, | ||
| 504 | }, | ||
| 505 | exit, | ||
| 506 | }; | ||
| 507 | |||
| 508 | fn handle(message: *const SwitchMessage, el: *EventLoop) void { | ||
| 509 | const thread: *Thread = .current(); | ||
| 510 | thread.current_context = message.contexts.ready; | ||
| 511 | switch (message.pending_task) { | ||
| 512 | .nothing => {}, | ||
| 513 | .reschedule => if (message.contexts.prev != &thread.idle_context) { | ||
| 514 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 515 | assert(prev_fiber.queue_next == null); | ||
| 516 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 517 | }, | ||
| 518 | .recycle => { | ||
| 519 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 520 | assert(prev_fiber.queue_next == null); | ||
| 521 | el.recycle(prev_fiber); | ||
| 522 | }, | ||
| 523 | .register_awaiter => |awaiter| { | ||
| 524 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 525 | assert(prev_fiber.queue_next == null); | ||
| 526 | if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) | ||
| 527 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 528 | }, | ||
| 529 | .register_select => |futures| { | ||
| 530 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 531 | assert(prev_fiber.queue_next == null); | ||
| 532 | for (futures) |any_future| { | ||
| 533 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 534 | if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) { | ||
| 535 | const closure: *AsyncClosure = .fromFiber(future_fiber); | ||
| 536 | if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) { | ||
| 537 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | } | ||
| 541 | }, | ||
| 542 | .mutex_lock => |mutex_lock| { | ||
| 543 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 544 | assert(prev_fiber.queue_next == null); | ||
| 545 | var prev_state = mutex_lock.prev_state; | ||
| 546 | while (switch (prev_state) { | ||
| 547 | else => next_state: { | ||
| 548 | prev_fiber.queue_next = @ptrFromInt(@intFromEnum(prev_state)); | ||
| 549 | break :next_state @cmpxchgWeak( | ||
| 550 | Io.Mutex.State, | ||
| 551 | &mutex_lock.mutex.state, | ||
| 552 | prev_state, | ||
| 553 | @enumFromInt(@intFromPtr(prev_fiber)), | ||
| 554 | .release, | ||
| 555 | .acquire, | ||
| 556 | ); | ||
| 557 | }, | ||
| 558 | .unlocked => @cmpxchgWeak( | ||
| 559 | Io.Mutex.State, | ||
| 560 | &mutex_lock.mutex.state, | ||
| 561 | .unlocked, | ||
| 562 | .locked_once, | ||
| 563 | .acquire, | ||
| 564 | .acquire, | ||
| 565 | ) orelse { | ||
| 566 | prev_fiber.queue_next = null; | ||
| 567 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 568 | return; | ||
| 569 | }, | ||
| 570 | }) |next_state| prev_state = next_state; | ||
| 571 | }, | ||
| 572 | .condition_wait => |condition_wait| { | ||
| 573 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 574 | assert(prev_fiber.queue_next == null); | ||
| 575 | const cond_impl = prev_fiber.resultPointer(ConditionImpl); | ||
| 576 | cond_impl.* = .{ | ||
| 577 | .tail = prev_fiber, | ||
| 578 | .event = .queued, | ||
| 579 | }; | ||
| 580 | if (@cmpxchgStrong( | ||
| 581 | ?*Fiber, | ||
| 582 | @as(*?*Fiber, @ptrCast(&condition_wait.cond.state)), | ||
| 583 | null, | ||
| 584 | prev_fiber, | ||
| 585 | .release, | ||
| 586 | .acquire, | ||
| 587 | )) |waiting_fiber| { | ||
| 588 | const waiting_cond_impl = waiting_fiber.?.resultPointer(ConditionImpl); | ||
| 589 | assert(waiting_cond_impl.tail.queue_next == null); | ||
| 590 | waiting_cond_impl.tail.queue_next = prev_fiber; | ||
| 591 | waiting_cond_impl.tail = prev_fiber; | ||
| 592 | } | ||
| 593 | condition_wait.mutex.unlock(el.io()); | ||
| 594 | }, | ||
| 595 | .exit => for (el.threads.allocated[0..@atomicLoad(u32, &el.threads.active, .acquire)]) |*each_thread| { | ||
| 596 | getSqe(&thread.io_uring).* = .{ | ||
| 597 | .opcode = .MSG_RING, | ||
| 598 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 599 | .ioprio = 0, | ||
| 600 | .fd = each_thread.io_uring.fd, | ||
| 601 | .off = @intFromEnum(Completion.UserData.exit), | ||
| 602 | .addr = 0, | ||
| 603 | .len = 0, | ||
| 604 | .rw_flags = 0, | ||
| 605 | .user_data = @intFromEnum(Completion.UserData.cleanup), | ||
| 606 | .buf_index = 0, | ||
| 607 | .personality = 0, | ||
| 608 | .splice_fd_in = 0, | ||
| 609 | .addr3 = 0, | ||
| 610 | .resv = 0, | ||
| 611 | }; | ||
| 612 | }, | ||
| 613 | } | ||
| 614 | } | ||
| 615 | }; | ||
| 616 | |||
| 617 | const Context = switch (builtin.cpu.arch) { | ||
| 618 | .aarch64 => extern struct { | ||
| 619 | sp: u64, | ||
| 620 | fp: u64, | ||
| 621 | pc: u64, | ||
| 622 | }, | ||
| 623 | .x86_64 => extern struct { | ||
| 624 | rsp: u64, | ||
| 625 | rbp: u64, | ||
| 626 | rip: u64, | ||
| 627 | }, | ||
| 628 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 629 | }; | ||
| 630 | |||
| 631 | inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage { | ||
| 632 | return @fieldParentPtr("contexts", switch (builtin.cpu.arch) { | ||
| 633 | .aarch64 => asm volatile ( | ||
| 634 | \\ ldp x0, x2, [x1] | ||
| 635 | \\ ldr x3, [x2, #16] | ||
| 636 | \\ mov x4, sp | ||
| 637 | \\ stp x4, fp, [x0] | ||
| 638 | \\ adr x5, 0f | ||
| 639 | \\ ldp x4, fp, [x2] | ||
| 640 | \\ str x5, [x0, #16] | ||
| 641 | \\ mov sp, x4 | ||
| 642 | \\ br x3 | ||
| 643 | \\0: | ||
| 644 | : [received_message] "={x1}" (-> *const @FieldType(SwitchMessage, "contexts")), | ||
| 645 | : [message_to_send] "{x1}" (&message.contexts), | ||
| 646 | : .{ | ||
| 647 | .x0 = true, | ||
| 648 | .x1 = true, | ||
| 649 | .x2 = true, | ||
| 650 | .x3 = true, | ||
| 651 | .x4 = true, | ||
| 652 | .x5 = true, | ||
| 653 | .x6 = true, | ||
| 654 | .x7 = true, | ||
| 655 | .x8 = true, | ||
| 656 | .x9 = true, | ||
| 657 | .x10 = true, | ||
| 658 | .x11 = true, | ||
| 659 | .x12 = true, | ||
| 660 | .x13 = true, | ||
| 661 | .x14 = true, | ||
| 662 | .x15 = true, | ||
| 663 | .x16 = true, | ||
| 664 | .x17 = true, | ||
| 665 | .x18 = true, | ||
| 666 | .x19 = true, | ||
| 667 | .x20 = true, | ||
| 668 | .x21 = true, | ||
| 669 | .x22 = true, | ||
| 670 | .x23 = true, | ||
| 671 | .x24 = true, | ||
| 672 | .x25 = true, | ||
| 673 | .x26 = true, | ||
| 674 | .x27 = true, | ||
| 675 | .x28 = true, | ||
| 676 | .x30 = true, | ||
| 677 | .z0 = true, | ||
| 678 | .z1 = true, | ||
| 679 | .z2 = true, | ||
| 680 | .z3 = true, | ||
| 681 | .z4 = true, | ||
| 682 | .z5 = true, | ||
| 683 | .z6 = true, | ||
| 684 | .z7 = true, | ||
| 685 | .z8 = true, | ||
| 686 | .z9 = true, | ||
| 687 | .z10 = true, | ||
| 688 | .z11 = true, | ||
| 689 | .z12 = true, | ||
| 690 | .z13 = true, | ||
| 691 | .z14 = true, | ||
| 692 | .z15 = true, | ||
| 693 | .z16 = true, | ||
| 694 | .z17 = true, | ||
| 695 | .z18 = true, | ||
| 696 | .z19 = true, | ||
| 697 | .z20 = true, | ||
| 698 | .z21 = true, | ||
| 699 | .z22 = true, | ||
| 700 | .z23 = true, | ||
| 701 | .z24 = true, | ||
| 702 | .z25 = true, | ||
| 703 | .z26 = true, | ||
| 704 | .z27 = true, | ||
| 705 | .z28 = true, | ||
| 706 | .z29 = true, | ||
| 707 | .z30 = true, | ||
| 708 | .z31 = true, | ||
| 709 | .p0 = true, | ||
| 710 | .p1 = true, | ||
| 711 | .p2 = true, | ||
| 712 | .p3 = true, | ||
| 713 | .p4 = true, | ||
| 714 | .p5 = true, | ||
| 715 | .p6 = true, | ||
| 716 | .p7 = true, | ||
| 717 | .p8 = true, | ||
| 718 | .p9 = true, | ||
| 719 | .p10 = true, | ||
| 720 | .p11 = true, | ||
| 721 | .p12 = true, | ||
| 722 | .p13 = true, | ||
| 723 | .p14 = true, | ||
| 724 | .p15 = true, | ||
| 725 | .fpcr = true, | ||
| 726 | .fpsr = true, | ||
| 727 | .ffr = true, | ||
| 728 | .memory = true, | ||
| 729 | }), | ||
| 730 | .x86_64 => asm volatile ( | ||
| 731 | \\ movq 0(%%rsi), %%rax | ||
| 732 | \\ movq 8(%%rsi), %%rcx | ||
| 733 | \\ leaq 0f(%%rip), %%rdx | ||
| 734 | \\ movq %%rsp, 0(%%rax) | ||
| 735 | \\ movq %%rbp, 8(%%rax) | ||
| 736 | \\ movq %%rdx, 16(%%rax) | ||
| 737 | \\ movq 0(%%rcx), %%rsp | ||
| 738 | \\ movq 8(%%rcx), %%rbp | ||
| 739 | \\ jmpq *16(%%rcx) | ||
| 740 | \\0: | ||
| 741 | : [received_message] "={rsi}" (-> *const @FieldType(SwitchMessage, "contexts")), | ||
| 742 | : [message_to_send] "{rsi}" (&message.contexts), | ||
| 743 | : .{ | ||
| 744 | .rax = true, | ||
| 745 | .rcx = true, | ||
| 746 | .rdx = true, | ||
| 747 | .rbx = true, | ||
| 748 | .rsi = true, | ||
| 749 | .rdi = true, | ||
| 750 | .r8 = true, | ||
| 751 | .r9 = true, | ||
| 752 | .r10 = true, | ||
| 753 | .r11 = true, | ||
| 754 | .r12 = true, | ||
| 755 | .r13 = true, | ||
| 756 | .r14 = true, | ||
| 757 | .r15 = true, | ||
| 758 | .mm0 = true, | ||
| 759 | .mm1 = true, | ||
| 760 | .mm2 = true, | ||
| 761 | .mm3 = true, | ||
| 762 | .mm4 = true, | ||
| 763 | .mm5 = true, | ||
| 764 | .mm6 = true, | ||
| 765 | .mm7 = true, | ||
| 766 | .zmm0 = true, | ||
| 767 | .zmm1 = true, | ||
| 768 | .zmm2 = true, | ||
| 769 | .zmm3 = true, | ||
| 770 | .zmm4 = true, | ||
| 771 | .zmm5 = true, | ||
| 772 | .zmm6 = true, | ||
| 773 | .zmm7 = true, | ||
| 774 | .zmm8 = true, | ||
| 775 | .zmm9 = true, | ||
| 776 | .zmm10 = true, | ||
| 777 | .zmm11 = true, | ||
| 778 | .zmm12 = true, | ||
| 779 | .zmm13 = true, | ||
| 780 | .zmm14 = true, | ||
| 781 | .zmm15 = true, | ||
| 782 | .zmm16 = true, | ||
| 783 | .zmm17 = true, | ||
| 784 | .zmm18 = true, | ||
| 785 | .zmm19 = true, | ||
| 786 | .zmm20 = true, | ||
| 787 | .zmm21 = true, | ||
| 788 | .zmm22 = true, | ||
| 789 | .zmm23 = true, | ||
| 790 | .zmm24 = true, | ||
| 791 | .zmm25 = true, | ||
| 792 | .zmm26 = true, | ||
| 793 | .zmm27 = true, | ||
| 794 | .zmm28 = true, | ||
| 795 | .zmm29 = true, | ||
| 796 | .zmm30 = true, | ||
| 797 | .zmm31 = true, | ||
| 798 | .fpsr = true, | ||
| 799 | .fpcr = true, | ||
| 800 | .mxcsr = true, | ||
| 801 | .rflags = true, | ||
| 802 | .dirflag = true, | ||
| 803 | .memory = true, | ||
| 804 | }), | ||
| 805 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 806 | }); | ||
| 807 | } | ||
| 808 | |||
| 809 | fn mainIdleEntry() callconv(.naked) void { | ||
| 810 | switch (builtin.cpu.arch) { | ||
| 811 | .x86_64 => asm volatile ( | ||
| 812 | \\ movq (%%rsp), %%rdi | ||
| 813 | \\ jmp %[mainIdle:P] | ||
| 814 | : | ||
| 815 | : [mainIdle] "X" (&mainIdle), | ||
| 816 | ), | ||
| 817 | .aarch64 => asm volatile ( | ||
| 818 | \\ ldr x0, [sp, #-8] | ||
| 819 | \\ b %[mainIdle] | ||
| 820 | : | ||
| 821 | : [mainIdle] "X" (&mainIdle), | ||
| 822 | ), | ||
| 823 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 824 | } | ||
| 825 | } | ||
| 826 | |||
| 827 | fn fiberEntry() callconv(.naked) void { | ||
| 828 | switch (builtin.cpu.arch) { | ||
| 829 | .x86_64 => asm volatile ( | ||
| 830 | \\ leaq 8(%%rsp), %%rdi | ||
| 831 | \\ jmpq *(%%rsp) | ||
| 832 | ), | ||
| 833 | .aarch64 => asm volatile ( | ||
| 834 | \\ mov x0, sp | ||
| 835 | \\ ldr x2, [sp, #-8] | ||
| 836 | \\ br x2 | ||
| 837 | ), | ||
| 838 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 839 | } | ||
| 840 | } | ||
| 841 | |||
| 842 | const AsyncClosure = struct { | ||
| 843 | event_loop: *EventLoop, | ||
| 844 | fiber: *Fiber, | ||
| 845 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, | ||
| 846 | result_align: Alignment, | ||
| 847 | already_awaited: bool, | ||
| 848 | |||
| 849 | fn contextPointer(closure: *AsyncClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 { | ||
| 850 | return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(AsyncClosure)); | ||
| 851 | } | ||
| 852 | |||
| 853 | fn call(closure: *AsyncClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn { | ||
| 854 | message.handle(closure.event_loop); | ||
| 855 | const fiber = closure.fiber; | ||
| 856 | std.log.debug("{*} performing async", .{fiber}); | ||
| 857 | closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align)); | ||
| 858 | const awaiter = @atomicRmw(?*Fiber, &fiber.awaiter, .Xchg, Fiber.finished, .acq_rel); | ||
| 859 | const ready_awaiter = r: { | ||
| 860 | const a = awaiter orelse break :r null; | ||
| 861 | if (@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .acq_rel)) break :r null; | ||
| 862 | break :r a; | ||
| 863 | }; | ||
| 864 | closure.event_loop.yield(ready_awaiter, .nothing); | ||
| 865 | unreachable; // switched to dead fiber | ||
| 866 | } | ||
| 867 | |||
| 868 | fn fromFiber(fiber: *Fiber) *AsyncClosure { | ||
| 869 | return @ptrFromInt(Fiber.max_context_align.max(.of(AsyncClosure)).backward( | ||
| 870 | @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size, | ||
| 871 | ) - @sizeOf(AsyncClosure)); | ||
| 872 | } | ||
| 873 | }; | ||
| 874 | |||
| 875 | fn async( | ||
| 876 | userdata: ?*anyopaque, | ||
| 877 | result: []u8, | ||
| 878 | result_alignment: Alignment, | ||
| 879 | context: []const u8, | ||
| 880 | context_alignment: Alignment, | ||
| 881 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, | ||
| 882 | ) ?*std.Io.AnyFuture { | ||
| 883 | return concurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch { | ||
| 884 | start(context.ptr, result.ptr); | ||
| 885 | return null; | ||
| 886 | }; | ||
| 887 | } | ||
| 888 | |||
| 889 | fn concurrent( | ||
| 890 | userdata: ?*anyopaque, | ||
| 891 | result_len: usize, | ||
| 892 | result_alignment: Alignment, | ||
| 893 | context: []const u8, | ||
| 894 | context_alignment: Alignment, | ||
| 895 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, | ||
| 896 | ) error{OutOfMemory}!*std.Io.AnyFuture { | ||
| 897 | assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO | ||
| 898 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO | ||
| 899 | assert(result_len <= Fiber.max_result_size); // TODO | ||
| 900 | assert(context.len <= Fiber.max_context_size); // TODO | ||
| 901 | |||
| 902 | const event_loop: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 903 | const fiber = try Fiber.allocate(event_loop); | ||
| 904 | std.log.debug("allocated {*}", .{fiber}); | ||
| 905 | |||
| 906 | const closure: *AsyncClosure = .fromFiber(fiber); | ||
| 907 | const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure)); | ||
| 908 | (stack_end - 1)[0..1].* = .{@intFromPtr(&AsyncClosure.call)}; | ||
| 909 | fiber.* = .{ | ||
| 910 | .required_align = {}, | ||
| 911 | .context = switch (builtin.cpu.arch) { | ||
| 912 | .x86_64 => .{ | ||
| 913 | .rsp = @intFromPtr(stack_end - 1), | ||
| 914 | .rbp = 0, | ||
| 915 | .rip = @intFromPtr(&fiberEntry), | ||
| 916 | }, | ||
| 917 | .aarch64 => .{ | ||
| 918 | .sp = @intFromPtr(stack_end), | ||
| 919 | .fp = 0, | ||
| 920 | .pc = @intFromPtr(&fiberEntry), | ||
| 921 | }, | ||
| 922 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 923 | }, | ||
| 924 | .awaiter = null, | ||
| 925 | .queue_next = null, | ||
| 926 | .cancel_thread = null, | ||
| 927 | .awaiting_completions = .initEmpty(), | ||
| 928 | }; | ||
| 929 | closure.* = .{ | ||
| 930 | .event_loop = event_loop, | ||
| 931 | .fiber = fiber, | ||
| 932 | .start = start, | ||
| 933 | .result_align = result_alignment, | ||
| 934 | .already_awaited = false, | ||
| 935 | }; | ||
| 936 | @memcpy(closure.contextPointer(), context); | ||
| 937 | |||
| 938 | event_loop.schedule(.current(), .{ .head = fiber, .tail = fiber }); | ||
| 939 | return @ptrCast(fiber); | ||
| 940 | } | ||
| 941 | |||
| 942 | const DetachedClosure = struct { | ||
| 943 | event_loop: *EventLoop, | ||
| 944 | fiber: *Fiber, | ||
| 945 | start: *const fn (context: *const anyopaque) void, | ||
| 946 | detached_queue_node: std.DoublyLinkedList.Node, | ||
| 947 | |||
| 948 | fn contextPointer(closure: *DetachedClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 { | ||
| 949 | return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(DetachedClosure)); | ||
| 950 | } | ||
| 951 | |||
| 952 | fn call(closure: *DetachedClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(DetachedClosure))) noreturn { | ||
| 953 | message.handle(closure.event_loop); | ||
| 954 | std.log.debug("{*} performing async detached", .{closure.fiber}); | ||
| 955 | closure.start(closure.contextPointer()); | ||
| 956 | const awaiter = @atomicRmw(?*Fiber, &closure.fiber.awaiter, .Xchg, Fiber.finished, .acq_rel); | ||
| 957 | closure.event_loop.yield(awaiter, pending_task: { | ||
| 958 | closure.event_loop.detached.mutex.lock(closure.event_loop.io()) catch |err| switch (err) { | ||
| 959 | error.Canceled => break :pending_task .nothing, | ||
| 960 | }; | ||
| 961 | defer closure.event_loop.detached.mutex.unlock(closure.event_loop.io()); | ||
| 962 | if (closure.detached_queue_node.next == &closure.detached_queue_node) break :pending_task .nothing; | ||
| 963 | closure.event_loop.detached.list.remove(&closure.detached_queue_node); | ||
| 964 | break :pending_task .recycle; | ||
| 965 | }); | ||
| 966 | unreachable; // switched to dead fiber | ||
| 967 | } | ||
| 968 | }; | ||
| 969 | |||
| 970 | fn asyncDetached( | ||
| 971 | userdata: ?*anyopaque, | ||
| 972 | context: []const u8, | ||
| 973 | context_alignment: std.mem.Alignment, | ||
| 974 | start: *const fn (context: *const anyopaque) void, | ||
| 975 | ) void { | ||
| 976 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO | ||
| 977 | assert(context.len <= Fiber.max_context_size); // TODO | ||
| 978 | |||
| 979 | const event_loop: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 980 | const fiber = Fiber.allocate(event_loop) catch { | ||
| 981 | start(context.ptr); | ||
| 982 | return; | ||
| 983 | }; | ||
| 984 | std.log.debug("allocated {*}", .{fiber}); | ||
| 985 | |||
| 986 | const current_thread: *Thread = .current(); | ||
| 987 | const closure: *DetachedClosure = @ptrFromInt(Fiber.max_context_align.max(.of(DetachedClosure)).backward( | ||
| 988 | @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size, | ||
| 989 | ) - @sizeOf(DetachedClosure)); | ||
| 990 | const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure)); | ||
| 991 | (stack_end - 1)[0..1].* = .{@intFromPtr(&DetachedClosure.call)}; | ||
| 992 | fiber.* = .{ | ||
| 993 | .required_align = {}, | ||
| 994 | .context = switch (builtin.cpu.arch) { | ||
| 995 | .x86_64 => .{ | ||
| 996 | .rsp = @intFromPtr(stack_end - 1), | ||
| 997 | .rbp = 0, | ||
| 998 | .rip = @intFromPtr(&fiberEntry), | ||
| 999 | }, | ||
| 1000 | .aarch64 => .{ | ||
| 1001 | .sp = @intFromPtr(stack_end), | ||
| 1002 | .fp = 0, | ||
| 1003 | .pc = @intFromPtr(&fiberEntry), | ||
| 1004 | }, | ||
| 1005 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 1006 | }, | ||
| 1007 | .awaiter = null, | ||
| 1008 | .queue_next = null, | ||
| 1009 | .cancel_thread = null, | ||
| 1010 | .awaiting_completions = .initEmpty(), | ||
| 1011 | }; | ||
| 1012 | closure.* = .{ | ||
| 1013 | .event_loop = event_loop, | ||
| 1014 | .fiber = fiber, | ||
| 1015 | .start = start, | ||
| 1016 | .detached_queue_node = .{}, | ||
| 1017 | }; | ||
| 1018 | { | ||
| 1019 | event_loop.detached.mutex.lock(event_loop.io()) catch |err| switch (err) { | ||
| 1020 | error.Canceled => { | ||
| 1021 | event_loop.recycle(fiber); | ||
| 1022 | start(context.ptr); | ||
| 1023 | return; | ||
| 1024 | }, | ||
| 1025 | }; | ||
| 1026 | defer event_loop.detached.mutex.unlock(event_loop.io()); | ||
| 1027 | event_loop.detached.list.append(&closure.detached_queue_node); | ||
| 1028 | } | ||
| 1029 | @memcpy(closure.contextPointer(), context); | ||
| 1030 | |||
| 1031 | event_loop.schedule(current_thread, .{ .head = fiber, .tail = fiber }); | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | fn await( | ||
| 1035 | userdata: ?*anyopaque, | ||
| 1036 | any_future: *std.Io.AnyFuture, | ||
| 1037 | result: []u8, | ||
| 1038 | result_alignment: Alignment, | ||
| 1039 | ) void { | ||
| 1040 | const event_loop: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1041 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1042 | if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished) | ||
| 1043 | event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter }); | ||
| 1044 | @memcpy(result, future_fiber.resultBytes(result_alignment)); | ||
| 1045 | event_loop.recycle(future_fiber); | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | ||
| 1049 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1050 | |||
| 1051 | // Optimization to avoid the yield below. | ||
| 1052 | for (futures, 0..) |any_future, i| { | ||
| 1053 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1054 | if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) == Fiber.finished) | ||
| 1055 | return i; | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | el.yield(null, .{ .register_select = futures }); | ||
| 1059 | |||
| 1060 | std.log.debug("back from select yield", .{}); | ||
| 1061 | |||
| 1062 | const my_thread: *Thread = .current(); | ||
| 1063 | const my_fiber = my_thread.currentFiber(); | ||
| 1064 | var result: ?usize = null; | ||
| 1065 | |||
| 1066 | for (futures, 0..) |any_future, i| { | ||
| 1067 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1068 | if (@cmpxchgStrong(?*Fiber, &future_fiber.awaiter, my_fiber, null, .seq_cst, .seq_cst)) |awaiter| { | ||
| 1069 | if (awaiter == Fiber.finished) { | ||
| 1070 | if (result == null) result = i; | ||
| 1071 | } else if (awaiter) |a| { | ||
| 1072 | const closure: *AsyncClosure = .fromFiber(a); | ||
| 1073 | closure.already_awaited = false; | ||
| 1074 | } | ||
| 1075 | } else { | ||
| 1076 | const closure: *AsyncClosure = .fromFiber(my_fiber); | ||
| 1077 | closure.already_awaited = false; | ||
| 1078 | } | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | return result.?; | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | fn cancel( | ||
| 1085 | userdata: ?*anyopaque, | ||
| 1086 | any_future: *std.Io.AnyFuture, | ||
| 1087 | result: []u8, | ||
| 1088 | result_alignment: Alignment, | ||
| 1089 | ) void { | ||
| 1090 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1091 | if (@atomicRmw( | ||
| 1092 | ?*Thread, | ||
| 1093 | &future_fiber.cancel_thread, | ||
| 1094 | .Xchg, | ||
| 1095 | Thread.canceling, | ||
| 1096 | .acq_rel, | ||
| 1097 | )) |cancel_thread| if (cancel_thread != Thread.canceling) { | ||
| 1098 | getSqe(&Thread.current().io_uring).* = .{ | ||
| 1099 | .opcode = .MSG_RING, | ||
| 1100 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 1101 | .ioprio = 0, | ||
| 1102 | .fd = cancel_thread.io_uring.fd, | ||
| 1103 | .off = @intFromPtr(future_fiber), | ||
| 1104 | .addr = 0, | ||
| 1105 | .len = @bitCast(-@as(i32, @intFromEnum(std.os.linux.E.INTR))), | ||
| 1106 | .rw_flags = 0, | ||
| 1107 | .user_data = @intFromEnum(Completion.UserData.cleanup), | ||
| 1108 | .buf_index = 0, | ||
| 1109 | .personality = 0, | ||
| 1110 | .splice_fd_in = 0, | ||
| 1111 | .addr3 = 0, | ||
| 1112 | .resv = 0, | ||
| 1113 | }; | ||
| 1114 | }; | ||
| 1115 | await(userdata, any_future, result, result_alignment); | ||
| 1116 | } | ||
| 1117 | |||
| 1118 | fn cancelRequested(userdata: ?*anyopaque) bool { | ||
| 1119 | _ = userdata; | ||
| 1120 | return @atomicLoad(?*Thread, &Thread.current().currentFiber().cancel_thread, .acquire) == Thread.canceling; | ||
| 1121 | } | ||
| 1122 | |||
| 1123 | fn createFile( | ||
| 1124 | userdata: ?*anyopaque, | ||
| 1125 | dir: Io.Dir, | ||
| 1126 | sub_path: []const u8, | ||
| 1127 | flags: Io.File.CreateFlags, | ||
| 1128 | ) Io.File.OpenError!Io.File { | ||
| 1129 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1130 | const thread: *Thread = .current(); | ||
| 1131 | const iou = &thread.io_uring; | ||
| 1132 | const fiber = thread.currentFiber(); | ||
| 1133 | try fiber.enterCancelRegion(thread); | ||
| 1134 | |||
| 1135 | const posix = std.posix; | ||
| 1136 | const sub_path_c = try posix.toPosixPath(sub_path); | ||
| 1137 | |||
| 1138 | var os_flags: posix.O = .{ | ||
| 1139 | .ACCMODE = if (flags.read) .RDWR else .WRONLY, | ||
| 1140 | .CREAT = true, | ||
| 1141 | .TRUNC = flags.truncate, | ||
| 1142 | .EXCL = flags.exclusive, | ||
| 1143 | }; | ||
| 1144 | if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; | ||
| 1145 | if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; | ||
| 1146 | |||
| 1147 | // Use the O locking flags if the os supports them to acquire the lock | ||
| 1148 | // atomically. Note that the NONBLOCK flag is removed after the openat() | ||
| 1149 | // call is successful. | ||
| 1150 | const has_flock_open_flags = @hasField(posix.O, "EXLOCK"); | ||
| 1151 | if (has_flock_open_flags) switch (flags.lock) { | ||
| 1152 | .none => {}, | ||
| 1153 | .shared => { | ||
| 1154 | os_flags.SHLOCK = true; | ||
| 1155 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1156 | }, | ||
| 1157 | .exclusive => { | ||
| 1158 | os_flags.EXLOCK = true; | ||
| 1159 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1160 | }, | ||
| 1161 | }; | ||
| 1162 | const have_flock = @TypeOf(posix.system.flock) != void; | ||
| 1163 | |||
| 1164 | if (have_flock and !has_flock_open_flags and flags.lock != .none) { | ||
| 1165 | @panic("TODO"); | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | if (has_flock_open_flags and flags.lock_nonblocking) { | ||
| 1169 | @panic("TODO"); | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | getSqe(iou).* = .{ | ||
| 1173 | .opcode = .OPENAT, | ||
| 1174 | .flags = 0, | ||
| 1175 | .ioprio = 0, | ||
| 1176 | .fd = dir.handle, | ||
| 1177 | .off = 0, | ||
| 1178 | .addr = @intFromPtr(&sub_path_c), | ||
| 1179 | .len = @intCast(flags.mode), | ||
| 1180 | .rw_flags = @bitCast(os_flags), | ||
| 1181 | .user_data = @intFromPtr(fiber), | ||
| 1182 | .buf_index = 0, | ||
| 1183 | .personality = 0, | ||
| 1184 | .splice_fd_in = 0, | ||
| 1185 | .addr3 = 0, | ||
| 1186 | .resv = 0, | ||
| 1187 | }; | ||
| 1188 | |||
| 1189 | el.yield(null, .nothing); | ||
| 1190 | fiber.exitCancelRegion(thread); | ||
| 1191 | |||
| 1192 | const completion = fiber.resultPointer(Completion); | ||
| 1193 | switch (errno(completion.result)) { | ||
| 1194 | .SUCCESS => return .{ .handle = completion.result }, | ||
| 1195 | .INTR => unreachable, | ||
| 1196 | .CANCELED => return error.Canceled, | ||
| 1197 | |||
| 1198 | .FAULT => unreachable, | ||
| 1199 | .INVAL => return error.BadPathName, | ||
| 1200 | .BADF => unreachable, | ||
| 1201 | .ACCES => return error.AccessDenied, | ||
| 1202 | .FBIG => return error.FileTooBig, | ||
| 1203 | .OVERFLOW => return error.FileTooBig, | ||
| 1204 | .ISDIR => return error.IsDir, | ||
| 1205 | .LOOP => return error.SymLinkLoop, | ||
| 1206 | .MFILE => return error.ProcessFdQuotaExceeded, | ||
| 1207 | .NAMETOOLONG => return error.NameTooLong, | ||
| 1208 | .NFILE => return error.SystemFdQuotaExceeded, | ||
| 1209 | .NODEV => return error.NoDevice, | ||
| 1210 | .NOENT => return error.FileNotFound, | ||
| 1211 | .NOMEM => return error.SystemResources, | ||
| 1212 | .NOSPC => return error.NoSpaceLeft, | ||
| 1213 | .NOTDIR => return error.NotDir, | ||
| 1214 | .PERM => return error.PermissionDenied, | ||
| 1215 | .EXIST => return error.PathAlreadyExists, | ||
| 1216 | .BUSY => return error.DeviceBusy, | ||
| 1217 | .OPNOTSUPP => return error.FileLocksNotSupported, | ||
| 1218 | .AGAIN => return error.WouldBlock, | ||
| 1219 | .TXTBSY => return error.FileBusy, | ||
| 1220 | .NXIO => return error.NoDevice, | ||
| 1221 | else => |err| return posix.unexpectedErrno(err), | ||
| 1222 | } | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | fn fileOpen( | ||
| 1226 | userdata: ?*anyopaque, | ||
| 1227 | dir: Io.Dir, | ||
| 1228 | sub_path: []const u8, | ||
| 1229 | flags: Io.File.OpenFlags, | ||
| 1230 | ) Io.File.OpenError!Io.File { | ||
| 1231 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1232 | const thread: *Thread = .current(); | ||
| 1233 | const iou = &thread.io_uring; | ||
| 1234 | const fiber = thread.currentFiber(); | ||
| 1235 | try fiber.enterCancelRegion(thread); | ||
| 1236 | |||
| 1237 | const posix = std.posix; | ||
| 1238 | const sub_path_c = try posix.toPosixPath(sub_path); | ||
| 1239 | |||
| 1240 | var os_flags: posix.O = .{ | ||
| 1241 | .ACCMODE = switch (flags.mode) { | ||
| 1242 | .read_only => .RDONLY, | ||
| 1243 | .write_only => .WRONLY, | ||
| 1244 | .read_write => .RDWR, | ||
| 1245 | }, | ||
| 1246 | }; | ||
| 1247 | |||
| 1248 | if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; | ||
| 1249 | if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; | ||
| 1250 | if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty; | ||
| 1251 | |||
| 1252 | // Use the O locking flags if the os supports them to acquire the lock | ||
| 1253 | // atomically. | ||
| 1254 | const has_flock_open_flags = @hasField(posix.O, "EXLOCK"); | ||
| 1255 | if (has_flock_open_flags) { | ||
| 1256 | // Note that the NONBLOCK flag is removed after the openat() call | ||
| 1257 | // is successful. | ||
| 1258 | switch (flags.lock) { | ||
| 1259 | .none => {}, | ||
| 1260 | .shared => { | ||
| 1261 | os_flags.SHLOCK = true; | ||
| 1262 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1263 | }, | ||
| 1264 | .exclusive => { | ||
| 1265 | os_flags.EXLOCK = true; | ||
| 1266 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1267 | }, | ||
| 1268 | } | ||
| 1269 | } | ||
| 1270 | const have_flock = @TypeOf(posix.system.flock) != void; | ||
| 1271 | |||
| 1272 | if (have_flock and !has_flock_open_flags and flags.lock != .none) { | ||
| 1273 | @panic("TODO"); | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | if (has_flock_open_flags and flags.lock_nonblocking) { | ||
| 1277 | @panic("TODO"); | ||
| 1278 | } | ||
| 1279 | |||
| 1280 | getSqe(iou).* = .{ | ||
| 1281 | .opcode = .OPENAT, | ||
| 1282 | .flags = 0, | ||
| 1283 | .ioprio = 0, | ||
| 1284 | .fd = dir.handle, | ||
| 1285 | .off = 0, | ||
| 1286 | .addr = @intFromPtr(&sub_path_c), | ||
| 1287 | .len = 0, | ||
| 1288 | .rw_flags = @bitCast(os_flags), | ||
| 1289 | .user_data = @intFromPtr(fiber), | ||
| 1290 | .buf_index = 0, | ||
| 1291 | .personality = 0, | ||
| 1292 | .splice_fd_in = 0, | ||
| 1293 | .addr3 = 0, | ||
| 1294 | .resv = 0, | ||
| 1295 | }; | ||
| 1296 | |||
| 1297 | el.yield(null, .nothing); | ||
| 1298 | fiber.exitCancelRegion(thread); | ||
| 1299 | |||
| 1300 | const completion = fiber.resultPointer(Completion); | ||
| 1301 | switch (errno(completion.result)) { | ||
| 1302 | .SUCCESS => return .{ .handle = completion.result }, | ||
| 1303 | .INTR => unreachable, | ||
| 1304 | .CANCELED => return error.Canceled, | ||
| 1305 | |||
| 1306 | .FAULT => unreachable, | ||
| 1307 | .INVAL => return error.BadPathName, | ||
| 1308 | .BADF => unreachable, | ||
| 1309 | .ACCES => return error.AccessDenied, | ||
| 1310 | .FBIG => return error.FileTooBig, | ||
| 1311 | .OVERFLOW => return error.FileTooBig, | ||
| 1312 | .ISDIR => return error.IsDir, | ||
| 1313 | .LOOP => return error.SymLinkLoop, | ||
| 1314 | .MFILE => return error.ProcessFdQuotaExceeded, | ||
| 1315 | .NAMETOOLONG => return error.NameTooLong, | ||
| 1316 | .NFILE => return error.SystemFdQuotaExceeded, | ||
| 1317 | .NODEV => return error.NoDevice, | ||
| 1318 | .NOENT => return error.FileNotFound, | ||
| 1319 | .NOMEM => return error.SystemResources, | ||
| 1320 | .NOSPC => return error.NoSpaceLeft, | ||
| 1321 | .NOTDIR => return error.NotDir, | ||
| 1322 | .PERM => return error.PermissionDenied, | ||
| 1323 | .EXIST => return error.PathAlreadyExists, | ||
| 1324 | .BUSY => return error.DeviceBusy, | ||
| 1325 | .OPNOTSUPP => return error.FileLocksNotSupported, | ||
| 1326 | .AGAIN => return error.WouldBlock, | ||
| 1327 | .TXTBSY => return error.FileBusy, | ||
| 1328 | .NXIO => return error.NoDevice, | ||
| 1329 | else => |err| return posix.unexpectedErrno(err), | ||
| 1330 | } | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | fn fileClose(userdata: ?*anyopaque, file: Io.File) void { | ||
| 1334 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1335 | const thread: *Thread = .current(); | ||
| 1336 | const iou = &thread.io_uring; | ||
| 1337 | const fiber = thread.currentFiber(); | ||
| 1338 | |||
| 1339 | getSqe(iou).* = .{ | ||
| 1340 | .opcode = .CLOSE, | ||
| 1341 | .flags = 0, | ||
| 1342 | .ioprio = 0, | ||
| 1343 | .fd = file.handle, | ||
| 1344 | .off = 0, | ||
| 1345 | .addr = 0, | ||
| 1346 | .len = 0, | ||
| 1347 | .rw_flags = 0, | ||
| 1348 | .user_data = @intFromPtr(fiber), | ||
| 1349 | .buf_index = 0, | ||
| 1350 | .personality = 0, | ||
| 1351 | .splice_fd_in = 0, | ||
| 1352 | .addr3 = 0, | ||
| 1353 | .resv = 0, | ||
| 1354 | }; | ||
| 1355 | |||
| 1356 | el.yield(null, .nothing); | ||
| 1357 | |||
| 1358 | const completion = fiber.resultPointer(Completion); | ||
| 1359 | switch (errno(completion.result)) { | ||
| 1360 | .SUCCESS => return, | ||
| 1361 | .INTR => unreachable, | ||
| 1362 | .CANCELED => return, | ||
| 1363 | |||
| 1364 | .BADF => unreachable, // Always a race condition. | ||
| 1365 | else => return, | ||
| 1366 | } | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize { | ||
| 1370 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1371 | const thread: *Thread = .current(); | ||
| 1372 | const iou = &thread.io_uring; | ||
| 1373 | const fiber = thread.currentFiber(); | ||
| 1374 | try fiber.enterCancelRegion(thread); | ||
| 1375 | |||
| 1376 | getSqe(iou).* = .{ | ||
| 1377 | .opcode = .READ, | ||
| 1378 | .flags = 0, | ||
| 1379 | .ioprio = 0, | ||
| 1380 | .fd = file.handle, | ||
| 1381 | .off = @bitCast(offset), | ||
| 1382 | .addr = @intFromPtr(buffer.ptr), | ||
| 1383 | .len = @min(buffer.len, 0x7ffff000), | ||
| 1384 | .rw_flags = 0, | ||
| 1385 | .user_data = @intFromPtr(fiber), | ||
| 1386 | .buf_index = 0, | ||
| 1387 | .personality = 0, | ||
| 1388 | .splice_fd_in = 0, | ||
| 1389 | .addr3 = 0, | ||
| 1390 | .resv = 0, | ||
| 1391 | }; | ||
| 1392 | |||
| 1393 | el.yield(null, .nothing); | ||
| 1394 | fiber.exitCancelRegion(thread); | ||
| 1395 | |||
| 1396 | const completion = fiber.resultPointer(Completion); | ||
| 1397 | switch (errno(completion.result)) { | ||
| 1398 | .SUCCESS => return @as(u32, @bitCast(completion.result)), | ||
| 1399 | .INTR => unreachable, | ||
| 1400 | .CANCELED => return error.Canceled, | ||
| 1401 | |||
| 1402 | .INVAL => unreachable, | ||
| 1403 | .FAULT => unreachable, | ||
| 1404 | .NOENT => return error.ProcessNotFound, | ||
| 1405 | .AGAIN => return error.WouldBlock, | ||
| 1406 | .BADF => return error.NotOpenForReading, // Can be a race condition. | ||
| 1407 | .IO => return error.InputOutput, | ||
| 1408 | .ISDIR => return error.IsDir, | ||
| 1409 | .NOBUFS => return error.SystemResources, | ||
| 1410 | .NOMEM => return error.SystemResources, | ||
| 1411 | .NOTCONN => return error.SocketUnconnected, | ||
| 1412 | .CONNRESET => return error.ConnectionResetByPeer, | ||
| 1413 | .TIMEDOUT => return error.Timeout, | ||
| 1414 | .NXIO => return error.Unseekable, | ||
| 1415 | .SPIPE => return error.Unseekable, | ||
| 1416 | .OVERFLOW => return error.Unseekable, | ||
| 1417 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 1418 | } | ||
| 1419 | } | ||
| 1420 | |||
| 1421 | fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize { | ||
| 1422 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1423 | const thread: *Thread = .current(); | ||
| 1424 | const iou = &thread.io_uring; | ||
| 1425 | const fiber = thread.currentFiber(); | ||
| 1426 | try fiber.enterCancelRegion(thread); | ||
| 1427 | |||
| 1428 | getSqe(iou).* = .{ | ||
| 1429 | .opcode = .WRITE, | ||
| 1430 | .flags = 0, | ||
| 1431 | .ioprio = 0, | ||
| 1432 | .fd = file.handle, | ||
| 1433 | .off = @bitCast(offset), | ||
| 1434 | .addr = @intFromPtr(buffer.ptr), | ||
| 1435 | .len = @min(buffer.len, 0x7ffff000), | ||
| 1436 | .rw_flags = 0, | ||
| 1437 | .user_data = @intFromPtr(fiber), | ||
| 1438 | .buf_index = 0, | ||
| 1439 | .personality = 0, | ||
| 1440 | .splice_fd_in = 0, | ||
| 1441 | .addr3 = 0, | ||
| 1442 | .resv = 0, | ||
| 1443 | }; | ||
| 1444 | |||
| 1445 | el.yield(null, .nothing); | ||
| 1446 | fiber.exitCancelRegion(thread); | ||
| 1447 | |||
| 1448 | const completion = fiber.resultPointer(Completion); | ||
| 1449 | switch (errno(completion.result)) { | ||
| 1450 | .SUCCESS => return @as(u32, @bitCast(completion.result)), | ||
| 1451 | .INTR => unreachable, | ||
| 1452 | .CANCELED => return error.Canceled, | ||
| 1453 | |||
| 1454 | .INVAL => return error.InvalidArgument, | ||
| 1455 | .FAULT => unreachable, | ||
| 1456 | .NOENT => return error.ProcessNotFound, | ||
| 1457 | .AGAIN => return error.WouldBlock, | ||
| 1458 | .BADF => return error.NotOpenForWriting, // can be a race condition. | ||
| 1459 | .DESTADDRREQ => unreachable, // `connect` was never called. | ||
| 1460 | .DQUOT => return error.DiskQuota, | ||
| 1461 | .FBIG => return error.FileTooBig, | ||
| 1462 | .IO => return error.InputOutput, | ||
| 1463 | .NOSPC => return error.NoSpaceLeft, | ||
| 1464 | .ACCES => return error.AccessDenied, | ||
| 1465 | .PERM => return error.PermissionDenied, | ||
| 1466 | .PIPE => return error.BrokenPipe, | ||
| 1467 | .NXIO => return error.Unseekable, | ||
| 1468 | .SPIPE => return error.Unseekable, | ||
| 1469 | .OVERFLOW => return error.Unseekable, | ||
| 1470 | .BUSY => return error.DeviceBusy, | ||
| 1471 | .CONNRESET => return error.ConnectionResetByPeer, | ||
| 1472 | .MSGSIZE => return error.MessageTooBig, | ||
| 1473 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 1474 | } | ||
| 1475 | } | ||
| 1476 | |||
| 1477 | fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp { | ||
| 1478 | _ = userdata; | ||
| 1479 | const timespec = try std.posix.clock_gettime(clockid); | ||
| 1480 | return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec); | ||
| 1481 | } | ||
| 1482 | |||
| 1483 | fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void { | ||
| 1484 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1485 | const thread: *Thread = .current(); | ||
| 1486 | const iou = &thread.io_uring; | ||
| 1487 | const fiber = thread.currentFiber(); | ||
| 1488 | try fiber.enterCancelRegion(thread); | ||
| 1489 | |||
| 1490 | const deadline_nanoseconds: i96 = switch (deadline) { | ||
| 1491 | .duration => |duration| duration.nanoseconds, | ||
| 1492 | .timestamp => |timestamp| @intFromEnum(timestamp), | ||
| 1493 | }; | ||
| 1494 | const timespec: std.os.linux.kernel_timespec = .{ | ||
| 1495 | .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)), | ||
| 1496 | .nsec = @intCast(@mod(deadline_nanoseconds, std.time.ns_per_s)), | ||
| 1497 | }; | ||
| 1498 | getSqe(iou).* = .{ | ||
| 1499 | .opcode = .TIMEOUT, | ||
| 1500 | .flags = 0, | ||
| 1501 | .ioprio = 0, | ||
| 1502 | .fd = 0, | ||
| 1503 | .off = 0, | ||
| 1504 | .addr = @intFromPtr(&timespec), | ||
| 1505 | .len = 1, | ||
| 1506 | .rw_flags = @as(u32, switch (deadline) { | ||
| 1507 | .duration => 0, | ||
| 1508 | .timestamp => std.os.linux.IORING_TIMEOUT_ABS, | ||
| 1509 | }) | @as(u32, switch (clockid) { | ||
| 1510 | .REALTIME => std.os.linux.IORING_TIMEOUT_REALTIME, | ||
| 1511 | .MONOTONIC => 0, | ||
| 1512 | .BOOTTIME => std.os.linux.IORING_TIMEOUT_BOOTTIME, | ||
| 1513 | else => return error.UnsupportedClock, | ||
| 1514 | }), | ||
| 1515 | .user_data = @intFromPtr(fiber), | ||
| 1516 | .buf_index = 0, | ||
| 1517 | .personality = 0, | ||
| 1518 | .splice_fd_in = 0, | ||
| 1519 | .addr3 = 0, | ||
| 1520 | .resv = 0, | ||
| 1521 | }; | ||
| 1522 | |||
| 1523 | el.yield(null, .nothing); | ||
| 1524 | fiber.exitCancelRegion(thread); | ||
| 1525 | |||
| 1526 | const completion = fiber.resultPointer(Completion); | ||
| 1527 | switch (errno(completion.result)) { | ||
| 1528 | .SUCCESS, .TIME => return, | ||
| 1529 | .INTR => unreachable, | ||
| 1530 | .CANCELED => return error.Canceled, | ||
| 1531 | |||
| 1532 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 1533 | } | ||
| 1534 | } | ||
| 1535 | |||
| 1536 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void { | ||
| 1537 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1538 | el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } }); | ||
| 1539 | } | ||
| 1540 | fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { | ||
| 1541 | var maybe_waiting_fiber: ?*Fiber = @ptrFromInt(@intFromEnum(prev_state)); | ||
| 1542 | while (if (maybe_waiting_fiber) |waiting_fiber| @cmpxchgWeak( | ||
| 1543 | Io.Mutex.State, | ||
| 1544 | &mutex.state, | ||
| 1545 | @enumFromInt(@intFromPtr(waiting_fiber)), | ||
| 1546 | @enumFromInt(@intFromPtr(waiting_fiber.queue_next)), | ||
| 1547 | .release, | ||
| 1548 | .acquire, | ||
| 1549 | ) else @cmpxchgWeak( | ||
| 1550 | Io.Mutex.State, | ||
| 1551 | &mutex.state, | ||
| 1552 | .locked_once, | ||
| 1553 | .unlocked, | ||
| 1554 | .release, | ||
| 1555 | .acquire, | ||
| 1556 | ) orelse return) |next_state| maybe_waiting_fiber = @ptrFromInt(@intFromEnum(next_state)); | ||
| 1557 | maybe_waiting_fiber.?.queue_next = null; | ||
| 1558 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1559 | el.yield(maybe_waiting_fiber.?, .reschedule); | ||
| 1560 | } | ||
| 1561 | |||
| 1562 | const ConditionImpl = struct { | ||
| 1563 | tail: *Fiber, | ||
| 1564 | event: union(enum) { | ||
| 1565 | queued, | ||
| 1566 | wake: Io.Condition.Wake, | ||
| 1567 | }, | ||
| 1568 | }; | ||
| 1569 | |||
| 1570 | fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void { | ||
| 1571 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1572 | el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } }); | ||
| 1573 | const thread = Thread.current(); | ||
| 1574 | const fiber = thread.currentFiber(); | ||
| 1575 | const cond_impl = fiber.resultPointer(ConditionImpl); | ||
| 1576 | try mutex.lock(el.io()); | ||
| 1577 | switch (cond_impl.event) { | ||
| 1578 | .queued => {}, | ||
| 1579 | .wake => |wake| if (fiber.queue_next) |next_fiber| switch (wake) { | ||
| 1580 | .one => if (@cmpxchgStrong( | ||
| 1581 | ?*Fiber, | ||
| 1582 | @as(*?*Fiber, @ptrCast(&cond.state)), | ||
| 1583 | null, | ||
| 1584 | next_fiber, | ||
| 1585 | .release, | ||
| 1586 | .acquire, | ||
| 1587 | )) |old_fiber| { | ||
| 1588 | const old_cond_impl = old_fiber.?.resultPointer(ConditionImpl); | ||
| 1589 | assert(old_cond_impl.tail.queue_next == null); | ||
| 1590 | old_cond_impl.tail.queue_next = next_fiber; | ||
| 1591 | old_cond_impl.tail = cond_impl.tail; | ||
| 1592 | }, | ||
| 1593 | .all => el.schedule(thread, .{ .head = next_fiber, .tail = cond_impl.tail }), | ||
| 1594 | }, | ||
| 1595 | } | ||
| 1596 | fiber.queue_next = null; | ||
| 1597 | } | ||
| 1598 | |||
| 1599 | fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void { | ||
| 1600 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1601 | const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return; | ||
| 1602 | waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake }; | ||
| 1603 | el.yield(waiting_fiber, .reschedule); | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | fn errno(signed: i32) std.os.linux.E { | ||
| 1607 | return .init(@bitCast(@as(isize, signed))); | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | fn getSqe(iou: *IoUring) *std.os.linux.io_uring_sqe { | ||
| 1611 | while (true) return iou.get_sqe() catch { | ||
| 1612 | _ = iou.submit_and_wait(0) catch |err| switch (err) { | ||
| 1613 | error.SignalInterrupt => std.log.warn("submit_and_wait failed with SignalInterrupt", .{}), | ||
| 1614 | else => |e| @panic(@errorName(e)), | ||
| 1615 | }; | ||
| 1616 | continue; | ||
| 1617 | }; | ||
| 1618 | } | ||
lib/std/Io/IoUring.zig created+1618| ... | @@ -0,0 +1,1618 @@ | ||
| 1 | const std = @import("../std.zig"); | ||
| 2 | const builtin = @import("builtin"); | ||
| 3 | const assert = std.debug.assert; | ||
| 4 | const Allocator = std.mem.Allocator; | ||
| 5 | const Io = std.Io; | ||
| 6 | const EventLoop = @This(); | ||
| 7 | const Alignment = std.mem.Alignment; | ||
| 8 | const IoUring = std.os.linux.IoUring; | ||
| 9 | |||
| 10 | /// Must be a thread-safe allocator. | ||
| 11 | gpa: Allocator, | ||
| 12 | main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)), | ||
| 13 | threads: Thread.List, | ||
| 14 | detached: struct { | ||
| 15 | mutex: std.Io.Mutex, | ||
| 16 | list: std.DoublyLinkedList, | ||
| 17 | }, | ||
| 18 | |||
| 19 | /// Empirically saw >128KB being used by the self-hosted backend to panic. | ||
| 20 | const idle_stack_size = 256 * 1024; | ||
| 21 | |||
| 22 | const max_idle_search = 4; | ||
| 23 | const max_steal_ready_search = 4; | ||
| 24 | |||
| 25 | const io_uring_entries = 64; | ||
| 26 | |||
| 27 | const Thread = struct { | ||
| 28 | thread: std.Thread, | ||
| 29 | idle_context: Context, | ||
| 30 | current_context: *Context, | ||
| 31 | ready_queue: ?*Fiber, | ||
| 32 | io_uring: IoUring, | ||
| 33 | idle_search_index: u32, | ||
| 34 | steal_ready_search_index: u32, | ||
| 35 | |||
| 36 | const canceling: ?*Thread = @ptrFromInt(@alignOf(Thread)); | ||
| 37 | |||
| 38 | threadlocal var self: *Thread = undefined; | ||
| 39 | |||
| 40 | fn current() *Thread { | ||
| 41 | return self; | ||
| 42 | } | ||
| 43 | |||
| 44 | fn currentFiber(thread: *Thread) *Fiber { | ||
| 45 | return @fieldParentPtr("context", thread.current_context); | ||
| 46 | } | ||
| 47 | |||
| 48 | const List = struct { | ||
| 49 | allocated: []Thread, | ||
| 50 | reserved: u32, | ||
| 51 | active: u32, | ||
| 52 | }; | ||
| 53 | }; | ||
| 54 | |||
| 55 | const Fiber = struct { | ||
| 56 | required_align: void align(4), | ||
| 57 | context: Context, | ||
| 58 | awaiter: ?*Fiber, | ||
| 59 | queue_next: ?*Fiber, | ||
| 60 | cancel_thread: ?*Thread, | ||
| 61 | awaiting_completions: std.StaticBitSet(3), | ||
| 62 | |||
| 63 | const finished: ?*Fiber = @ptrFromInt(@alignOf(Thread)); | ||
| 64 | |||
| 65 | const max_result_align: Alignment = .@"16"; | ||
| 66 | const max_result_size = max_result_align.forward(64); | ||
| 67 | /// This includes any stack realignments that need to happen, and also the | ||
| 68 | /// initial frame return address slot and argument frame, depending on target. | ||
| 69 | const min_stack_size = 4 * 1024 * 1024; | ||
| 70 | const max_context_align: Alignment = .@"16"; | ||
| 71 | const max_context_size = max_context_align.forward(1024); | ||
| 72 | const max_closure_size: usize = @max(@sizeOf(AsyncClosure), @sizeOf(DetachedClosure)); | ||
| 73 | const max_closure_align: Alignment = .max(.of(AsyncClosure), .of(DetachedClosure)); | ||
| 74 | const allocation_size = std.mem.alignForward( | ||
| 75 | usize, | ||
| 76 | max_closure_align.max(max_context_align).forward( | ||
| 77 | max_result_align.forward(@sizeOf(Fiber)) + max_result_size + min_stack_size, | ||
| 78 | ) + max_closure_size + max_context_size, | ||
| 79 | std.heap.page_size_max, | ||
| 80 | ); | ||
| 81 | |||
| 82 | fn allocate(el: *EventLoop) error{OutOfMemory}!*Fiber { | ||
| 83 | return @ptrCast(try el.gpa.alignedAlloc(u8, .of(Fiber), allocation_size)); | ||
| 84 | } | ||
| 85 | |||
| 86 | fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 { | ||
| 87 | return @as([*]align(@alignOf(Fiber)) u8, @ptrCast(f))[0..allocation_size]; | ||
| 88 | } | ||
| 89 | |||
| 90 | fn allocatedEnd(f: *Fiber) [*]u8 { | ||
| 91 | const allocated_slice = f.allocatedSlice(); | ||
| 92 | return allocated_slice[allocated_slice.len..].ptr; | ||
| 93 | } | ||
| 94 | |||
| 95 | fn resultPointer(f: *Fiber, comptime Result: type) *Result { | ||
| 96 | return @ptrCast(@alignCast(f.resultBytes(.of(Result)))); | ||
| 97 | } | ||
| 98 | |||
| 99 | fn resultBytes(f: *Fiber, alignment: Alignment) [*]u8 { | ||
| 100 | return @ptrFromInt(alignment.forward(@intFromPtr(f) + @sizeOf(Fiber))); | ||
| 101 | } | ||
| 102 | |||
| 103 | fn enterCancelRegion(fiber: *Fiber, thread: *Thread) error{Canceled}!void { | ||
| 104 | if (@cmpxchgStrong( | ||
| 105 | ?*Thread, | ||
| 106 | &fiber.cancel_thread, | ||
| 107 | null, | ||
| 108 | thread, | ||
| 109 | .acq_rel, | ||
| 110 | .acquire, | ||
| 111 | )) |cancel_thread| { | ||
| 112 | assert(cancel_thread == Thread.canceling); | ||
| 113 | return error.Canceled; | ||
| 114 | } | ||
| 115 | } | ||
| 116 | |||
| 117 | fn exitCancelRegion(fiber: *Fiber, thread: *Thread) void { | ||
| 118 | if (@cmpxchgStrong( | ||
| 119 | ?*Thread, | ||
| 120 | &fiber.cancel_thread, | ||
| 121 | thread, | ||
| 122 | null, | ||
| 123 | .acq_rel, | ||
| 124 | .acquire, | ||
| 125 | )) |cancel_thread| assert(cancel_thread == Thread.canceling); | ||
| 126 | } | ||
| 127 | |||
| 128 | const Queue = struct { head: *Fiber, tail: *Fiber }; | ||
| 129 | }; | ||
| 130 | |||
| 131 | fn recycle(el: *EventLoop, fiber: *Fiber) void { | ||
| 132 | std.log.debug("recyling {*}", .{fiber}); | ||
| 133 | assert(fiber.queue_next == null); | ||
| 134 | el.gpa.free(fiber.allocatedSlice()); | ||
| 135 | } | ||
| 136 | |||
| 137 | pub fn io(el: *EventLoop) Io { | ||
| 138 | return .{ | ||
| 139 | .userdata = el, | ||
| 140 | .vtable = &.{ | ||
| 141 | .async = async, | ||
| 142 | .concurrent = concurrent, | ||
| 143 | .await = await, | ||
| 144 | .asyncDetached = asyncDetached, | ||
| 145 | .select = select, | ||
| 146 | .cancel = cancel, | ||
| 147 | .cancelRequested = cancelRequested, | ||
| 148 | |||
| 149 | .mutexLock = mutexLock, | ||
| 150 | .mutexUnlock = mutexUnlock, | ||
| 151 | |||
| 152 | .conditionWait = conditionWait, | ||
| 153 | .conditionWake = conditionWake, | ||
| 154 | |||
| 155 | .createFile = createFile, | ||
| 156 | .fileOpen = fileOpen, | ||
| 157 | .fileClose = fileClose, | ||
| 158 | .pread = pread, | ||
| 159 | .pwrite = pwrite, | ||
| 160 | |||
| 161 | .now = now, | ||
| 162 | .sleep = sleep, | ||
| 163 | }, | ||
| 164 | }; | ||
| 165 | } | ||
| 166 | |||
| 167 | pub fn init(el: *EventLoop, gpa: Allocator) !void { | ||
| 168 | const threads_size = @max(std.Thread.getCpuCount() catch 1, 1) * @sizeOf(Thread); | ||
| 169 | const idle_stack_end_offset = std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max); | ||
| 170 | const allocated_slice = try gpa.alignedAlloc(u8, .of(Thread), idle_stack_end_offset); | ||
| 171 | errdefer gpa.free(allocated_slice); | ||
| 172 | el.* = .{ | ||
| 173 | .gpa = gpa, | ||
| 174 | .main_fiber_buffer = undefined, | ||
| 175 | .threads = .{ | ||
| 176 | .allocated = @ptrCast(allocated_slice[0..threads_size]), | ||
| 177 | .reserved = 1, | ||
| 178 | .active = 1, | ||
| 179 | }, | ||
| 180 | .detached = .{ | ||
| 181 | .mutex = .init, | ||
| 182 | .list = .{}, | ||
| 183 | }, | ||
| 184 | }; | ||
| 185 | const main_fiber: *Fiber = @ptrCast(&el.main_fiber_buffer); | ||
| 186 | main_fiber.* = .{ | ||
| 187 | .required_align = {}, | ||
| 188 | .context = undefined, | ||
| 189 | .awaiter = null, | ||
| 190 | .queue_next = null, | ||
| 191 | .cancel_thread = null, | ||
| 192 | .awaiting_completions = .initEmpty(), | ||
| 193 | }; | ||
| 194 | const main_thread = &el.threads.allocated[0]; | ||
| 195 | Thread.self = main_thread; | ||
| 196 | const idle_stack_end: [*]align(16) usize = @ptrCast(@alignCast(allocated_slice[idle_stack_end_offset..].ptr)); | ||
| 197 | (idle_stack_end - 1)[0..1].* = .{@intFromPtr(el)}; | ||
| 198 | main_thread.* = .{ | ||
| 199 | .thread = undefined, | ||
| 200 | .idle_context = switch (builtin.cpu.arch) { | ||
| 201 | .aarch64 => .{ | ||
| 202 | .sp = @intFromPtr(idle_stack_end), | ||
| 203 | .fp = 0, | ||
| 204 | .pc = @intFromPtr(&mainIdleEntry), | ||
| 205 | }, | ||
| 206 | .x86_64 => .{ | ||
| 207 | .rsp = @intFromPtr(idle_stack_end - 1), | ||
| 208 | .rbp = 0, | ||
| 209 | .rip = @intFromPtr(&mainIdleEntry), | ||
| 210 | }, | ||
| 211 | else => @compileError("unimplemented architecture"), | ||
| 212 | }, | ||
| 213 | .current_context = &main_fiber.context, | ||
| 214 | .ready_queue = null, | ||
| 215 | .io_uring = try IoUring.init(io_uring_entries, 0), | ||
| 216 | .idle_search_index = 1, | ||
| 217 | .steal_ready_search_index = 1, | ||
| 218 | }; | ||
| 219 | errdefer main_thread.io_uring.deinit(); | ||
| 220 | std.log.debug("created main idle {*}", .{&main_thread.idle_context}); | ||
| 221 | std.log.debug("created main {*}", .{main_fiber}); | ||
| 222 | } | ||
| 223 | |||
| 224 | pub fn deinit(el: *EventLoop) void { | ||
| 225 | while (true) cancel(el, detached_future: { | ||
| 226 | el.detached.mutex.lock(el.io()) catch |err| switch (err) { | ||
| 227 | error.Canceled => unreachable, // main fiber cannot be canceled | ||
| 228 | }; | ||
| 229 | defer el.detached.mutex.unlock(el.io()); | ||
| 230 | const detached: *DetachedClosure = @fieldParentPtr( | ||
| 231 | "detached_queue_node", | ||
| 232 | el.detached.list.pop() orelse break, | ||
| 233 | ); | ||
| 234 | // notify the detached fiber that it is no longer allowed to recycle itself | ||
| 235 | detached.detached_queue_node = .{ | ||
| 236 | .prev = &detached.detached_queue_node, | ||
| 237 | .next = &detached.detached_queue_node, | ||
| 238 | }; | ||
| 239 | break :detached_future @ptrCast(detached.fiber); | ||
| 240 | }, &.{}, .@"1"); | ||
| 241 | const active_threads = @atomicLoad(u32, &el.threads.active, .acquire); | ||
| 242 | for (el.threads.allocated[0..active_threads]) |*thread| { | ||
| 243 | const ready_fiber = @atomicLoad(?*Fiber, &thread.ready_queue, .monotonic); | ||
| 244 | assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async | ||
| 245 | } | ||
| 246 | el.yield(null, .exit); | ||
| 247 | const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(el.threads.allocated.ptr)); | ||
| 248 | const idle_stack_end_offset = std.mem.alignForward(usize, el.threads.allocated.len * @sizeOf(Thread) + idle_stack_size, std.heap.page_size_max); | ||
| 249 | for (el.threads.allocated[1..active_threads]) |*thread| thread.thread.join(); | ||
| 250 | el.gpa.free(allocated_ptr[0..idle_stack_end_offset]); | ||
| 251 | el.* = undefined; | ||
| 252 | } | ||
| 253 | |||
| 254 | fn findReadyFiber(el: *EventLoop, thread: *Thread) ?*Fiber { | ||
| 255 | if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| { | ||
| 256 | @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release); | ||
| 257 | ready_fiber.queue_next = null; | ||
| 258 | return ready_fiber; | ||
| 259 | } | ||
| 260 | const active_threads = @atomicLoad(u32, &el.threads.active, .acquire); | ||
| 261 | for (0..@min(max_steal_ready_search, active_threads)) |_| { | ||
| 262 | defer thread.steal_ready_search_index += 1; | ||
| 263 | if (thread.steal_ready_search_index == active_threads) thread.steal_ready_search_index = 0; | ||
| 264 | const steal_ready_search_thread = &el.threads.allocated[0..active_threads][thread.steal_ready_search_index]; | ||
| 265 | if (steal_ready_search_thread == thread) continue; | ||
| 266 | const ready_fiber = @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .acquire) orelse continue; | ||
| 267 | if (ready_fiber == Fiber.finished) continue; | ||
| 268 | if (@cmpxchgWeak( | ||
| 269 | ?*Fiber, | ||
| 270 | &steal_ready_search_thread.ready_queue, | ||
| 271 | ready_fiber, | ||
| 272 | null, | ||
| 273 | .acquire, | ||
| 274 | .monotonic, | ||
| 275 | )) |_| continue; | ||
| 276 | @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.queue_next, .release); | ||
| 277 | ready_fiber.queue_next = null; | ||
| 278 | return ready_fiber; | ||
| 279 | } | ||
| 280 | // couldn't find anything to do, so we are now open for business | ||
| 281 | @atomicStore(?*Fiber, &thread.ready_queue, null, .monotonic); | ||
| 282 | return null; | ||
| 283 | } | ||
| 284 | |||
| 285 | fn yield(el: *EventLoop, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.PendingTask) void { | ||
| 286 | const thread: *Thread = .current(); | ||
| 287 | const ready_context = if (maybe_ready_fiber orelse el.findReadyFiber(thread)) |ready_fiber| | ||
| 288 | &ready_fiber.context | ||
| 289 | else | ||
| 290 | &thread.idle_context; | ||
| 291 | const message: SwitchMessage = .{ | ||
| 292 | .contexts = .{ | ||
| 293 | .prev = thread.current_context, | ||
| 294 | .ready = ready_context, | ||
| 295 | }, | ||
| 296 | .pending_task = pending_task, | ||
| 297 | }; | ||
| 298 | std.log.debug("switching from {*} to {*}", .{ message.contexts.prev, message.contexts.ready }); | ||
| 299 | contextSwitch(&message).handle(el); | ||
| 300 | } | ||
| 301 | |||
| 302 | fn schedule(el: *EventLoop, thread: *Thread, ready_queue: Fiber.Queue) void { | ||
| 303 | { | ||
| 304 | var fiber = ready_queue.head; | ||
| 305 | while (true) { | ||
| 306 | std.log.debug("scheduling {*}", .{fiber}); | ||
| 307 | fiber = fiber.queue_next orelse break; | ||
| 308 | } | ||
| 309 | assert(fiber == ready_queue.tail); | ||
| 310 | } | ||
| 311 | // shared fields of previous `Thread` must be initialized before later ones are marked as active | ||
| 312 | const new_thread_index = @atomicLoad(u32, &el.threads.active, .acquire); | ||
| 313 | for (0..@min(max_idle_search, new_thread_index)) |_| { | ||
| 314 | defer thread.idle_search_index += 1; | ||
| 315 | if (thread.idle_search_index == new_thread_index) thread.idle_search_index = 0; | ||
| 316 | const idle_search_thread = &el.threads.allocated[0..new_thread_index][thread.idle_search_index]; | ||
| 317 | if (idle_search_thread == thread) continue; | ||
| 318 | if (@cmpxchgWeak( | ||
| 319 | ?*Fiber, | ||
| 320 | &idle_search_thread.ready_queue, | ||
| 321 | null, | ||
| 322 | ready_queue.head, | ||
| 323 | .release, | ||
| 324 | .monotonic, | ||
| 325 | )) |_| continue; | ||
| 326 | getSqe(&thread.io_uring).* = .{ | ||
| 327 | .opcode = .MSG_RING, | ||
| 328 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 329 | .ioprio = 0, | ||
| 330 | .fd = idle_search_thread.io_uring.fd, | ||
| 331 | .off = @intFromEnum(Completion.UserData.wakeup), | ||
| 332 | .addr = 0, | ||
| 333 | .len = 0, | ||
| 334 | .rw_flags = 0, | ||
| 335 | .user_data = @intFromEnum(Completion.UserData.wakeup), | ||
| 336 | .buf_index = 0, | ||
| 337 | .personality = 0, | ||
| 338 | .splice_fd_in = 0, | ||
| 339 | .addr3 = 0, | ||
| 340 | .resv = 0, | ||
| 341 | }; | ||
| 342 | return; | ||
| 343 | } | ||
| 344 | spawn_thread: { | ||
| 345 | // previous failed reservations must have completed before retrying | ||
| 346 | if (new_thread_index == el.threads.allocated.len or @cmpxchgWeak( | ||
| 347 | u32, | ||
| 348 | &el.threads.reserved, | ||
| 349 | new_thread_index, | ||
| 350 | new_thread_index + 1, | ||
| 351 | .acquire, | ||
| 352 | .monotonic, | ||
| 353 | ) != null) break :spawn_thread; | ||
| 354 | const new_thread = &el.threads.allocated[new_thread_index]; | ||
| 355 | const next_thread_index = new_thread_index + 1; | ||
| 356 | new_thread.* = .{ | ||
| 357 | .thread = undefined, | ||
| 358 | .idle_context = undefined, | ||
| 359 | .current_context = &new_thread.idle_context, | ||
| 360 | .ready_queue = ready_queue.head, | ||
| 361 | .io_uring = IoUring.init(io_uring_entries, 0) catch |err| { | ||
| 362 | @atomicStore(u32, &el.threads.reserved, new_thread_index, .release); | ||
| 363 | // no more access to `thread` after giving up reservation | ||
| 364 | std.log.warn("unable to create worker thread due to io_uring init failure: {s}", .{@errorName(err)}); | ||
| 365 | break :spawn_thread; | ||
| 366 | }, | ||
| 367 | .idle_search_index = 0, | ||
| 368 | .steal_ready_search_index = 0, | ||
| 369 | }; | ||
| 370 | new_thread.thread = std.Thread.spawn(.{ | ||
| 371 | .stack_size = idle_stack_size, | ||
| 372 | .allocator = el.gpa, | ||
| 373 | }, threadEntry, .{ el, new_thread_index }) catch |err| { | ||
| 374 | new_thread.io_uring.deinit(); | ||
| 375 | @atomicStore(u32, &el.threads.reserved, new_thread_index, .release); | ||
| 376 | // no more access to `thread` after giving up reservation | ||
| 377 | std.log.warn("unable to create worker thread due spawn failure: {s}", .{@errorName(err)}); | ||
| 378 | break :spawn_thread; | ||
| 379 | }; | ||
| 380 | // shared fields of `Thread` must be initialized before being marked active | ||
| 381 | @atomicStore(u32, &el.threads.active, next_thread_index, .release); | ||
| 382 | return; | ||
| 383 | } | ||
| 384 | // nobody wanted it, so just queue it on ourselves | ||
| 385 | while (@cmpxchgWeak( | ||
| 386 | ?*Fiber, | ||
| 387 | &thread.ready_queue, | ||
| 388 | ready_queue.tail.queue_next, | ||
| 389 | ready_queue.head, | ||
| 390 | .acq_rel, | ||
| 391 | .acquire, | ||
| 392 | )) |old_head| ready_queue.tail.queue_next = old_head; | ||
| 393 | } | ||
| 394 | |||
| 395 | fn mainIdle(el: *EventLoop, message: *const SwitchMessage) callconv(.withStackAlign(.c, @max(@alignOf(Thread), @alignOf(Context)))) noreturn { | ||
| 396 | message.handle(el); | ||
| 397 | el.idle(&el.threads.allocated[0]); | ||
| 398 | el.yield(@ptrCast(&el.main_fiber_buffer), .nothing); | ||
| 399 | unreachable; // switched to dead fiber | ||
| 400 | } | ||
| 401 | |||
| 402 | fn threadEntry(el: *EventLoop, index: u32) void { | ||
| 403 | const thread: *Thread = &el.threads.allocated[index]; | ||
| 404 | Thread.self = thread; | ||
| 405 | std.log.debug("created thread idle {*}", .{&thread.idle_context}); | ||
| 406 | el.idle(thread); | ||
| 407 | } | ||
| 408 | |||
| 409 | const Completion = struct { | ||
| 410 | const UserData = enum(usize) { | ||
| 411 | unused, | ||
| 412 | wakeup, | ||
| 413 | cleanup, | ||
| 414 | exit, | ||
| 415 | /// *Fiber | ||
| 416 | _, | ||
| 417 | }; | ||
| 418 | result: i32, | ||
| 419 | flags: u32, | ||
| 420 | }; | ||
| 421 | |||
| 422 | fn idle(el: *EventLoop, thread: *Thread) void { | ||
| 423 | var maybe_ready_fiber: ?*Fiber = null; | ||
| 424 | while (true) { | ||
| 425 | while (maybe_ready_fiber orelse el.findReadyFiber(thread)) |ready_fiber| { | ||
| 426 | el.yield(ready_fiber, .nothing); | ||
| 427 | maybe_ready_fiber = null; | ||
| 428 | } | ||
| 429 | _ = thread.io_uring.submit_and_wait(1) catch |err| switch (err) { | ||
| 430 | error.SignalInterrupt => std.log.warn("submit_and_wait failed with SignalInterrupt", .{}), | ||
| 431 | else => |e| @panic(@errorName(e)), | ||
| 432 | }; | ||
| 433 | var cqes_buffer: [io_uring_entries]std.os.linux.io_uring_cqe = undefined; | ||
| 434 | var maybe_ready_queue: ?Fiber.Queue = null; | ||
| 435 | for (cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) { | ||
| 436 | error.SignalInterrupt => cqes_len: { | ||
| 437 | std.log.warn("copy_cqes failed with SignalInterrupt", .{}); | ||
| 438 | break :cqes_len 0; | ||
| 439 | }, | ||
| 440 | else => |e| @panic(@errorName(e)), | ||
| 441 | }]) |cqe| switch (@as(Completion.UserData, @enumFromInt(cqe.user_data))) { | ||
| 442 | .unused => unreachable, // bad submission queued? | ||
| 443 | .wakeup => {}, | ||
| 444 | .cleanup => @panic("failed to notify other threads that we are exiting"), | ||
| 445 | .exit => { | ||
| 446 | assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async | ||
| 447 | return; | ||
| 448 | }, | ||
| 449 | _ => switch (errno(cqe.res)) { | ||
| 450 | .INTR => getSqe(&thread.io_uring).* = .{ | ||
| 451 | .opcode = .ASYNC_CANCEL, | ||
| 452 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 453 | .ioprio = 0, | ||
| 454 | .fd = 0, | ||
| 455 | .off = 0, | ||
| 456 | .addr = cqe.user_data, | ||
| 457 | .len = 0, | ||
| 458 | .rw_flags = 0, | ||
| 459 | .user_data = @intFromEnum(Completion.UserData.wakeup), | ||
| 460 | .buf_index = 0, | ||
| 461 | .personality = 0, | ||
| 462 | .splice_fd_in = 0, | ||
| 463 | .addr3 = 0, | ||
| 464 | .resv = 0, | ||
| 465 | }, | ||
| 466 | else => { | ||
| 467 | const fiber: *Fiber = @ptrFromInt(cqe.user_data); | ||
| 468 | assert(fiber.queue_next == null); | ||
| 469 | fiber.resultPointer(Completion).* = .{ | ||
| 470 | .result = cqe.res, | ||
| 471 | .flags = cqe.flags, | ||
| 472 | }; | ||
| 473 | if (maybe_ready_fiber == null) maybe_ready_fiber = fiber else if (maybe_ready_queue) |*ready_queue| { | ||
| 474 | ready_queue.tail.queue_next = fiber; | ||
| 475 | ready_queue.tail = fiber; | ||
| 476 | } else maybe_ready_queue = .{ .head = fiber, .tail = fiber }; | ||
| 477 | }, | ||
| 478 | }, | ||
| 479 | }; | ||
| 480 | if (maybe_ready_queue) |ready_queue| el.schedule(thread, ready_queue); | ||
| 481 | } | ||
| 482 | } | ||
| 483 | |||
| 484 | const SwitchMessage = struct { | ||
| 485 | contexts: extern struct { | ||
| 486 | prev: *Context, | ||
| 487 | ready: *Context, | ||
| 488 | }, | ||
| 489 | pending_task: PendingTask, | ||
| 490 | |||
| 491 | const PendingTask = union(enum) { | ||
| 492 | nothing, | ||
| 493 | reschedule, | ||
| 494 | recycle, | ||
| 495 | register_awaiter: *?*Fiber, | ||
| 496 | register_select: []const *Io.AnyFuture, | ||
| 497 | mutex_lock: struct { | ||
| 498 | prev_state: Io.Mutex.State, | ||
| 499 | mutex: *Io.Mutex, | ||
| 500 | }, | ||
| 501 | condition_wait: struct { | ||
| 502 | cond: *Io.Condition, | ||
| 503 | mutex: *Io.Mutex, | ||
| 504 | }, | ||
| 505 | exit, | ||
| 506 | }; | ||
| 507 | |||
| 508 | fn handle(message: *const SwitchMessage, el: *EventLoop) void { | ||
| 509 | const thread: *Thread = .current(); | ||
| 510 | thread.current_context = message.contexts.ready; | ||
| 511 | switch (message.pending_task) { | ||
| 512 | .nothing => {}, | ||
| 513 | .reschedule => if (message.contexts.prev != &thread.idle_context) { | ||
| 514 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 515 | assert(prev_fiber.queue_next == null); | ||
| 516 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 517 | }, | ||
| 518 | .recycle => { | ||
| 519 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 520 | assert(prev_fiber.queue_next == null); | ||
| 521 | el.recycle(prev_fiber); | ||
| 522 | }, | ||
| 523 | .register_awaiter => |awaiter| { | ||
| 524 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 525 | assert(prev_fiber.queue_next == null); | ||
| 526 | if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) | ||
| 527 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 528 | }, | ||
| 529 | .register_select => |futures| { | ||
| 530 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 531 | assert(prev_fiber.queue_next == null); | ||
| 532 | for (futures) |any_future| { | ||
| 533 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 534 | if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) { | ||
| 535 | const closure: *AsyncClosure = .fromFiber(future_fiber); | ||
| 536 | if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) { | ||
| 537 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 538 | } | ||
| 539 | } | ||
| 540 | } | ||
| 541 | }, | ||
| 542 | .mutex_lock => |mutex_lock| { | ||
| 543 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 544 | assert(prev_fiber.queue_next == null); | ||
| 545 | var prev_state = mutex_lock.prev_state; | ||
| 546 | while (switch (prev_state) { | ||
| 547 | else => next_state: { | ||
| 548 | prev_fiber.queue_next = @ptrFromInt(@intFromEnum(prev_state)); | ||
| 549 | break :next_state @cmpxchgWeak( | ||
| 550 | Io.Mutex.State, | ||
| 551 | &mutex_lock.mutex.state, | ||
| 552 | prev_state, | ||
| 553 | @enumFromInt(@intFromPtr(prev_fiber)), | ||
| 554 | .release, | ||
| 555 | .acquire, | ||
| 556 | ); | ||
| 557 | }, | ||
| 558 | .unlocked => @cmpxchgWeak( | ||
| 559 | Io.Mutex.State, | ||
| 560 | &mutex_lock.mutex.state, | ||
| 561 | .unlocked, | ||
| 562 | .locked_once, | ||
| 563 | .acquire, | ||
| 564 | .acquire, | ||
| 565 | ) orelse { | ||
| 566 | prev_fiber.queue_next = null; | ||
| 567 | el.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber }); | ||
| 568 | return; | ||
| 569 | }, | ||
| 570 | }) |next_state| prev_state = next_state; | ||
| 571 | }, | ||
| 572 | .condition_wait => |condition_wait| { | ||
| 573 | const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev)); | ||
| 574 | assert(prev_fiber.queue_next == null); | ||
| 575 | const cond_impl = prev_fiber.resultPointer(ConditionImpl); | ||
| 576 | cond_impl.* = .{ | ||
| 577 | .tail = prev_fiber, | ||
| 578 | .event = .queued, | ||
| 579 | }; | ||
| 580 | if (@cmpxchgStrong( | ||
| 581 | ?*Fiber, | ||
| 582 | @as(*?*Fiber, @ptrCast(&condition_wait.cond.state)), | ||
| 583 | null, | ||
| 584 | prev_fiber, | ||
| 585 | .release, | ||
| 586 | .acquire, | ||
| 587 | )) |waiting_fiber| { | ||
| 588 | const waiting_cond_impl = waiting_fiber.?.resultPointer(ConditionImpl); | ||
| 589 | assert(waiting_cond_impl.tail.queue_next == null); | ||
| 590 | waiting_cond_impl.tail.queue_next = prev_fiber; | ||
| 591 | waiting_cond_impl.tail = prev_fiber; | ||
| 592 | } | ||
| 593 | condition_wait.mutex.unlock(el.io()); | ||
| 594 | }, | ||
| 595 | .exit => for (el.threads.allocated[0..@atomicLoad(u32, &el.threads.active, .acquire)]) |*each_thread| { | ||
| 596 | getSqe(&thread.io_uring).* = .{ | ||
| 597 | .opcode = .MSG_RING, | ||
| 598 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 599 | .ioprio = 0, | ||
| 600 | .fd = each_thread.io_uring.fd, | ||
| 601 | .off = @intFromEnum(Completion.UserData.exit), | ||
| 602 | .addr = 0, | ||
| 603 | .len = 0, | ||
| 604 | .rw_flags = 0, | ||
| 605 | .user_data = @intFromEnum(Completion.UserData.cleanup), | ||
| 606 | .buf_index = 0, | ||
| 607 | .personality = 0, | ||
| 608 | .splice_fd_in = 0, | ||
| 609 | .addr3 = 0, | ||
| 610 | .resv = 0, | ||
| 611 | }; | ||
| 612 | }, | ||
| 613 | } | ||
| 614 | } | ||
| 615 | }; | ||
| 616 | |||
| 617 | const Context = switch (builtin.cpu.arch) { | ||
| 618 | .aarch64 => extern struct { | ||
| 619 | sp: u64, | ||
| 620 | fp: u64, | ||
| 621 | pc: u64, | ||
| 622 | }, | ||
| 623 | .x86_64 => extern struct { | ||
| 624 | rsp: u64, | ||
| 625 | rbp: u64, | ||
| 626 | rip: u64, | ||
| 627 | }, | ||
| 628 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 629 | }; | ||
| 630 | |||
| 631 | inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage { | ||
| 632 | return @fieldParentPtr("contexts", switch (builtin.cpu.arch) { | ||
| 633 | .aarch64 => asm volatile ( | ||
| 634 | \\ ldp x0, x2, [x1] | ||
| 635 | \\ ldr x3, [x2, #16] | ||
| 636 | \\ mov x4, sp | ||
| 637 | \\ stp x4, fp, [x0] | ||
| 638 | \\ adr x5, 0f | ||
| 639 | \\ ldp x4, fp, [x2] | ||
| 640 | \\ str x5, [x0, #16] | ||
| 641 | \\ mov sp, x4 | ||
| 642 | \\ br x3 | ||
| 643 | \\0: | ||
| 644 | : [received_message] "={x1}" (-> *const @FieldType(SwitchMessage, "contexts")), | ||
| 645 | : [message_to_send] "{x1}" (&message.contexts), | ||
| 646 | : .{ | ||
| 647 | .x0 = true, | ||
| 648 | .x1 = true, | ||
| 649 | .x2 = true, | ||
| 650 | .x3 = true, | ||
| 651 | .x4 = true, | ||
| 652 | .x5 = true, | ||
| 653 | .x6 = true, | ||
| 654 | .x7 = true, | ||
| 655 | .x8 = true, | ||
| 656 | .x9 = true, | ||
| 657 | .x10 = true, | ||
| 658 | .x11 = true, | ||
| 659 | .x12 = true, | ||
| 660 | .x13 = true, | ||
| 661 | .x14 = true, | ||
| 662 | .x15 = true, | ||
| 663 | .x16 = true, | ||
| 664 | .x17 = true, | ||
| 665 | .x18 = true, | ||
| 666 | .x19 = true, | ||
| 667 | .x20 = true, | ||
| 668 | .x21 = true, | ||
| 669 | .x22 = true, | ||
| 670 | .x23 = true, | ||
| 671 | .x24 = true, | ||
| 672 | .x25 = true, | ||
| 673 | .x26 = true, | ||
| 674 | .x27 = true, | ||
| 675 | .x28 = true, | ||
| 676 | .x30 = true, | ||
| 677 | .z0 = true, | ||
| 678 | .z1 = true, | ||
| 679 | .z2 = true, | ||
| 680 | .z3 = true, | ||
| 681 | .z4 = true, | ||
| 682 | .z5 = true, | ||
| 683 | .z6 = true, | ||
| 684 | .z7 = true, | ||
| 685 | .z8 = true, | ||
| 686 | .z9 = true, | ||
| 687 | .z10 = true, | ||
| 688 | .z11 = true, | ||
| 689 | .z12 = true, | ||
| 690 | .z13 = true, | ||
| 691 | .z14 = true, | ||
| 692 | .z15 = true, | ||
| 693 | .z16 = true, | ||
| 694 | .z17 = true, | ||
| 695 | .z18 = true, | ||
| 696 | .z19 = true, | ||
| 697 | .z20 = true, | ||
| 698 | .z21 = true, | ||
| 699 | .z22 = true, | ||
| 700 | .z23 = true, | ||
| 701 | .z24 = true, | ||
| 702 | .z25 = true, | ||
| 703 | .z26 = true, | ||
| 704 | .z27 = true, | ||
| 705 | .z28 = true, | ||
| 706 | .z29 = true, | ||
| 707 | .z30 = true, | ||
| 708 | .z31 = true, | ||
| 709 | .p0 = true, | ||
| 710 | .p1 = true, | ||
| 711 | .p2 = true, | ||
| 712 | .p3 = true, | ||
| 713 | .p4 = true, | ||
| 714 | .p5 = true, | ||
| 715 | .p6 = true, | ||
| 716 | .p7 = true, | ||
| 717 | .p8 = true, | ||
| 718 | .p9 = true, | ||
| 719 | .p10 = true, | ||
| 720 | .p11 = true, | ||
| 721 | .p12 = true, | ||
| 722 | .p13 = true, | ||
| 723 | .p14 = true, | ||
| 724 | .p15 = true, | ||
| 725 | .fpcr = true, | ||
| 726 | .fpsr = true, | ||
| 727 | .ffr = true, | ||
| 728 | .memory = true, | ||
| 729 | }), | ||
| 730 | .x86_64 => asm volatile ( | ||
| 731 | \\ movq 0(%%rsi), %%rax | ||
| 732 | \\ movq 8(%%rsi), %%rcx | ||
| 733 | \\ leaq 0f(%%rip), %%rdx | ||
| 734 | \\ movq %%rsp, 0(%%rax) | ||
| 735 | \\ movq %%rbp, 8(%%rax) | ||
| 736 | \\ movq %%rdx, 16(%%rax) | ||
| 737 | \\ movq 0(%%rcx), %%rsp | ||
| 738 | \\ movq 8(%%rcx), %%rbp | ||
| 739 | \\ jmpq *16(%%rcx) | ||
| 740 | \\0: | ||
| 741 | : [received_message] "={rsi}" (-> *const @FieldType(SwitchMessage, "contexts")), | ||
| 742 | : [message_to_send] "{rsi}" (&message.contexts), | ||
| 743 | : .{ | ||
| 744 | .rax = true, | ||
| 745 | .rcx = true, | ||
| 746 | .rdx = true, | ||
| 747 | .rbx = true, | ||
| 748 | .rsi = true, | ||
| 749 | .rdi = true, | ||
| 750 | .r8 = true, | ||
| 751 | .r9 = true, | ||
| 752 | .r10 = true, | ||
| 753 | .r11 = true, | ||
| 754 | .r12 = true, | ||
| 755 | .r13 = true, | ||
| 756 | .r14 = true, | ||
| 757 | .r15 = true, | ||
| 758 | .mm0 = true, | ||
| 759 | .mm1 = true, | ||
| 760 | .mm2 = true, | ||
| 761 | .mm3 = true, | ||
| 762 | .mm4 = true, | ||
| 763 | .mm5 = true, | ||
| 764 | .mm6 = true, | ||
| 765 | .mm7 = true, | ||
| 766 | .zmm0 = true, | ||
| 767 | .zmm1 = true, | ||
| 768 | .zmm2 = true, | ||
| 769 | .zmm3 = true, | ||
| 770 | .zmm4 = true, | ||
| 771 | .zmm5 = true, | ||
| 772 | .zmm6 = true, | ||
| 773 | .zmm7 = true, | ||
| 774 | .zmm8 = true, | ||
| 775 | .zmm9 = true, | ||
| 776 | .zmm10 = true, | ||
| 777 | .zmm11 = true, | ||
| 778 | .zmm12 = true, | ||
| 779 | .zmm13 = true, | ||
| 780 | .zmm14 = true, | ||
| 781 | .zmm15 = true, | ||
| 782 | .zmm16 = true, | ||
| 783 | .zmm17 = true, | ||
| 784 | .zmm18 = true, | ||
| 785 | .zmm19 = true, | ||
| 786 | .zmm20 = true, | ||
| 787 | .zmm21 = true, | ||
| 788 | .zmm22 = true, | ||
| 789 | .zmm23 = true, | ||
| 790 | .zmm24 = true, | ||
| 791 | .zmm25 = true, | ||
| 792 | .zmm26 = true, | ||
| 793 | .zmm27 = true, | ||
| 794 | .zmm28 = true, | ||
| 795 | .zmm29 = true, | ||
| 796 | .zmm30 = true, | ||
| 797 | .zmm31 = true, | ||
| 798 | .fpsr = true, | ||
| 799 | .fpcr = true, | ||
| 800 | .mxcsr = true, | ||
| 801 | .rflags = true, | ||
| 802 | .dirflag = true, | ||
| 803 | .memory = true, | ||
| 804 | }), | ||
| 805 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 806 | }); | ||
| 807 | } | ||
| 808 | |||
| 809 | fn mainIdleEntry() callconv(.naked) void { | ||
| 810 | switch (builtin.cpu.arch) { | ||
| 811 | .x86_64 => asm volatile ( | ||
| 812 | \\ movq (%%rsp), %%rdi | ||
| 813 | \\ jmp %[mainIdle:P] | ||
| 814 | : | ||
| 815 | : [mainIdle] "X" (&mainIdle), | ||
| 816 | ), | ||
| 817 | .aarch64 => asm volatile ( | ||
| 818 | \\ ldr x0, [sp, #-8] | ||
| 819 | \\ b %[mainIdle] | ||
| 820 | : | ||
| 821 | : [mainIdle] "X" (&mainIdle), | ||
| 822 | ), | ||
| 823 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 824 | } | ||
| 825 | } | ||
| 826 | |||
| 827 | fn fiberEntry() callconv(.naked) void { | ||
| 828 | switch (builtin.cpu.arch) { | ||
| 829 | .x86_64 => asm volatile ( | ||
| 830 | \\ leaq 8(%%rsp), %%rdi | ||
| 831 | \\ jmpq *(%%rsp) | ||
| 832 | ), | ||
| 833 | .aarch64 => asm volatile ( | ||
| 834 | \\ mov x0, sp | ||
| 835 | \\ ldr x2, [sp, #-8] | ||
| 836 | \\ br x2 | ||
| 837 | ), | ||
| 838 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 839 | } | ||
| 840 | } | ||
| 841 | |||
| 842 | const AsyncClosure = struct { | ||
| 843 | event_loop: *EventLoop, | ||
| 844 | fiber: *Fiber, | ||
| 845 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, | ||
| 846 | result_align: Alignment, | ||
| 847 | already_awaited: bool, | ||
| 848 | |||
| 849 | fn contextPointer(closure: *AsyncClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 { | ||
| 850 | return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(AsyncClosure)); | ||
| 851 | } | ||
| 852 | |||
| 853 | fn call(closure: *AsyncClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn { | ||
| 854 | message.handle(closure.event_loop); | ||
| 855 | const fiber = closure.fiber; | ||
| 856 | std.log.debug("{*} performing async", .{fiber}); | ||
| 857 | closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align)); | ||
| 858 | const awaiter = @atomicRmw(?*Fiber, &fiber.awaiter, .Xchg, Fiber.finished, .acq_rel); | ||
| 859 | const ready_awaiter = r: { | ||
| 860 | const a = awaiter orelse break :r null; | ||
| 861 | if (@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .acq_rel)) break :r null; | ||
| 862 | break :r a; | ||
| 863 | }; | ||
| 864 | closure.event_loop.yield(ready_awaiter, .nothing); | ||
| 865 | unreachable; // switched to dead fiber | ||
| 866 | } | ||
| 867 | |||
| 868 | fn fromFiber(fiber: *Fiber) *AsyncClosure { | ||
| 869 | return @ptrFromInt(Fiber.max_context_align.max(.of(AsyncClosure)).backward( | ||
| 870 | @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size, | ||
| 871 | ) - @sizeOf(AsyncClosure)); | ||
| 872 | } | ||
| 873 | }; | ||
| 874 | |||
| 875 | fn async( | ||
| 876 | userdata: ?*anyopaque, | ||
| 877 | result: []u8, | ||
| 878 | result_alignment: Alignment, | ||
| 879 | context: []const u8, | ||
| 880 | context_alignment: Alignment, | ||
| 881 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, | ||
| 882 | ) ?*std.Io.AnyFuture { | ||
| 883 | return concurrent(userdata, result.len, result_alignment, context, context_alignment, start) catch { | ||
| 884 | start(context.ptr, result.ptr); | ||
| 885 | return null; | ||
| 886 | }; | ||
| 887 | } | ||
| 888 | |||
| 889 | fn concurrent( | ||
| 890 | userdata: ?*anyopaque, | ||
| 891 | result_len: usize, | ||
| 892 | result_alignment: Alignment, | ||
| 893 | context: []const u8, | ||
| 894 | context_alignment: Alignment, | ||
| 895 | start: *const fn (context: *const anyopaque, result: *anyopaque) void, | ||
| 896 | ) error{OutOfMemory}!*std.Io.AnyFuture { | ||
| 897 | assert(result_alignment.compare(.lte, Fiber.max_result_align)); // TODO | ||
| 898 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO | ||
| 899 | assert(result_len <= Fiber.max_result_size); // TODO | ||
| 900 | assert(context.len <= Fiber.max_context_size); // TODO | ||
| 901 | |||
| 902 | const event_loop: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 903 | const fiber = try Fiber.allocate(event_loop); | ||
| 904 | std.log.debug("allocated {*}", .{fiber}); | ||
| 905 | |||
| 906 | const closure: *AsyncClosure = .fromFiber(fiber); | ||
| 907 | const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure)); | ||
| 908 | (stack_end - 1)[0..1].* = .{@intFromPtr(&AsyncClosure.call)}; | ||
| 909 | fiber.* = .{ | ||
| 910 | .required_align = {}, | ||
| 911 | .context = switch (builtin.cpu.arch) { | ||
| 912 | .x86_64 => .{ | ||
| 913 | .rsp = @intFromPtr(stack_end - 1), | ||
| 914 | .rbp = 0, | ||
| 915 | .rip = @intFromPtr(&fiberEntry), | ||
| 916 | }, | ||
| 917 | .aarch64 => .{ | ||
| 918 | .sp = @intFromPtr(stack_end), | ||
| 919 | .fp = 0, | ||
| 920 | .pc = @intFromPtr(&fiberEntry), | ||
| 921 | }, | ||
| 922 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 923 | }, | ||
| 924 | .awaiter = null, | ||
| 925 | .queue_next = null, | ||
| 926 | .cancel_thread = null, | ||
| 927 | .awaiting_completions = .initEmpty(), | ||
| 928 | }; | ||
| 929 | closure.* = .{ | ||
| 930 | .event_loop = event_loop, | ||
| 931 | .fiber = fiber, | ||
| 932 | .start = start, | ||
| 933 | .result_align = result_alignment, | ||
| 934 | .already_awaited = false, | ||
| 935 | }; | ||
| 936 | @memcpy(closure.contextPointer(), context); | ||
| 937 | |||
| 938 | event_loop.schedule(.current(), .{ .head = fiber, .tail = fiber }); | ||
| 939 | return @ptrCast(fiber); | ||
| 940 | } | ||
| 941 | |||
| 942 | const DetachedClosure = struct { | ||
| 943 | event_loop: *EventLoop, | ||
| 944 | fiber: *Fiber, | ||
| 945 | start: *const fn (context: *const anyopaque) void, | ||
| 946 | detached_queue_node: std.DoublyLinkedList.Node, | ||
| 947 | |||
| 948 | fn contextPointer(closure: *DetachedClosure) [*]align(Fiber.max_context_align.toByteUnits()) u8 { | ||
| 949 | return @alignCast(@as([*]u8, @ptrCast(closure)) + @sizeOf(DetachedClosure)); | ||
| 950 | } | ||
| 951 | |||
| 952 | fn call(closure: *DetachedClosure, message: *const SwitchMessage) callconv(.withStackAlign(.c, @alignOf(DetachedClosure))) noreturn { | ||
| 953 | message.handle(closure.event_loop); | ||
| 954 | std.log.debug("{*} performing async detached", .{closure.fiber}); | ||
| 955 | closure.start(closure.contextPointer()); | ||
| 956 | const awaiter = @atomicRmw(?*Fiber, &closure.fiber.awaiter, .Xchg, Fiber.finished, .acq_rel); | ||
| 957 | closure.event_loop.yield(awaiter, pending_task: { | ||
| 958 | closure.event_loop.detached.mutex.lock(closure.event_loop.io()) catch |err| switch (err) { | ||
| 959 | error.Canceled => break :pending_task .nothing, | ||
| 960 | }; | ||
| 961 | defer closure.event_loop.detached.mutex.unlock(closure.event_loop.io()); | ||
| 962 | if (closure.detached_queue_node.next == &closure.detached_queue_node) break :pending_task .nothing; | ||
| 963 | closure.event_loop.detached.list.remove(&closure.detached_queue_node); | ||
| 964 | break :pending_task .recycle; | ||
| 965 | }); | ||
| 966 | unreachable; // switched to dead fiber | ||
| 967 | } | ||
| 968 | }; | ||
| 969 | |||
| 970 | fn asyncDetached( | ||
| 971 | userdata: ?*anyopaque, | ||
| 972 | context: []const u8, | ||
| 973 | context_alignment: std.mem.Alignment, | ||
| 974 | start: *const fn (context: *const anyopaque) void, | ||
| 975 | ) void { | ||
| 976 | assert(context_alignment.compare(.lte, Fiber.max_context_align)); // TODO | ||
| 977 | assert(context.len <= Fiber.max_context_size); // TODO | ||
| 978 | |||
| 979 | const event_loop: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 980 | const fiber = Fiber.allocate(event_loop) catch { | ||
| 981 | start(context.ptr); | ||
| 982 | return; | ||
| 983 | }; | ||
| 984 | std.log.debug("allocated {*}", .{fiber}); | ||
| 985 | |||
| 986 | const current_thread: *Thread = .current(); | ||
| 987 | const closure: *DetachedClosure = @ptrFromInt(Fiber.max_context_align.max(.of(DetachedClosure)).backward( | ||
| 988 | @intFromPtr(fiber.allocatedEnd()) - Fiber.max_context_size, | ||
| 989 | ) - @sizeOf(DetachedClosure)); | ||
| 990 | const stack_end: [*]align(16) usize = @ptrCast(@alignCast(closure)); | ||
| 991 | (stack_end - 1)[0..1].* = .{@intFromPtr(&DetachedClosure.call)}; | ||
| 992 | fiber.* = .{ | ||
| 993 | .required_align = {}, | ||
| 994 | .context = switch (builtin.cpu.arch) { | ||
| 995 | .x86_64 => .{ | ||
| 996 | .rsp = @intFromPtr(stack_end - 1), | ||
| 997 | .rbp = 0, | ||
| 998 | .rip = @intFromPtr(&fiberEntry), | ||
| 999 | }, | ||
| 1000 | .aarch64 => .{ | ||
| 1001 | .sp = @intFromPtr(stack_end), | ||
| 1002 | .fp = 0, | ||
| 1003 | .pc = @intFromPtr(&fiberEntry), | ||
| 1004 | }, | ||
| 1005 | else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)), | ||
| 1006 | }, | ||
| 1007 | .awaiter = null, | ||
| 1008 | .queue_next = null, | ||
| 1009 | .cancel_thread = null, | ||
| 1010 | .awaiting_completions = .initEmpty(), | ||
| 1011 | }; | ||
| 1012 | closure.* = .{ | ||
| 1013 | .event_loop = event_loop, | ||
| 1014 | .fiber = fiber, | ||
| 1015 | .start = start, | ||
| 1016 | .detached_queue_node = .{}, | ||
| 1017 | }; | ||
| 1018 | { | ||
| 1019 | event_loop.detached.mutex.lock(event_loop.io()) catch |err| switch (err) { | ||
| 1020 | error.Canceled => { | ||
| 1021 | event_loop.recycle(fiber); | ||
| 1022 | start(context.ptr); | ||
| 1023 | return; | ||
| 1024 | }, | ||
| 1025 | }; | ||
| 1026 | defer event_loop.detached.mutex.unlock(event_loop.io()); | ||
| 1027 | event_loop.detached.list.append(&closure.detached_queue_node); | ||
| 1028 | } | ||
| 1029 | @memcpy(closure.contextPointer(), context); | ||
| 1030 | |||
| 1031 | event_loop.schedule(current_thread, .{ .head = fiber, .tail = fiber }); | ||
| 1032 | } | ||
| 1033 | |||
| 1034 | fn await( | ||
| 1035 | userdata: ?*anyopaque, | ||
| 1036 | any_future: *std.Io.AnyFuture, | ||
| 1037 | result: []u8, | ||
| 1038 | result_alignment: Alignment, | ||
| 1039 | ) void { | ||
| 1040 | const event_loop: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1041 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1042 | if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) != Fiber.finished) | ||
| 1043 | event_loop.yield(null, .{ .register_awaiter = &future_fiber.awaiter }); | ||
| 1044 | @memcpy(result, future_fiber.resultBytes(result_alignment)); | ||
| 1045 | event_loop.recycle(future_fiber); | ||
| 1046 | } | ||
| 1047 | |||
| 1048 | fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) usize { | ||
| 1049 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1050 | |||
| 1051 | // Optimization to avoid the yield below. | ||
| 1052 | for (futures, 0..) |any_future, i| { | ||
| 1053 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1054 | if (@atomicLoad(?*Fiber, &future_fiber.awaiter, .acquire) == Fiber.finished) | ||
| 1055 | return i; | ||
| 1056 | } | ||
| 1057 | |||
| 1058 | el.yield(null, .{ .register_select = futures }); | ||
| 1059 | |||
| 1060 | std.log.debug("back from select yield", .{}); | ||
| 1061 | |||
| 1062 | const my_thread: *Thread = .current(); | ||
| 1063 | const my_fiber = my_thread.currentFiber(); | ||
| 1064 | var result: ?usize = null; | ||
| 1065 | |||
| 1066 | for (futures, 0..) |any_future, i| { | ||
| 1067 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1068 | if (@cmpxchgStrong(?*Fiber, &future_fiber.awaiter, my_fiber, null, .seq_cst, .seq_cst)) |awaiter| { | ||
| 1069 | if (awaiter == Fiber.finished) { | ||
| 1070 | if (result == null) result = i; | ||
| 1071 | } else if (awaiter) |a| { | ||
| 1072 | const closure: *AsyncClosure = .fromFiber(a); | ||
| 1073 | closure.already_awaited = false; | ||
| 1074 | } | ||
| 1075 | } else { | ||
| 1076 | const closure: *AsyncClosure = .fromFiber(my_fiber); | ||
| 1077 | closure.already_awaited = false; | ||
| 1078 | } | ||
| 1079 | } | ||
| 1080 | |||
| 1081 | return result.?; | ||
| 1082 | } | ||
| 1083 | |||
| 1084 | fn cancel( | ||
| 1085 | userdata: ?*anyopaque, | ||
| 1086 | any_future: *std.Io.AnyFuture, | ||
| 1087 | result: []u8, | ||
| 1088 | result_alignment: Alignment, | ||
| 1089 | ) void { | ||
| 1090 | const future_fiber: *Fiber = @ptrCast(@alignCast(any_future)); | ||
| 1091 | if (@atomicRmw( | ||
| 1092 | ?*Thread, | ||
| 1093 | &future_fiber.cancel_thread, | ||
| 1094 | .Xchg, | ||
| 1095 | Thread.canceling, | ||
| 1096 | .acq_rel, | ||
| 1097 | )) |cancel_thread| if (cancel_thread != Thread.canceling) { | ||
| 1098 | getSqe(&Thread.current().io_uring).* = .{ | ||
| 1099 | .opcode = .MSG_RING, | ||
| 1100 | .flags = std.os.linux.IOSQE_CQE_SKIP_SUCCESS, | ||
| 1101 | .ioprio = 0, | ||
| 1102 | .fd = cancel_thread.io_uring.fd, | ||
| 1103 | .off = @intFromPtr(future_fiber), | ||
| 1104 | .addr = 0, | ||
| 1105 | .len = @bitCast(-@as(i32, @intFromEnum(std.os.linux.E.INTR))), | ||
| 1106 | .rw_flags = 0, | ||
| 1107 | .user_data = @intFromEnum(Completion.UserData.cleanup), | ||
| 1108 | .buf_index = 0, | ||
| 1109 | .personality = 0, | ||
| 1110 | .splice_fd_in = 0, | ||
| 1111 | .addr3 = 0, | ||
| 1112 | .resv = 0, | ||
| 1113 | }; | ||
| 1114 | }; | ||
| 1115 | await(userdata, any_future, result, result_alignment); | ||
| 1116 | } | ||
| 1117 | |||
| 1118 | fn cancelRequested(userdata: ?*anyopaque) bool { | ||
| 1119 | _ = userdata; | ||
| 1120 | return @atomicLoad(?*Thread, &Thread.current().currentFiber().cancel_thread, .acquire) == Thread.canceling; | ||
| 1121 | } | ||
| 1122 | |||
| 1123 | fn createFile( | ||
| 1124 | userdata: ?*anyopaque, | ||
| 1125 | dir: Io.Dir, | ||
| 1126 | sub_path: []const u8, | ||
| 1127 | flags: Io.File.CreateFlags, | ||
| 1128 | ) Io.File.OpenError!Io.File { | ||
| 1129 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1130 | const thread: *Thread = .current(); | ||
| 1131 | const iou = &thread.io_uring; | ||
| 1132 | const fiber = thread.currentFiber(); | ||
| 1133 | try fiber.enterCancelRegion(thread); | ||
| 1134 | |||
| 1135 | const posix = std.posix; | ||
| 1136 | const sub_path_c = try posix.toPosixPath(sub_path); | ||
| 1137 | |||
| 1138 | var os_flags: posix.O = .{ | ||
| 1139 | .ACCMODE = if (flags.read) .RDWR else .WRONLY, | ||
| 1140 | .CREAT = true, | ||
| 1141 | .TRUNC = flags.truncate, | ||
| 1142 | .EXCL = flags.exclusive, | ||
| 1143 | }; | ||
| 1144 | if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; | ||
| 1145 | if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; | ||
| 1146 | |||
| 1147 | // Use the O locking flags if the os supports them to acquire the lock | ||
| 1148 | // atomically. Note that the NONBLOCK flag is removed after the openat() | ||
| 1149 | // call is successful. | ||
| 1150 | const has_flock_open_flags = @hasField(posix.O, "EXLOCK"); | ||
| 1151 | if (has_flock_open_flags) switch (flags.lock) { | ||
| 1152 | .none => {}, | ||
| 1153 | .shared => { | ||
| 1154 | os_flags.SHLOCK = true; | ||
| 1155 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1156 | }, | ||
| 1157 | .exclusive => { | ||
| 1158 | os_flags.EXLOCK = true; | ||
| 1159 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1160 | }, | ||
| 1161 | }; | ||
| 1162 | const have_flock = @TypeOf(posix.system.flock) != void; | ||
| 1163 | |||
| 1164 | if (have_flock and !has_flock_open_flags and flags.lock != .none) { | ||
| 1165 | @panic("TODO"); | ||
| 1166 | } | ||
| 1167 | |||
| 1168 | if (has_flock_open_flags and flags.lock_nonblocking) { | ||
| 1169 | @panic("TODO"); | ||
| 1170 | } | ||
| 1171 | |||
| 1172 | getSqe(iou).* = .{ | ||
| 1173 | .opcode = .OPENAT, | ||
| 1174 | .flags = 0, | ||
| 1175 | .ioprio = 0, | ||
| 1176 | .fd = dir.handle, | ||
| 1177 | .off = 0, | ||
| 1178 | .addr = @intFromPtr(&sub_path_c), | ||
| 1179 | .len = @intCast(flags.mode), | ||
| 1180 | .rw_flags = @bitCast(os_flags), | ||
| 1181 | .user_data = @intFromPtr(fiber), | ||
| 1182 | .buf_index = 0, | ||
| 1183 | .personality = 0, | ||
| 1184 | .splice_fd_in = 0, | ||
| 1185 | .addr3 = 0, | ||
| 1186 | .resv = 0, | ||
| 1187 | }; | ||
| 1188 | |||
| 1189 | el.yield(null, .nothing); | ||
| 1190 | fiber.exitCancelRegion(thread); | ||
| 1191 | |||
| 1192 | const completion = fiber.resultPointer(Completion); | ||
| 1193 | switch (errno(completion.result)) { | ||
| 1194 | .SUCCESS => return .{ .handle = completion.result }, | ||
| 1195 | .INTR => unreachable, | ||
| 1196 | .CANCELED => return error.Canceled, | ||
| 1197 | |||
| 1198 | .FAULT => unreachable, | ||
| 1199 | .INVAL => return error.BadPathName, | ||
| 1200 | .BADF => unreachable, | ||
| 1201 | .ACCES => return error.AccessDenied, | ||
| 1202 | .FBIG => return error.FileTooBig, | ||
| 1203 | .OVERFLOW => return error.FileTooBig, | ||
| 1204 | .ISDIR => return error.IsDir, | ||
| 1205 | .LOOP => return error.SymLinkLoop, | ||
| 1206 | .MFILE => return error.ProcessFdQuotaExceeded, | ||
| 1207 | .NAMETOOLONG => return error.NameTooLong, | ||
| 1208 | .NFILE => return error.SystemFdQuotaExceeded, | ||
| 1209 | .NODEV => return error.NoDevice, | ||
| 1210 | .NOENT => return error.FileNotFound, | ||
| 1211 | .NOMEM => return error.SystemResources, | ||
| 1212 | .NOSPC => return error.NoSpaceLeft, | ||
| 1213 | .NOTDIR => return error.NotDir, | ||
| 1214 | .PERM => return error.PermissionDenied, | ||
| 1215 | .EXIST => return error.PathAlreadyExists, | ||
| 1216 | .BUSY => return error.DeviceBusy, | ||
| 1217 | .OPNOTSUPP => return error.FileLocksNotSupported, | ||
| 1218 | .AGAIN => return error.WouldBlock, | ||
| 1219 | .TXTBSY => return error.FileBusy, | ||
| 1220 | .NXIO => return error.NoDevice, | ||
| 1221 | else => |err| return posix.unexpectedErrno(err), | ||
| 1222 | } | ||
| 1223 | } | ||
| 1224 | |||
| 1225 | fn fileOpen( | ||
| 1226 | userdata: ?*anyopaque, | ||
| 1227 | dir: Io.Dir, | ||
| 1228 | sub_path: []const u8, | ||
| 1229 | flags: Io.File.OpenFlags, | ||
| 1230 | ) Io.File.OpenError!Io.File { | ||
| 1231 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1232 | const thread: *Thread = .current(); | ||
| 1233 | const iou = &thread.io_uring; | ||
| 1234 | const fiber = thread.currentFiber(); | ||
| 1235 | try fiber.enterCancelRegion(thread); | ||
| 1236 | |||
| 1237 | const posix = std.posix; | ||
| 1238 | const sub_path_c = try posix.toPosixPath(sub_path); | ||
| 1239 | |||
| 1240 | var os_flags: posix.O = .{ | ||
| 1241 | .ACCMODE = switch (flags.mode) { | ||
| 1242 | .read_only => .RDONLY, | ||
| 1243 | .write_only => .WRONLY, | ||
| 1244 | .read_write => .RDWR, | ||
| 1245 | }, | ||
| 1246 | }; | ||
| 1247 | |||
| 1248 | if (@hasField(posix.O, "CLOEXEC")) os_flags.CLOEXEC = true; | ||
| 1249 | if (@hasField(posix.O, "LARGEFILE")) os_flags.LARGEFILE = true; | ||
| 1250 | if (@hasField(posix.O, "NOCTTY")) os_flags.NOCTTY = !flags.allow_ctty; | ||
| 1251 | |||
| 1252 | // Use the O locking flags if the os supports them to acquire the lock | ||
| 1253 | // atomically. | ||
| 1254 | const has_flock_open_flags = @hasField(posix.O, "EXLOCK"); | ||
| 1255 | if (has_flock_open_flags) { | ||
| 1256 | // Note that the NONBLOCK flag is removed after the openat() call | ||
| 1257 | // is successful. | ||
| 1258 | switch (flags.lock) { | ||
| 1259 | .none => {}, | ||
| 1260 | .shared => { | ||
| 1261 | os_flags.SHLOCK = true; | ||
| 1262 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1263 | }, | ||
| 1264 | .exclusive => { | ||
| 1265 | os_flags.EXLOCK = true; | ||
| 1266 | os_flags.NONBLOCK = flags.lock_nonblocking; | ||
| 1267 | }, | ||
| 1268 | } | ||
| 1269 | } | ||
| 1270 | const have_flock = @TypeOf(posix.system.flock) != void; | ||
| 1271 | |||
| 1272 | if (have_flock and !has_flock_open_flags and flags.lock != .none) { | ||
| 1273 | @panic("TODO"); | ||
| 1274 | } | ||
| 1275 | |||
| 1276 | if (has_flock_open_flags and flags.lock_nonblocking) { | ||
| 1277 | @panic("TODO"); | ||
| 1278 | } | ||
| 1279 | |||
| 1280 | getSqe(iou).* = .{ | ||
| 1281 | .opcode = .OPENAT, | ||
| 1282 | .flags = 0, | ||
| 1283 | .ioprio = 0, | ||
| 1284 | .fd = dir.handle, | ||
| 1285 | .off = 0, | ||
| 1286 | .addr = @intFromPtr(&sub_path_c), | ||
| 1287 | .len = 0, | ||
| 1288 | .rw_flags = @bitCast(os_flags), | ||
| 1289 | .user_data = @intFromPtr(fiber), | ||
| 1290 | .buf_index = 0, | ||
| 1291 | .personality = 0, | ||
| 1292 | .splice_fd_in = 0, | ||
| 1293 | .addr3 = 0, | ||
| 1294 | .resv = 0, | ||
| 1295 | }; | ||
| 1296 | |||
| 1297 | el.yield(null, .nothing); | ||
| 1298 | fiber.exitCancelRegion(thread); | ||
| 1299 | |||
| 1300 | const completion = fiber.resultPointer(Completion); | ||
| 1301 | switch (errno(completion.result)) { | ||
| 1302 | .SUCCESS => return .{ .handle = completion.result }, | ||
| 1303 | .INTR => unreachable, | ||
| 1304 | .CANCELED => return error.Canceled, | ||
| 1305 | |||
| 1306 | .FAULT => unreachable, | ||
| 1307 | .INVAL => return error.BadPathName, | ||
| 1308 | .BADF => unreachable, | ||
| 1309 | .ACCES => return error.AccessDenied, | ||
| 1310 | .FBIG => return error.FileTooBig, | ||
| 1311 | .OVERFLOW => return error.FileTooBig, | ||
| 1312 | .ISDIR => return error.IsDir, | ||
| 1313 | .LOOP => return error.SymLinkLoop, | ||
| 1314 | .MFILE => return error.ProcessFdQuotaExceeded, | ||
| 1315 | .NAMETOOLONG => return error.NameTooLong, | ||
| 1316 | .NFILE => return error.SystemFdQuotaExceeded, | ||
| 1317 | .NODEV => return error.NoDevice, | ||
| 1318 | .NOENT => return error.FileNotFound, | ||
| 1319 | .NOMEM => return error.SystemResources, | ||
| 1320 | .NOSPC => return error.NoSpaceLeft, | ||
| 1321 | .NOTDIR => return error.NotDir, | ||
| 1322 | .PERM => return error.PermissionDenied, | ||
| 1323 | .EXIST => return error.PathAlreadyExists, | ||
| 1324 | .BUSY => return error.DeviceBusy, | ||
| 1325 | .OPNOTSUPP => return error.FileLocksNotSupported, | ||
| 1326 | .AGAIN => return error.WouldBlock, | ||
| 1327 | .TXTBSY => return error.FileBusy, | ||
| 1328 | .NXIO => return error.NoDevice, | ||
| 1329 | else => |err| return posix.unexpectedErrno(err), | ||
| 1330 | } | ||
| 1331 | } | ||
| 1332 | |||
| 1333 | fn fileClose(userdata: ?*anyopaque, file: Io.File) void { | ||
| 1334 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1335 | const thread: *Thread = .current(); | ||
| 1336 | const iou = &thread.io_uring; | ||
| 1337 | const fiber = thread.currentFiber(); | ||
| 1338 | |||
| 1339 | getSqe(iou).* = .{ | ||
| 1340 | .opcode = .CLOSE, | ||
| 1341 | .flags = 0, | ||
| 1342 | .ioprio = 0, | ||
| 1343 | .fd = file.handle, | ||
| 1344 | .off = 0, | ||
| 1345 | .addr = 0, | ||
| 1346 | .len = 0, | ||
| 1347 | .rw_flags = 0, | ||
| 1348 | .user_data = @intFromPtr(fiber), | ||
| 1349 | .buf_index = 0, | ||
| 1350 | .personality = 0, | ||
| 1351 | .splice_fd_in = 0, | ||
| 1352 | .addr3 = 0, | ||
| 1353 | .resv = 0, | ||
| 1354 | }; | ||
| 1355 | |||
| 1356 | el.yield(null, .nothing); | ||
| 1357 | |||
| 1358 | const completion = fiber.resultPointer(Completion); | ||
| 1359 | switch (errno(completion.result)) { | ||
| 1360 | .SUCCESS => return, | ||
| 1361 | .INTR => unreachable, | ||
| 1362 | .CANCELED => return, | ||
| 1363 | |||
| 1364 | .BADF => unreachable, // Always a race condition. | ||
| 1365 | else => return, | ||
| 1366 | } | ||
| 1367 | } | ||
| 1368 | |||
| 1369 | fn pread(userdata: ?*anyopaque, file: Io.File, buffer: []u8, offset: std.posix.off_t) Io.File.PReadError!usize { | ||
| 1370 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1371 | const thread: *Thread = .current(); | ||
| 1372 | const iou = &thread.io_uring; | ||
| 1373 | const fiber = thread.currentFiber(); | ||
| 1374 | try fiber.enterCancelRegion(thread); | ||
| 1375 | |||
| 1376 | getSqe(iou).* = .{ | ||
| 1377 | .opcode = .READ, | ||
| 1378 | .flags = 0, | ||
| 1379 | .ioprio = 0, | ||
| 1380 | .fd = file.handle, | ||
| 1381 | .off = @bitCast(offset), | ||
| 1382 | .addr = @intFromPtr(buffer.ptr), | ||
| 1383 | .len = @min(buffer.len, 0x7ffff000), | ||
| 1384 | .rw_flags = 0, | ||
| 1385 | .user_data = @intFromPtr(fiber), | ||
| 1386 | .buf_index = 0, | ||
| 1387 | .personality = 0, | ||
| 1388 | .splice_fd_in = 0, | ||
| 1389 | .addr3 = 0, | ||
| 1390 | .resv = 0, | ||
| 1391 | }; | ||
| 1392 | |||
| 1393 | el.yield(null, .nothing); | ||
| 1394 | fiber.exitCancelRegion(thread); | ||
| 1395 | |||
| 1396 | const completion = fiber.resultPointer(Completion); | ||
| 1397 | switch (errno(completion.result)) { | ||
| 1398 | .SUCCESS => return @as(u32, @bitCast(completion.result)), | ||
| 1399 | .INTR => unreachable, | ||
| 1400 | .CANCELED => return error.Canceled, | ||
| 1401 | |||
| 1402 | .INVAL => unreachable, | ||
| 1403 | .FAULT => unreachable, | ||
| 1404 | .NOENT => return error.ProcessNotFound, | ||
| 1405 | .AGAIN => return error.WouldBlock, | ||
| 1406 | .BADF => return error.NotOpenForReading, // Can be a race condition. | ||
| 1407 | .IO => return error.InputOutput, | ||
| 1408 | .ISDIR => return error.IsDir, | ||
| 1409 | .NOBUFS => return error.SystemResources, | ||
| 1410 | .NOMEM => return error.SystemResources, | ||
| 1411 | .NOTCONN => return error.SocketUnconnected, | ||
| 1412 | .CONNRESET => return error.ConnectionResetByPeer, | ||
| 1413 | .TIMEDOUT => return error.Timeout, | ||
| 1414 | .NXIO => return error.Unseekable, | ||
| 1415 | .SPIPE => return error.Unseekable, | ||
| 1416 | .OVERFLOW => return error.Unseekable, | ||
| 1417 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 1418 | } | ||
| 1419 | } | ||
| 1420 | |||
| 1421 | fn pwrite(userdata: ?*anyopaque, file: Io.File, buffer: []const u8, offset: std.posix.off_t) Io.File.PWriteError!usize { | ||
| 1422 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1423 | const thread: *Thread = .current(); | ||
| 1424 | const iou = &thread.io_uring; | ||
| 1425 | const fiber = thread.currentFiber(); | ||
| 1426 | try fiber.enterCancelRegion(thread); | ||
| 1427 | |||
| 1428 | getSqe(iou).* = .{ | ||
| 1429 | .opcode = .WRITE, | ||
| 1430 | .flags = 0, | ||
| 1431 | .ioprio = 0, | ||
| 1432 | .fd = file.handle, | ||
| 1433 | .off = @bitCast(offset), | ||
| 1434 | .addr = @intFromPtr(buffer.ptr), | ||
| 1435 | .len = @min(buffer.len, 0x7ffff000), | ||
| 1436 | .rw_flags = 0, | ||
| 1437 | .user_data = @intFromPtr(fiber), | ||
| 1438 | .buf_index = 0, | ||
| 1439 | .personality = 0, | ||
| 1440 | .splice_fd_in = 0, | ||
| 1441 | .addr3 = 0, | ||
| 1442 | .resv = 0, | ||
| 1443 | }; | ||
| 1444 | |||
| 1445 | el.yield(null, .nothing); | ||
| 1446 | fiber.exitCancelRegion(thread); | ||
| 1447 | |||
| 1448 | const completion = fiber.resultPointer(Completion); | ||
| 1449 | switch (errno(completion.result)) { | ||
| 1450 | .SUCCESS => return @as(u32, @bitCast(completion.result)), | ||
| 1451 | .INTR => unreachable, | ||
| 1452 | .CANCELED => return error.Canceled, | ||
| 1453 | |||
| 1454 | .INVAL => return error.InvalidArgument, | ||
| 1455 | .FAULT => unreachable, | ||
| 1456 | .NOENT => return error.ProcessNotFound, | ||
| 1457 | .AGAIN => return error.WouldBlock, | ||
| 1458 | .BADF => return error.NotOpenForWriting, // can be a race condition. | ||
| 1459 | .DESTADDRREQ => unreachable, // `connect` was never called. | ||
| 1460 | .DQUOT => return error.DiskQuota, | ||
| 1461 | .FBIG => return error.FileTooBig, | ||
| 1462 | .IO => return error.InputOutput, | ||
| 1463 | .NOSPC => return error.NoSpaceLeft, | ||
| 1464 | .ACCES => return error.AccessDenied, | ||
| 1465 | .PERM => return error.PermissionDenied, | ||
| 1466 | .PIPE => return error.BrokenPipe, | ||
| 1467 | .NXIO => return error.Unseekable, | ||
| 1468 | .SPIPE => return error.Unseekable, | ||
| 1469 | .OVERFLOW => return error.Unseekable, | ||
| 1470 | .BUSY => return error.DeviceBusy, | ||
| 1471 | .CONNRESET => return error.ConnectionResetByPeer, | ||
| 1472 | .MSGSIZE => return error.MessageTooBig, | ||
| 1473 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 1474 | } | ||
| 1475 | } | ||
| 1476 | |||
| 1477 | fn now(userdata: ?*anyopaque, clockid: std.posix.clockid_t) Io.ClockGetTimeError!Io.Timestamp { | ||
| 1478 | _ = userdata; | ||
| 1479 | const timespec = try std.posix.clock_gettime(clockid); | ||
| 1480 | return @enumFromInt(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec); | ||
| 1481 | } | ||
| 1482 | |||
| 1483 | fn sleep(userdata: ?*anyopaque, clockid: std.posix.clockid_t, deadline: Io.Deadline) Io.SleepError!void { | ||
| 1484 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1485 | const thread: *Thread = .current(); | ||
| 1486 | const iou = &thread.io_uring; | ||
| 1487 | const fiber = thread.currentFiber(); | ||
| 1488 | try fiber.enterCancelRegion(thread); | ||
| 1489 | |||
| 1490 | const deadline_nanoseconds: i96 = switch (deadline) { | ||
| 1491 | .duration => |duration| duration.nanoseconds, | ||
| 1492 | .timestamp => |timestamp| @intFromEnum(timestamp), | ||
| 1493 | }; | ||
| 1494 | const timespec: std.os.linux.kernel_timespec = .{ | ||
| 1495 | .sec = @intCast(@divFloor(deadline_nanoseconds, std.time.ns_per_s)), | ||
| 1496 | .nsec = @intCast(@mod(deadline_nanoseconds, std.time.ns_per_s)), | ||
| 1497 | }; | ||
| 1498 | getSqe(iou).* = .{ | ||
| 1499 | .opcode = .TIMEOUT, | ||
| 1500 | .flags = 0, | ||
| 1501 | .ioprio = 0, | ||
| 1502 | .fd = 0, | ||
| 1503 | .off = 0, | ||
| 1504 | .addr = @intFromPtr(&timespec), | ||
| 1505 | .len = 1, | ||
| 1506 | .rw_flags = @as(u32, switch (deadline) { | ||
| 1507 | .duration => 0, | ||
| 1508 | .timestamp => std.os.linux.IORING_TIMEOUT_ABS, | ||
| 1509 | }) | @as(u32, switch (clockid) { | ||
| 1510 | .REALTIME => std.os.linux.IORING_TIMEOUT_REALTIME, | ||
| 1511 | .MONOTONIC => 0, | ||
| 1512 | .BOOTTIME => std.os.linux.IORING_TIMEOUT_BOOTTIME, | ||
| 1513 | else => return error.UnsupportedClock, | ||
| 1514 | }), | ||
| 1515 | .user_data = @intFromPtr(fiber), | ||
| 1516 | .buf_index = 0, | ||
| 1517 | .personality = 0, | ||
| 1518 | .splice_fd_in = 0, | ||
| 1519 | .addr3 = 0, | ||
| 1520 | .resv = 0, | ||
| 1521 | }; | ||
| 1522 | |||
| 1523 | el.yield(null, .nothing); | ||
| 1524 | fiber.exitCancelRegion(thread); | ||
| 1525 | |||
| 1526 | const completion = fiber.resultPointer(Completion); | ||
| 1527 | switch (errno(completion.result)) { | ||
| 1528 | .SUCCESS, .TIME => return, | ||
| 1529 | .INTR => unreachable, | ||
| 1530 | .CANCELED => return error.Canceled, | ||
| 1531 | |||
| 1532 | else => |err| return std.posix.unexpectedErrno(err), | ||
| 1533 | } | ||
| 1534 | } | ||
| 1535 | |||
| 1536 | fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) error{Canceled}!void { | ||
| 1537 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1538 | el.yield(null, .{ .mutex_lock = .{ .prev_state = prev_state, .mutex = mutex } }); | ||
| 1539 | } | ||
| 1540 | fn mutexUnlock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) void { | ||
| 1541 | var maybe_waiting_fiber: ?*Fiber = @ptrFromInt(@intFromEnum(prev_state)); | ||
| 1542 | while (if (maybe_waiting_fiber) |waiting_fiber| @cmpxchgWeak( | ||
| 1543 | Io.Mutex.State, | ||
| 1544 | &mutex.state, | ||
| 1545 | @enumFromInt(@intFromPtr(waiting_fiber)), | ||
| 1546 | @enumFromInt(@intFromPtr(waiting_fiber.queue_next)), | ||
| 1547 | .release, | ||
| 1548 | .acquire, | ||
| 1549 | ) else @cmpxchgWeak( | ||
| 1550 | Io.Mutex.State, | ||
| 1551 | &mutex.state, | ||
| 1552 | .locked_once, | ||
| 1553 | .unlocked, | ||
| 1554 | .release, | ||
| 1555 | .acquire, | ||
| 1556 | ) orelse return) |next_state| maybe_waiting_fiber = @ptrFromInt(@intFromEnum(next_state)); | ||
| 1557 | maybe_waiting_fiber.?.queue_next = null; | ||
| 1558 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1559 | el.yield(maybe_waiting_fiber.?, .reschedule); | ||
| 1560 | } | ||
| 1561 | |||
| 1562 | const ConditionImpl = struct { | ||
| 1563 | tail: *Fiber, | ||
| 1564 | event: union(enum) { | ||
| 1565 | queued, | ||
| 1566 | wake: Io.Condition.Wake, | ||
| 1567 | }, | ||
| 1568 | }; | ||
| 1569 | |||
| 1570 | fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) Io.Cancelable!void { | ||
| 1571 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1572 | el.yield(null, .{ .condition_wait = .{ .cond = cond, .mutex = mutex } }); | ||
| 1573 | const thread = Thread.current(); | ||
| 1574 | const fiber = thread.currentFiber(); | ||
| 1575 | const cond_impl = fiber.resultPointer(ConditionImpl); | ||
| 1576 | try mutex.lock(el.io()); | ||
| 1577 | switch (cond_impl.event) { | ||
| 1578 | .queued => {}, | ||
| 1579 | .wake => |wake| if (fiber.queue_next) |next_fiber| switch (wake) { | ||
| 1580 | .one => if (@cmpxchgStrong( | ||
| 1581 | ?*Fiber, | ||
| 1582 | @as(*?*Fiber, @ptrCast(&cond.state)), | ||
| 1583 | null, | ||
| 1584 | next_fiber, | ||
| 1585 | .release, | ||
| 1586 | .acquire, | ||
| 1587 | )) |old_fiber| { | ||
| 1588 | const old_cond_impl = old_fiber.?.resultPointer(ConditionImpl); | ||
| 1589 | assert(old_cond_impl.tail.queue_next == null); | ||
| 1590 | old_cond_impl.tail.queue_next = next_fiber; | ||
| 1591 | old_cond_impl.tail = cond_impl.tail; | ||
| 1592 | }, | ||
| 1593 | .all => el.schedule(thread, .{ .head = next_fiber, .tail = cond_impl.tail }), | ||
| 1594 | }, | ||
| 1595 | } | ||
| 1596 | fiber.queue_next = null; | ||
| 1597 | } | ||
| 1598 | |||
| 1599 | fn conditionWake(userdata: ?*anyopaque, cond: *Io.Condition, wake: Io.Condition.Wake) void { | ||
| 1600 | const el: *EventLoop = @ptrCast(@alignCast(userdata)); | ||
| 1601 | const waiting_fiber = @atomicRmw(?*Fiber, @as(*?*Fiber, @ptrCast(&cond.state)), .Xchg, null, .acquire) orelse return; | ||
| 1602 | waiting_fiber.resultPointer(ConditionImpl).event = .{ .wake = wake }; | ||
| 1603 | el.yield(waiting_fiber, .reschedule); | ||
| 1604 | } | ||
| 1605 | |||
| 1606 | fn errno(signed: i32) std.os.linux.E { | ||
| 1607 | return .init(@bitCast(@as(isize, signed))); | ||
| 1608 | } | ||
| 1609 | |||
| 1610 | fn getSqe(iou: *IoUring) *std.os.linux.io_uring_sqe { | ||
| 1611 | while (true) return iou.get_sqe() catch { | ||
| 1612 | _ = iou.submit_and_wait(0) catch |err| switch (err) { | ||
| 1613 | error.SignalInterrupt => std.log.warn("submit_and_wait failed with SignalInterrupt", .{}), | ||
| 1614 | else => |e| @panic(@errorName(e)), | ||
| 1615 | }; | ||
| 1616 | continue; | ||
| 1617 | }; | ||
| 1618 | } | ||