authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-02-10 11:05:35-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-10 23:45:48+01:00
log7369008d8ca94fe48d9408d03851a4a17f7e1b73
treeb64ea36b859f86b0120fe9d19d011deb57ce62bd
parente314dadb015a913a2cdc12298986e9181305d8db

Io.IoUring: implement some thread pool options and other cleanup


2 files changed, 666 insertions(+), 478 deletions(-)

lib/std/Io/IoUring.zig+663-474
......@@ -59,7 +59,9 @@ backing_allocator: Allocator,
5959main_fiber_buffer: [
6060 std.mem.alignForward(usize, @sizeOf(Fiber), @alignOf(Completion)) + @sizeOf(Completion)
6161]u8 align(@max(@alignOf(Fiber), @alignOf(Completion))),
62log2_ring_entries: u4,
6263threads: Thread.List,
64sync_limit: ?Io.Semaphore,
6365
6466stderr_mutex: Io.Mutex,
6567stderr_writer: File.Writer = .{
......@@ -84,10 +86,9 @@ csprng: Csprng,
8486/// Empirically saw glibc complain about 256KB.
8587const idle_stack_size = 512 * 1024;
8688
87const max_idle_search = 4;
88const max_steal_ready_search = 4;
89
90const io_uring_entries = 64;
89const max_idle_search = 1;
90const max_steal_ready_search = 2;
91const max_steal_free_search = 4;
9192
9293const Thread = struct {
9394 required_align: void align(4),
......@@ -95,9 +96,11 @@ const Thread = struct {
9596 idle_context: Context,
9697 current_context: *Context,
9798 ready_queue: ?*Fiber,
99 free_queue: ?*Fiber,
98100 io_uring: IoUring,
99101 idle_search_index: u32,
100102 steal_ready_search_index: u32,
103 steal_free_search_index: u32,
101104 name_arena: if (tracy.enable) std.heap.ArenaAllocator.State else struct {},
102105 csprng: Csprng,
103106
......@@ -107,6 +110,15 @@ const Thread = struct {
107110 return self.?;
108111 }
109112
113 fn deinit(thread: *Thread, gpa: Allocator) void {
114 var next_fiber = thread.free_queue;
115 while (next_fiber) |free_fiber| {
116 next_fiber = free_fiber.status.free_next;
117 gpa.free(free_fiber.allocatedSlice());
118 }
119 thread.io_uring.deinit();
120 }
121
110122 fn currentFiber(thread: *Thread) *Fiber {
111123 assert(thread.current_context != &thread.idle_context);
112124 return @fieldParentPtr("context", thread.current_context);
......@@ -144,6 +156,7 @@ const Fiber = struct {
144156 status: union(enum) {
145157 queue_next: ?*Fiber,
146158 awaiting_group: Group,
159 free_next: ?*Fiber,
147160 },
148161 cancel_status: CancelStatus,
149162 cancel_protection: CancelProtection,
......@@ -235,7 +248,7 @@ const Fiber = struct {
235248 }
236249 };
237250
238 const finished: ?*Fiber = @ptrFromInt(@alignOf(Thread));
251 const finished: ?*Fiber = @ptrFromInt(@alignOf(Fiber));
239252
240253 const max_result_align: Alignment = .@"16";
241254 const max_result_size = max_result_align.forward(512);
......@@ -259,13 +272,49 @@ const Fiber = struct {
259272 }
260273
261274 fn create(ev: *Evented) error{OutOfMemory}!*Fiber {
275 const thread: *Thread = .current();
276 if (@atomicRmw(?*Fiber, &thread.free_queue, .Xchg, finished, .acquire)) |free_fiber| {
277 assert(free_fiber != finished);
278 @atomicStore(?*Fiber, &thread.free_queue, free_fiber.status.free_next, .release);
279 return free_fiber;
280 }
281 const active_threads = @atomicLoad(u32, &ev.threads.active, .acquire);
282 for (0..@min(max_steal_free_search, active_threads)) |_| {
283 defer thread.steal_free_search_index += 1;
284 if (thread.steal_free_search_index == active_threads) thread.steal_free_search_index = 0;
285 const steal_free_search_thread =
286 &ev.threads.allocated[0..active_threads][thread.steal_free_search_index];
287 if (steal_free_search_thread == thread) continue;
288 const free_fiber =
289 @atomicLoad(?*Fiber, &steal_free_search_thread.free_queue, .monotonic) orelse continue;
290 if (free_fiber == finished) continue;
291 if (@cmpxchgWeak(
292 ?*Fiber,
293 &steal_free_search_thread.free_queue,
294 free_fiber,
295 null,
296 .acquire,
297 .monotonic,
298 )) |_| continue;
299 @atomicStore(?*Fiber, &thread.free_queue, free_fiber.status.free_next, .release);
300 return free_fiber;
301 }
302 @atomicStore(?*Fiber, &thread.free_queue, null, .monotonic);
262303 return @ptrCast(try ev.allocator().alignedAlloc(u8, .of(Fiber), allocation_size));
263304 }
264305
265 fn destroy(fiber: *Fiber, gpa: std.mem.Allocator) void {
266 log.debug("destroying {*}", .{fiber});
306 fn destroy(fiber: *Fiber) void {
307 const thread: *Thread = .current();
267308 assert(fiber.status.queue_next == null);
268 gpa.free(fiber.allocatedSlice());
309 fiber.status = .{ .free_next = @atomicLoad(?*Fiber, &thread.free_queue, .acquire) };
310 while (true) fiber.status.free_next = @cmpxchgWeak(
311 ?*Fiber,
312 &thread.free_queue,
313 fiber.status.free_next,
314 fiber,
315 .acq_rel,
316 .acquire,
317 ) orelse break;
269318 }
270319
271320 fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 {
......@@ -416,6 +465,62 @@ const CancelRegion = struct {
416465 fn errno(cancel_region: *const CancelRegion) linux.E {
417466 return cancel_region.completion().errno();
418467 }
468
469 const Sync = struct {
470 cancel_region: CancelRegion,
471 fn init(ev: *Evented) Io.Cancelable!Sync {
472 if (ev.sync_limit) |*sync_limit| try sync_limit.wait(ev.io());
473 return .{ .cancel_region = .init() };
474 }
475 fn initBlocked(ev: *Evented) Sync {
476 if (ev.sync_limit) |*sync_limit| sync_limit.waitUncancelable(ev.io());
477 return .{ .cancel_region = .initBlocked() };
478 }
479 fn deinit(sync: *Sync, ev: *Evented) void {
480 sync.cancel_region.deinit();
481 if (ev.sync_limit) |*sync_limit| sync_limit.post(ev.io());
482 }
483
484 const Maybe = union(enum) {
485 cancel_region: CancelRegion,
486 sync: Sync,
487
488 fn deinit(maybe: *Maybe, ev: *Evented) void {
489 switch (maybe.*) {
490 .cancel_region => |*cancel_region| cancel_region.deinit(),
491 .sync => |*sync| sync.deinit(ev),
492 }
493 }
494
495 fn enterSync(maybe: *Maybe, ev: *Evented) Io.Cancelable!*Sync {
496 switch (maybe.*) {
497 .cancel_region => |cancel_region| {
498 if (ev.sync_limit) |*sync_limit| try sync_limit.wait(ev.io());
499 maybe.* = .{ .sync = .{ .cancel_region = cancel_region } };
500 },
501 .sync => {},
502 }
503 return &maybe.sync;
504 }
505
506 fn leaveSync(maybe: *Maybe, ev: *Evented) void {
507 switch (maybe.*) {
508 .cancel_region => {},
509 .sync => |sync| {
510 if (ev.sync_limit) |*sync_limit| sync_limit.post(ev.io());
511 maybe.* = .{ .cancel_region = sync.cancel_region };
512 },
513 }
514 }
515
516 fn cancelRegion(maybe: *Maybe) *CancelRegion {
517 return switch (maybe.*) {
518 .cancel_region => |*cancel_region| cancel_region,
519 .sync => |*sync| &sync.cancel_region,
520 };
521 }
522 };
523 };
419524};
420525
421526const CachedFd = struct {
......@@ -685,7 +790,7 @@ fn fileMemoryMapSetLength(
685790 new_len: usize,
686791) File.MemoryMap.SetLengthError!void {
687792 const ev: *Evented = @ptrCast(@alignCast(userdata));
688 _ = ev;
793
689794 const page_size = std.heap.pageSize();
690795 const alignment: Alignment = .fromByteUnits(page_size);
691796 const page_align = std.heap.page_size_min;
......@@ -695,12 +800,12 @@ fn fileMemoryMapSetLength(
695800 mm.memory.len = new_len;
696801 return;
697802 }
698 var cancel_region: CancelRegion = .init();
699 defer cancel_region.deinit();
700803 const flags: linux.MREMAP = .{ .MAYMOVE = true };
701804 const addr_hint: ?[*]const u8 = null;
805 var sync: CancelRegion.Sync = try .init(ev);
806 defer sync.deinit(ev);
702807 const new_memory = while (true) {
703 try cancel_region.await(.nothing);
808 try sync.cancel_region.await(.nothing);
704809 const rc = linux.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);
705810 switch (linux.errno(rc)) {
706811 .SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len],
......@@ -730,17 +835,28 @@ fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePosi
730835pub const InitOptions = struct {
731836 backing_allocator_needs_mutex: bool = true,
732837
838 /// Maximum thread pool size (excluding the main thread).
839 /// Defaults to one less than the number of logical CPU cores.
840 thread_limit: ?usize = null,
841 /// Maximum number of threads that may perform synchronous syscalls.
842 sync_limit: Io.Limit = .unlimited,
843
844 log2_ring_entries: u4 = 3,
845
733846 /// Affects the following operations:
734847 /// * `processExecutablePath` on OpenBSD and Haiku.
735848 argv0: Argv0 = .empty,
736849 /// Affects the following operations:
737850 /// * `fileIsTty`
738851 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
739 environ: process.Environ,
852 environ: process.Environ = .empty,
740853};
741854
742855pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !void {
743 const threads_size = @max(std.Thread.getCpuCount() catch 1, 1) * @sizeOf(Thread);
856 const threads_size = @sizeOf(Thread) * if (options.thread_limit) |thread_limit|
857 1 + thread_limit
858 else
859 @max(std.Thread.getCpuCount() catch 1, 1);
744860 const idle_stack_end_offset =
745861 std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max);
746862 const allocated_slice = try backing_allocator.alignedAlloc(u8, .of(Thread), idle_stack_end_offset);
......@@ -750,11 +866,13 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
750866 .backing_allocator_mutex = .init,
751867 .backing_allocator = backing_allocator,
752868 .main_fiber_buffer = undefined,
869 .log2_ring_entries = options.log2_ring_entries,
753870 .threads = .{
754871 .allocated = @ptrCast(allocated_slice[0..threads_size]),
755872 .reserved = 1,
756873 .active = 1,
757874 },
875 .sync_limit = if (options.sync_limit.toInt()) |sync_limit| .{ .permits = sync_limit } else null,
758876
759877 .stderr_mutex = .init,
760878 .stderr_writer = .{
......@@ -809,18 +927,18 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
809927 },
810928 .current_context = &main_fiber.context,
811929 .ready_queue = null,
930 .free_queue = null,
812931 .io_uring = try .init(
813 io_uring_entries,
932 @as(u16, 1) << ev.log2_ring_entries,
814933 linux.IORING_SETUP_COOP_TASKRUN | linux.IORING_SETUP_SINGLE_ISSUER,
815934 ),
816935 .idle_search_index = 1,
817936 .steal_ready_search_index = 1,
937 .steal_free_search_index = 1,
818938 .name_arena = .{},
819939 .csprng = .uninitialized,
820940 };
821941 errdefer main_thread.io_uring.deinit();
822 log.debug("created main idle {*}", .{&main_thread.idle_context});
823 log.debug("created main {*}", .{main_fiber});
824942 if (tracy.enable) tracy.fiberEnter(main_fiber.name);
825943}
826944
......@@ -831,7 +949,7 @@ pub fn deinit(ev: *Evented) void {
831949 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
832950 }
833951 ev.yield(null, .exit);
834 ev.threads.allocated[0].io_uring.deinit();
952 ev.threads.allocated[0].deinit(ev.allocator());
835953 ev.null_fd.close();
836954 ev.random_fd.close();
837955 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(ev.threads.allocated.ptr));
......@@ -848,6 +966,7 @@ pub fn deinit(ev: *Evented) void {
848966
849967fn findReadyFiber(ev: *Evented, thread: *Thread) ?*Fiber {
850968 if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| {
969 assert(ready_fiber != Fiber.finished);
851970 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.status.queue_next, .release);
852971 ready_fiber.status.queue_next = null;
853972 return ready_fiber;
......@@ -860,7 +979,7 @@ fn findReadyFiber(ev: *Evented, thread: *Thread) ?*Fiber {
860979 &ev.threads.allocated[0..active_threads][thread.steal_ready_search_index];
861980 if (steal_ready_search_thread == thread) continue;
862981 const ready_fiber =
863 @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .acquire) orelse continue;
982 @atomicLoad(?*Fiber, &steal_ready_search_thread.ready_queue, .monotonic) orelse continue;
864983 if (ready_fiber == Fiber.finished) continue;
865984 if (@cmpxchgWeak(
866985 ?*Fiber,
......@@ -892,19 +1011,10 @@ fn yield(ev: *Evented, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.P
8921011 },
8931012 .pending_task = pending_task,
8941013 };
895 log.debug("switching from {*} to {*}", .{ message.contexts.prev, message.contexts.ready });
8961014 contextSwitch(&message).handle(ev);
8971015}
8981016
8991017fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {
900 {
901 var fiber = ready_queue.head;
902 while (true) {
903 log.debug("scheduling {*}", .{fiber});
904 fiber = fiber.status.queue_next orelse break;
905 }
906 assert(fiber == ready_queue.tail);
907 }
9081018 // shared fields of previous `Thread` must be initialized before later ones are marked as active
9091019 const new_thread_index = @atomicLoad(u32, &ev.threads.active, .acquire);
9101020 for (0..@min(max_idle_search, new_thread_index)) |_| {
......@@ -963,7 +1073,8 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {
9631073 .idle_context = undefined,
9641074 .current_context = &new_thread.idle_context,
9651075 .ready_queue = ready_queue.head,
966 .io_uring = IoUring.init_params(io_uring_entries, &params) catch |err| {
1076 .free_queue = null,
1077 .io_uring = IoUring.init_params(@as(u16, 1) << ev.log2_ring_entries, &params) catch |err| {
9671078 @atomicStore(u32, &ev.threads.reserved, new_thread_index, .release);
9681079 // no more access to `thread` after giving up reservation
9691080 log.warn("unable to create worker thread due to io_uring init failure: {s}", .{
......@@ -973,6 +1084,7 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {
9731084 },
9741085 .idle_search_index = 0,
9751086 .steal_ready_search_index = 0,
1087 .steal_free_search_index = 0,
9761088 .name_arena = .{},
9771089 .csprng = .uninitialized,
9781090 };
......@@ -991,14 +1103,14 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {
9911103 return false;
9921104 }
9931105 // nobody wanted it, so just queue it on ourselves
994 while (@cmpxchgWeak(
1106 while (true) ready_queue.tail.status.queue_next = @cmpxchgWeak(
9951107 ?*Fiber,
9961108 &thread.ready_queue,
9971109 ready_queue.tail.status.queue_next,
9981110 ready_queue.head,
9991111 .acq_rel,
10001112 .acquire,
1001 )) |old_head| ready_queue.tail.status.queue_next = old_head;
1113 ) orelse break;
10021114 return false;
10031115}
10041116
......@@ -1015,8 +1127,7 @@ fn mainIdle(
10151127fn threadEntry(ev: *Evented, index: u32) void {
10161128 const thread: *Thread = &ev.threads.allocated[index];
10171129 Thread.self = thread;
1018 defer thread.io_uring.deinit();
1019 log.debug("created thread idle {*}", .{&thread.idle_context});
1130 defer thread.deinit(ev.allocator());
10201131 switch (linux.errno(linux.io_uring_register(thread.io_uring.fd, .REGISTER_ENABLE_RINGS, null, 0))) {
10211132 .SUCCESS => ev.idle(thread),
10221133 else => |err| @panic(@tagName(err)),
......@@ -1054,103 +1165,107 @@ fn idle(ev: *Evented, thread: *Thread) void {
10541165 error.SignalInterrupt => {},
10551166 else => |e| @panic(@errorName(e)),
10561167 };
1057 var cqes_buffer: [io_uring_entries]linux.io_uring_cqe = undefined;
10581168 var maybe_ready_queue: ?Fiber.Queue = null;
1059 for (cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) {
1060 error.SignalInterrupt => 0,
1061 else => |e| @panic(@errorName(e)),
1062 }]) |cqe| if (cqe.flags & linux.IORING_CQE_F_SKIP == 0) switch (@as(
1063 Completion.UserData,
1064 @enumFromInt(cqe.user_data),
1065 )) {
1066 .unused => unreachable, // bad submission queued?
1067 .wakeup => {},
1068 .futex_wake => switch (Completion.errno(.{ .result = cqe.res, .flags = cqe.flags })) {
1069 .SUCCESS => recoverableOsBugDetected(), // success is skipped
1070 .INVAL => {}, // invalid futex_wait() on ptr done elsewhere
1071 .INTR, .CANCELED => recoverableOsBugDetected(), // `Completion.UserData.futex_wake` is not cancelable
1072 .FAULT => {}, // pointer became invalid while doing the wake
1073 else => recoverableOsBugDetected(), // deadlock due to operating system bug
1074 },
1075 .cleanup => @panic("failed to notify other threads that we are exiting"),
1076 .exit => {
1077 assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async
1078 return;
1079 },
1080 _ => if (@as(?*Fiber, ready_fiber: switch (@as(u2, @truncate(cqe.user_data))) {
1081 0b00 => {
1082 const ready_fiber: *Fiber = @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));
1083 ready_fiber.resultPointer(Completion).* = .{
1084 .result = cqe.res,
1085 .flags = cqe.flags,
1086 };
1087 break :ready_fiber ready_fiber;
1088 },
1089 0b01 => {
1090 thread.enqueue().* = .{
1091 .opcode = .ASYNC_CANCEL,
1092 .flags = linux.IOSQE_CQE_SKIP_SUCCESS,
1093 .ioprio = 0,
1094 .fd = 0,
1095 .off = 0,
1096 .addr = cqe.user_data & ~@as(usize, 0b11),
1097 .len = 0,
1098 .rw_flags = 0,
1099 .user_data = @intFromEnum(Completion.UserData.wakeup),
1100 .buf_index = 0,
1101 .personality = 0,
1102 .splice_fd_in = 0,
1103 .addr3 = 0,
1104 .resv = 0,
1105 };
1106 break :ready_fiber null;
1169 while (true) {
1170 var cqes_buffer: [1 << 8]linux.io_uring_cqe = undefined;
1171 const cqes = cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) {
1172 error.SignalInterrupt => 0,
1173 else => |e| @panic(@errorName(e)),
1174 }];
1175 if (cqes.len == 0) break;
1176 for (cqes) |cqe| if (cqe.flags & linux.IORING_CQE_F_SKIP == 0) switch (@as(
1177 Completion.UserData,
1178 @enumFromInt(cqe.user_data),
1179 )) {
1180 .unused => unreachable, // bad submission queued?
1181 .wakeup => {},
1182 .futex_wake => switch (Completion.errno(.{ .result = cqe.res, .flags = cqe.flags })) {
1183 .SUCCESS => recoverableOsBugDetected(), // success is skipped
1184 .INVAL => {}, // invalid futex_wait() on ptr done elsewhere
1185 .INTR, .CANCELED => recoverableOsBugDetected(), // `Completion.UserData.futex_wake` is not cancelable
1186 .FAULT => {}, // pointer became invalid while doing the wake
1187 else => recoverableOsBugDetected(), // deadlock due to operating system bug
11071188 },
1108 0b10 => {
1109 const context: *Io.Operation.Storage.Pending.Context =
1110 @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));
1111 const batch: *Io.Batch = @ptrFromInt(context[0]);
1112 var next: usize = 0b00;
1113 context[0..3].* = .{ next, @as(u32, @bitCast(cqe.res)), cqe.flags };
1114 while (true) {
1115 next = @cmpxchgWeak(
1116 usize,
1117 @as(*usize, @ptrCast(&batch.context)),
1118 next,
1119 cqe.user_data,
1120 .release,
1121 .acquire,
1122 ) orelse break;
1123 context[0] = next;
1124 }
1125 break :ready_fiber switch (@as(u2, @truncate(next))) {
1126 0b00, 0b01 => @ptrFromInt(next & ~@as(usize, 0b11)),
1127 0b10, 0b11 => null,
1128 };
1189 .cleanup => @panic("failed to notify other threads that we are exiting"),
1190 .exit => {
1191 assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async
1192 return;
11291193 },
1130 0b11 => switch (Completion.errno(.{ .result = cqe.res, .flags = cqe.flags })) {
1131 .SUCCESS => unreachable, // no event count specified
1132 .TIME => {
1133 const context: *usize = @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));
1134 const fiber = @atomicRmw(usize, context, .Add, 0b01, .acquire);
1135 break :ready_fiber switch (@as(u2, @truncate(fiber))) {
1136 else => unreachable, // timeout completed multiple times
1137 0b00 => @ptrFromInt(fiber & ~@as(usize, 0b11)),
1138 0b10 => null,
1194 _ => if (@as(?*Fiber, ready_fiber: switch (@as(u2, @truncate(cqe.user_data))) {
1195 0b00 => {
1196 const ready_fiber: *Fiber = @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));
1197 ready_fiber.resultPointer(Completion).* = .{
1198 .result = cqe.res,
1199 .flags = cqe.flags,
1200 };
1201 break :ready_fiber ready_fiber;
1202 },
1203 0b01 => {
1204 thread.enqueue().* = .{
1205 .opcode = .ASYNC_CANCEL,
1206 .flags = linux.IOSQE_CQE_SKIP_SUCCESS,
1207 .ioprio = 0,
1208 .fd = 0,
1209 .off = 0,
1210 .addr = cqe.user_data & ~@as(usize, 0b11),
1211 .len = 0,
1212 .rw_flags = 0,
1213 .user_data = @intFromEnum(Completion.UserData.wakeup),
1214 .buf_index = 0,
1215 .personality = 0,
1216 .splice_fd_in = 0,
1217 .addr3 = 0,
1218 .resv = 0,
11391219 };
1220 break :ready_fiber null;
11401221 },
1141 .CANCELED => null, // user data may have been invalidated
1142 else => |err| unexpectedErrno(err) catch null,
1222 0b10 => {
1223 const context: *Io.Operation.Storage.Pending.Context =
1224 @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));
1225 const batch: *Io.Batch = @ptrFromInt(context[0]);
1226 var next: usize = 0b00;
1227 context[0..3].* = .{ next, @as(u32, @bitCast(cqe.res)), cqe.flags };
1228 while (true) {
1229 next = @cmpxchgWeak(
1230 usize,
1231 @as(*usize, @ptrCast(&batch.context)),
1232 next,
1233 cqe.user_data,
1234 .release,
1235 .acquire,
1236 ) orelse break;
1237 context[0] = next;
1238 }
1239 break :ready_fiber switch (@as(u2, @truncate(next))) {
1240 0b00, 0b01 => @ptrFromInt(next & ~@as(usize, 0b11)),
1241 0b10, 0b11 => null,
1242 };
1243 },
1244 0b11 => switch (Completion.errno(.{ .result = cqe.res, .flags = cqe.flags })) {
1245 .SUCCESS => unreachable, // no event count specified
1246 .TIME => {
1247 const context: *usize = @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));
1248 const fiber = @atomicRmw(usize, context, .Add, 0b01, .acquire);
1249 break :ready_fiber switch (@as(u2, @truncate(fiber))) {
1250 else => unreachable, // timeout completed multiple times
1251 0b00 => @ptrFromInt(fiber & ~@as(usize, 0b11)),
1252 0b10 => null,
1253 };
1254 },
1255 .CANCELED => null, // user data may have been invalidated
1256 else => |err| unexpectedErrno(err) catch null,
1257 },
1258 })) |ready_fiber| {
1259 assert(ready_fiber.status.queue_next == null);
1260 if (maybe_ready_fiber == null) {
1261 maybe_ready_fiber = ready_fiber;
1262 } else if (maybe_ready_queue) |*ready_queue| {
1263 ready_queue.tail.status.queue_next = ready_fiber;
1264 ready_queue.tail = ready_fiber;
1265 } else maybe_ready_queue = .{ .head = ready_fiber, .tail = ready_fiber };
11431266 },
1144 })) |ready_fiber| {
1145 assert(ready_fiber.status.queue_next == null);
1146 if (maybe_ready_fiber == null) {
1147 maybe_ready_fiber = ready_fiber;
1148 } else if (maybe_ready_queue) |*ready_queue| {
1149 ready_queue.tail.status.queue_next = ready_fiber;
1150 ready_queue.tail = ready_fiber;
1151 } else maybe_ready_queue = .{ .head = ready_fiber, .tail = ready_fiber };
1152 },
1153 };
1267 };
1268 }
11541269 if (maybe_ready_queue) |ready_queue| _ = ev.schedule(thread, ready_queue);
11551270 }
11561271}
......@@ -1220,8 +1335,7 @@ const SwitchMessage = struct {
12201335 },
12211336 .destroy => {
12221337 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
1223 fiber.destroy(ev.backing_allocator);
1224 ev.backing_allocator_mutex.unlock(ev.io());
1338 fiber.destroy();
12251339 },
12261340 .exit => for (
12271341 ev.threads.allocated[0..@atomicLoad(u32, &ev.threads.active, .acquire)],
......@@ -1295,7 +1409,6 @@ inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {
12951409 .x15 = true,
12961410 .x16 = true,
12971411 .x17 = true,
1298 .x18 = true,
12991412 .x19 = true,
13001413 .x20 = true,
13011414 .x21 = true,
......@@ -1497,7 +1610,6 @@ const AsyncClosure = struct {
14971610 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
14981611 message.handle(closure.ev);
14991612 const fiber = closure.fiber;
1500 log.debug("{*} performing async", .{fiber});
15011613 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
15021614 closure.ev.yield(
15031615 if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|
......@@ -1542,7 +1654,6 @@ fn concurrent(
15421654 const fiber = Fiber.create(ev) catch |err| switch (err) {
15431655 error.OutOfMemory => return error.ConcurrencyUnavailable,
15441656 };
1545 log.debug("allocated {*}", .{fiber});
15461657
15471658 const closure: *AsyncClosure = .fromFiber(fiber);
15481659 fiber.* = .{
......@@ -1608,7 +1719,7 @@ fn await(
16081719 assert(awaiter == fiber); // spurious wakeup
16091720 }
16101721 @memcpy(result, future_fiber.resultBytes(result_alignment));
1611 future_fiber.destroy(ev.allocator());
1722 future_fiber.destroy();
16121723}
16131724
16141725fn cancel(
......@@ -1866,7 +1977,6 @@ const Group = struct {
18661977 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
18671978 message.handle(closure.ev);
18681979 assert(closure.fiber.status.queue_next == null);
1869 log.debug("{*} performing group async", .{closure.fiber});
18701980 const result = closure.start(closure.contextPointer());
18711981 const ev = closure.ev;
18721982 const group = closure.group;
......@@ -1877,9 +1987,7 @@ const Group = struct {
18771987 } else |err| switch (err) {
18781988 error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled
18791989 }
1880 const awaiter = group.removeFiber(ev, fiber);
1881 ev.backing_allocator_mutex.lockUncancelable(ev.io());
1882 ev.yield(awaiter, .destroy);
1990 ev.yield(group.removeFiber(ev, fiber), .destroy);
18831991 unreachable; // switched to dead fiber
18841992 }
18851993 };
......@@ -1930,7 +2038,6 @@ fn groupConcurrent(
19302038 const fiber = Fiber.create(ev) catch |err| switch (err) {
19312039 error.OutOfMemory => return error.ConcurrencyUnavailable,
19322040 };
1933 log.debug("allocated {*}", .{fiber});
19342041
19352042 const closure: *Group.AsyncClosure = .fromFiber(fiber);
19362043 fiber.* = .{
......@@ -2080,7 +2187,6 @@ fn futexWait(
20802187 timeout: Io.Timeout,
20812188) Io.Cancelable!void {
20822189 const ev: *Evented = @ptrCast(@alignCast(userdata));
2083 if (builtin.single_threaded) unreachable; // Deadlock.
20842190 const timespec: ?linux.kernel_timespec, const clock: Io.Clock, const timeout_flags: u32 = timespec: switch (timeout) {
20852191 .none => .{
20862192 null,
......@@ -2163,7 +2269,6 @@ fn futexWait(
21632269
21642270fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
21652271 const ev: *Evented = @ptrCast(@alignCast(userdata));
2166 if (builtin.single_threaded) unreachable; // Deadlock.
21672272 var cancel_region: CancelRegion = .initBlocked();
21682273 defer cancel_region.deinit();
21692274 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {
......@@ -2199,7 +2304,6 @@ fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32)
21992304fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
22002305 const ev: *Evented = @ptrCast(@alignCast(userdata));
22012306 _ = ev;
2202 if (builtin.single_threaded) unreachable; // Nothing to wake up.
22032307 const thread: *Thread = .current();
22042308 thread.enqueue().* = .{
22052309 .opcode = .FUTEX_WAKE,
......@@ -2222,24 +2326,43 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
22222326
22232327fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result {
22242328 const ev: *Evented = @ptrCast(@alignCast(userdata));
2225 switch (operation) {
2226 .file_read_streaming => |o| return .{
2227 .file_read_streaming = ev.fileReadStreaming(o.file, o.data) catch |err| switch (err) {
2329 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
2330 defer maybe_sync.deinit(ev);
2331 return switch (operation) {
2332 .file_read_streaming => |o| .{
2333 .file_read_streaming = ev.fileReadStreaming(
2334 &maybe_sync.cancel_region,
2335 o.file,
2336 o.data,
2337 ) catch |err| switch (err) {
22282338 error.Canceled => |e| return e,
22292339 else => |e| e,
22302340 },
22312341 },
2232 .file_write_streaming => |o| return .{
2233 .file_write_streaming = ev.fileWriteStreaming(o.file, o.header, o.data, o.splat) catch |err| switch (err) {
2342 .file_write_streaming => |o| .{
2343 .file_write_streaming = ev.fileWriteStreaming(
2344 &maybe_sync.cancel_region,
2345 o.file,
2346 o.header,
2347 o.data,
2348 o.splat,
2349 ) catch |err| switch (err) {
22342350 error.Canceled => |e| return e,
22352351 else => |e| e,
22362352 },
22372353 },
2238 .device_io_control => |*o| return .{ .device_io_control = try deviceIoControl(o) },
2239 }
2354 .device_io_control => |o| .{
2355 .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o),
2356 },
2357 };
22402358}
22412359
2242fn fileReadStreaming(ev: *Evented, file: File, data: []const []u8) File.Reader.Error!usize {
2360fn fileReadStreaming(
2361 ev: *Evented,
2362 cancel_region: *CancelRegion,
2363 file: File,
2364 data: []const []u8,
2365) File.ReadStreamingError!usize {
22432366 var iovecs_buffer: [max_iovecs_len]iovec = undefined;
22442367 var i: usize = 0;
22452368 for (data) |buf| {
......@@ -2252,13 +2375,13 @@ fn fileReadStreaming(ev: *Evented, file: File, data: []const []u8) File.Reader.E
22522375 const dest = iovecs_buffer[0..i];
22532376 assert(dest[0].len > 0);
22542377
2255 var cancel_region: CancelRegion = .init();
2256 defer cancel_region.deinit();
2257 return ev.preadv(&cancel_region, file.handle, dest, null);
2378 const n = try ev.preadv(cancel_region, file.handle, dest, null);
2379 return if (n == 0) error.EndOfStream else n;
22582380}
22592381
22602382fn fileWriteStreaming(
22612383 ev: *Evented,
2384 cancel_region: *CancelRegion,
22622385 file: File,
22632386 header: []const u8,
22642387 data: []const []const u8,
......@@ -2294,17 +2417,17 @@ fn fileWriteStreaming(
22942417 },
22952418 },
22962419 };
2297
2298 var cancel_region: CancelRegion = .init();
2299 defer cancel_region.deinit();
2300 return ev.pwritev(&cancel_region, file.handle, iovecs[0..iovlen], null);
2420 return ev.pwritev(cancel_region, file.handle, iovecs[0..iovlen], null);
23012421}
23022422
2303fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!i32 {
2304 var cancel_region: CancelRegion = .init();
2305 defer cancel_region.deinit();
2423fn deviceIoControl(
2424 ev: *Evented,
2425 sync: *CancelRegion.Sync,
2426 o: Io.Operation.DeviceIoControl,
2427) Io.Cancelable!i32 {
2428 _ = ev;
23062429 while (true) {
2307 try cancel_region.await(.nothing);
2430 try sync.cancel_region.await(.nothing);
23082431 const rc = linux.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));
23092432 switch (linux.errno(rc)) {
23102433 .SUCCESS => return @bitCast(@as(u32, @truncate(rc))),
......@@ -2316,12 +2439,13 @@ fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!i32 {
23162439
23172440fn batchAwaitAsync(userdata: ?*anyopaque, batch: *Io.Batch) Io.Cancelable!void {
23182441 const ev: *Evented = @ptrCast(@alignCast(userdata));
2319 var cancel_region: CancelRegion = .init();
2320 defer cancel_region.deinit();
2321 batchDrainSubmitted(batch, &cancel_region, false) catch |err| switch (err) {
2442 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
2443 defer maybe_sync.deinit(ev);
2444 ev.batchDrainSubmitted(&maybe_sync, batch, false) catch |err| switch (err) {
23222445 error.ConcurrencyUnavailable => unreachable, // passed concurrency=false
23232446 else => |e| return e,
23242447 };
2448 maybe_sync.leaveSync(ev);
23252449 while (true) {
23262450 batchDrainReady(batch) catch |err| switch (err) {
23272451 error.Timeout => unreachable, // no timeout
......@@ -2337,9 +2461,10 @@ fn batchAwaitConcurrent(
23372461 timeout: Io.Timeout,
23382462) Io.Batch.AwaitConcurrentError!void {
23392463 const ev: *Evented = @ptrCast(@alignCast(userdata));
2340 var cancel_region: CancelRegion = .init();
2341 defer cancel_region.deinit();
2342 try batchDrainSubmitted(batch, &cancel_region, true);
2464 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
2465 defer maybe_sync.deinit(ev);
2466 try ev.batchDrainSubmitted(&maybe_sync, batch, true);
2467 maybe_sync.leaveSync(ev);
23432468 const timespec: linux.kernel_timespec, const clock: Io.Clock, const timeout_flags: u32 = while (true) {
23442469 batchDrainReady(batch) catch |err| switch (err) {
23452470 error.Timeout => unreachable, // no timeout
......@@ -2372,7 +2497,7 @@ fn batchAwaitConcurrent(
23722497 }
23732498 };
23742499 {
2375 const thread = try cancel_region.awaitIoUring();
2500 const thread = try maybe_sync.cancel_region.awaitIoUring();
23762501 thread.enqueue().* = .{
23772502 .opcode = .TIMEOUT,
23782503 .flags = 0,
......@@ -2401,7 +2526,7 @@ fn batchAwaitConcurrent(
24012526 };
24022527 if (batch.completed.head == .none) continue;
24032528 }
2404 const thread = try cancel_region.awaitIoUring();
2529 const thread = try maybe_sync.cancel_region.awaitIoUring();
24052530 thread.enqueue().* = .{
24062531 .opcode = .TIMEOUT_REMOVE,
24072532 .flags = 0,
......@@ -2411,7 +2536,7 @@ fn batchAwaitConcurrent(
24112536 .addr = @intFromPtr(&batch.context) | 0b11,
24122537 .len = 0,
24132538 .rw_flags = 0,
2414 .user_data = @intFromPtr(cancel_region.fiber),
2539 .user_data = @intFromPtr(maybe_sync.cancel_region.fiber),
24152540 .buf_index = 0,
24162541 .personality = 0,
24172542 .splice_fd_in = 0,
......@@ -2419,7 +2544,7 @@ fn batchAwaitConcurrent(
24192544 .resv = 0,
24202545 };
24212546 ev.yield(null, .nothing);
2422 switch (cancel_region.errno()) {
2547 switch (maybe_sync.cancel_region.errno()) {
24232548 .SUCCESS => return,
24242549 .BUSY, .NOENT => {},
24252550 else => |err| unexpectedErrno(err) catch {},
......@@ -2434,22 +2559,23 @@ fn batchAwaitConcurrent(
24342559
24352560/// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable.
24362561fn batchDrainSubmitted(
2562 ev: *Evented,
2563 maybe_sync: *CancelRegion.Sync.Maybe,
24372564 batch: *Io.Batch,
2438 cancel_region: *CancelRegion,
24392565 concurrency: bool,
24402566) (Io.ConcurrentError || Io.Cancelable)!void {
24412567 var index = batch.submitted.head;
24422568 if (index == .none) return;
24432569 errdefer batch.submitted.head = index;
2444 const thread = try cancel_region.awaitIoUring();
2570 const thread = try maybe_sync.cancelRegion().awaitIoUring();
24452571 while (index != .none) {
24462572 const storage = &batch.storage[index.toIndex()];
24472573 const next_index = storage.submission.node.next;
2448 if (@as(?Io.Operation.Result, operation: switch (storage.submission.operation) {
2574 if (@as(?Io.Operation.Result, result: switch (storage.submission.operation) {
24492575 .file_read_streaming => |o| {
24502576 const buffer = for (o.data) |buffer| {
24512577 if (buffer.len != 0) break buffer;
2452 } else break :operation .{ .file_read_streaming = 0 };
2578 } else break :result .{ .file_read_streaming = 0 };
24532579 const fd = o.file.handle;
24542580 storage.* = .{ .pending = .{
24552581 .node = .{ .prev = batch.pending.tail, .next = .none },
......@@ -2472,7 +2598,7 @@ fn batchDrainSubmitted(
24722598 .addr3 = 0,
24732599 .resv = 0,
24742600 };
2475 break :operation null;
2601 break :result null;
24762602 },
24772603 .file_write_streaming => |o| {
24782604 const buffer = buffer: {
......@@ -2481,7 +2607,7 @@ fn batchDrainSubmitted(
24812607 if (buffer.len != 0) break :buffer buffer;
24822608 }
24832609 if (o.splat > 0) break :buffer o.data[o.data.len - 1];
2484 break :operation .{ .file_write_streaming = 0 };
2610 break :result .{ .file_write_streaming = 0 };
24852611 };
24862612 const fd = o.file.handle;
24872613 storage.* = .{ .pending = .{
......@@ -2505,12 +2631,12 @@ fn batchDrainSubmitted(
25052631 .addr3 = 0,
25062632 .resv = 0,
25072633 };
2508 break :operation null;
2634 break :result null;
25092635 },
25102636 .device_io_control => |o| if (concurrency)
25112637 return error.ConcurrencyUnavailable
25122638 else
2513 .{ .device_io_control = try deviceIoControl(&o) },
2639 .{ .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o) },
25142640 })) |result| {
25152641 switch (batch.completed.tail) {
25162642 .none => batch.completed.head = index,
......@@ -2838,7 +2964,6 @@ fn dirAccess(
28382964 options: Dir.AccessOptions,
28392965) Dir.AccessError!void {
28402966 const ev: *Evented = @ptrCast(@alignCast(userdata));
2841 _ = ev;
28422967
28432968 var path_buffer: [PATH_MAX]u8 = undefined;
28442969 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
......@@ -2849,10 +2974,10 @@ fn dirAccess(
28492974 @as(u32, if (options.execute) linux.X_OK else 0);
28502975 const flags: u32 = if (options.follow_symlinks) 0 else linux.AT.SYMLINK_NOFOLLOW;
28512976
2852 var cancel_region: CancelRegion = .init();
2853 defer cancel_region.deinit();
2977 var sync: CancelRegion.Sync = try .init(ev);
2978 defer sync.deinit(ev);
28542979 while (true) {
2855 try cancel_region.await(.nothing);
2980 try sync.cancel_region.await(.nothing);
28562981 switch (linux.errno(linux.faccessat(dir.handle, sub_path_posix, mode, flags))) {
28572982 .SUCCESS => return,
28582983 .INTR => continue,
......@@ -2885,21 +3010,21 @@ fn dirCreateFile(
28853010 var path_buffer: [PATH_MAX]u8 = undefined;
28863011 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
28873012
2888 var cancel_region: CancelRegion = .init();
2889 defer cancel_region.deinit();
2890 const fd = try ev.openat(&cancel_region, dir.handle, sub_path_posix, .{
3013 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
3014 defer maybe_sync.deinit(ev);
3015 const fd = try ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{
28913016 .ACCMODE = if (flags.read) .RDWR else .WRONLY,
28923017 .CREAT = true,
28933018 .TRUNC = flags.truncate,
28943019 .EXCL = flags.exclusive,
28953020 .CLOEXEC = true,
28963021 }, flags.permissions.toMode());
2897 errdefer ev.close(fd);
3022 errdefer ev.close(maybe_sync.cancelRegion(), fd);
28983023
28993024 switch (flags.lock) {
29003025 .none => {},
29013026 .shared, .exclusive => try ev.flock(
2902 &cancel_region,
3027 try maybe_sync.enterSync(ev),
29033028 fd,
29043029 flags.lock,
29053030 if (flags.lock_nonblocking) .nonblocking else .blocking,
......@@ -3069,9 +3194,9 @@ fn dirOpenFile(
30693194 var path_buffer: [PATH_MAX]u8 = undefined;
30703195 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
30713196
3072 var cancel_region: CancelRegion = .init();
3073 defer cancel_region.deinit();
3074 const fd = try ev.openat(&cancel_region, dir.handle, sub_path_posix, .{
3197 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
3198 defer maybe_sync.deinit(ev);
3199 const fd = try ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{
30753200 .ACCMODE = switch (flags.mode) {
30763201 .read_only => .RDONLY,
30773202 .write_only => .WRONLY,
......@@ -3082,11 +3207,11 @@ fn dirOpenFile(
30823207 .CLOEXEC = true,
30833208 .PATH = flags.path_only,
30843209 }, 0);
3085 errdefer ev.close(fd);
3210 errdefer ev.close(maybe_sync.cancelRegion(), fd);
30863211
30873212 if (!flags.allow_directory) {
30883213 const is_dir = is_dir: {
3089 const s = ev.stat(&cancel_region, fd) catch |err| switch (err) {
3214 const s = ev.stat(&maybe_sync.cancel_region, fd) catch |err| switch (err) {
30903215 // The directory-ness is either unknown or unknowable
30913216 error.Streaming => break :is_dir false,
30923217 else => |e| return e,
......@@ -3099,7 +3224,7 @@ fn dirOpenFile(
30993224 switch (flags.lock) {
31003225 .none => {},
31013226 .shared, .exclusive => try ev.flock(
3102 &cancel_region,
3227 try maybe_sync.enterSync(ev),
31033228 fd,
31043229 flags.lock,
31053230 if (flags.lock_nonblocking) .nonblocking else .blocking,
......@@ -3111,7 +3236,9 @@ fn dirOpenFile(
31113236
31123237fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
31133238 const ev: *Evented = @ptrCast(@alignCast(userdata));
3114 for (dirs) |dir| ev.close(dir.handle);
3239 var cancel_region: CancelRegion = .init();
3240 defer cancel_region.deinit();
3241 for (dirs) |dir| ev.close(&cancel_region, dir.handle);
31153242}
31163243
31173244fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {
......@@ -3122,17 +3249,17 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read
31223249 // Refill the buffer, unless we've already created references to
31233250 // buffered data.
31243251 if (buffer_index != 0) break;
3125 var cancel_region: CancelRegion = .init();
3126 defer cancel_region.deinit();
3252 var sync: CancelRegion.Sync = try .init(ev);
3253 defer sync.deinit(ev);
31273254 if (dr.state == .reset) {
3128 ev.lseek(&cancel_region, dr.dir.handle, 0, linux.SEEK.SET) catch |err| switch (err) {
3255 ev.lseek(&sync, dr.dir.handle, 0, linux.SEEK.SET) catch |err| switch (err) {
31293256 error.Unseekable => return error.Unexpected,
31303257 else => |e| return e,
31313258 };
31323259 dr.state = .reading;
31333260 }
31343261 const n = while (true) {
3135 try cancel_region.await(.nothing);
3262 try sync.cancel_region.await(.nothing);
31363263 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
31373264 switch (linux.errno(rc)) {
31383265 .SUCCESS => break rc,
......@@ -3203,9 +3330,9 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read
32033330
32043331fn dirRealPath(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
32053332 const ev: *Evented = @ptrCast(@alignCast(userdata));
3206 var cancel_region: CancelRegion = .init();
3207 defer cancel_region.deinit();
3208 return ev.realPath(&cancel_region, dir.handle, out_buffer);
3333 var sync: CancelRegion.Sync = try .init(ev);
3334 defer sync.deinit(ev);
3335 return ev.realPath(&sync, dir.handle, out_buffer);
32093336}
32103337
32113338fn dirRealPathFile(
......@@ -3219,9 +3346,9 @@ fn dirRealPathFile(
32193346 var path_buffer: [PATH_MAX]u8 = undefined;
32203347 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
32213348
3222 var cancel_region: CancelRegion = .init();
3223 defer cancel_region.deinit();
3224 const fd = ev.openat(&cancel_region, dir.handle, sub_path_posix, .{
3349 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
3350 defer maybe_sync.deinit(ev);
3351 const fd = ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{
32253352 .CLOEXEC = true,
32263353 .PATH = true,
32273354 }, 0) catch |err| switch (err) {
......@@ -3229,8 +3356,8 @@ fn dirRealPathFile(
32293356 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.
32303357 else => |e| return e,
32313358 };
3232 defer ev.close(fd);
3233 return ev.realPath(&cancel_region, fd, out_buffer);
3359 defer ev.close(maybe_sync.cancelRegion(), fd);
3360 return ev.realPath(try maybe_sync.enterSync(ev), fd, out_buffer);
32343361}
32353362
32363363fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
......@@ -3458,15 +3585,14 @@ fn dirReadLink(
34583585 buffer: []u8,
34593586) Dir.ReadLinkError!usize {
34603587 const ev: *Evented = @ptrCast(@alignCast(userdata));
3461 _ = ev;
34623588
34633589 var sub_path_buffer: [PATH_MAX]u8 = undefined;
34643590 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
34653591
3466 var cancel_region: CancelRegion = .init();
3467 defer cancel_region.deinit();
3592 var sync: CancelRegion.Sync = try .init(ev);
3593 defer sync.deinit(ev);
34683594 while (true) {
3469 try cancel_region.await(.nothing);
3595 try sync.cancel_region.await(.nothing);
34703596 const rc = linux.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
34713597 switch (linux.errno(rc)) {
34723598 .SUCCESS => {
......@@ -3496,10 +3622,10 @@ fn dirSetOwner(
34963622 group: ?File.Gid,
34973623) Dir.SetOwnerError!void {
34983624 const ev: *Evented = @ptrCast(@alignCast(userdata));
3499 var cancel_region: CancelRegion = .init();
3500 defer cancel_region.deinit();
3625 var sync: CancelRegion.Sync = try .init(ev);
3626 defer sync.deinit(ev);
35013627 try ev.fchownat(
3502 &cancel_region,
3628 &sync,
35033629 dir.handle,
35043630 "",
35053631 owner orelse std.math.maxInt(linux.uid_t),
......@@ -3519,10 +3645,10 @@ fn dirSetFileOwner(
35193645 const ev: *Evented = @ptrCast(@alignCast(userdata));
35203646 var path_buffer: [PATH_MAX]u8 = undefined;
35213647 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3522 var cancel_region: CancelRegion = .init();
3523 defer cancel_region.deinit();
3648 var sync: CancelRegion.Sync = try .init(ev);
3649 defer sync.deinit(ev);
35243650 try ev.fchownat(
3525 &cancel_region,
3651 &sync,
35263652 dir.handle,
35273653 sub_path_posix,
35283654 owner orelse std.math.maxInt(linux.uid_t),
......@@ -3537,10 +3663,10 @@ fn dirSetPermissions(
35373663 permissions: Dir.Permissions,
35383664) Dir.SetPermissionsError!void {
35393665 const ev: *Evented = @ptrCast(@alignCast(userdata));
3540 var cancel_region: CancelRegion = .init();
3541 defer cancel_region.deinit();
3666 var sync: CancelRegion.Sync = try .init(ev);
3667 defer sync.deinit(ev);
35423668 ev.fchmodat(
3543 &cancel_region,
3669 &sync,
35443670 dir.handle,
35453671 "",
35463672 permissions.toMode(),
......@@ -3565,10 +3691,10 @@ fn dirSetFilePermissions(
35653691 const ev: *Evented = @ptrCast(@alignCast(userdata));
35663692 var path_buffer: [PATH_MAX]u8 = undefined;
35673693 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3568 var cancel_region: CancelRegion = .init();
3569 defer cancel_region.deinit();
3694 var sync: CancelRegion.Sync = try .init(ev);
3695 defer sync.deinit(ev);
35703696 try ev.fchmodat(
3571 &cancel_region,
3697 &sync,
35723698 dir.handle,
35733699 sub_path_posix,
35743700 permissions.toMode(),
......@@ -3585,8 +3711,8 @@ fn dirSetTimestamps(
35853711 const ev: *Evented = @ptrCast(@alignCast(userdata));
35863712 var path_buffer: [PATH_MAX]u8 = undefined;
35873713 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3588 var cancel_region: CancelRegion = .init();
3589 defer cancel_region.deinit();
3714 var cancel_region: CancelRegion.Sync = try .init(ev);
3715 defer cancel_region.deinit(ev);
35903716 try ev.utimensat(
35913717 &cancel_region,
35923718 dir.handle,
......@@ -3680,7 +3806,9 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
36803806
36813807fn fileClose(userdata: ?*anyopaque, files: []const File) void {
36823808 const ev: *Evented = @ptrCast(@alignCast(userdata));
3683 for (files) |file| ev.close(file.handle);
3809 var cancel_region: CancelRegion = .init();
3810 defer cancel_region.deinit();
3811 for (files) |file| ev.close(&cancel_region, file.handle);
36843812}
36853813
36863814fn fileWritePositional(
......@@ -3807,16 +3935,16 @@ fn fileReadPositional(
38073935
38083936fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {
38093937 const ev: *Evented = @ptrCast(@alignCast(userdata));
3810 var cancel_region: CancelRegion = .init();
3811 defer cancel_region.deinit();
3812 try ev.lseek(&cancel_region, file.handle, @bitCast(offset), linux.SEEK.CUR);
3938 var sync: CancelRegion.Sync = try .init(ev);
3939 defer sync.deinit(ev);
3940 try ev.lseek(&sync, file.handle, @bitCast(offset), linux.SEEK.CUR);
38133941}
38143942
38153943fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {
38163944 const ev: *Evented = @ptrCast(@alignCast(userdata));
3817 var cancel_region: CancelRegion = .init();
3818 defer cancel_region.deinit();
3819 try ev.lseek(&cancel_region, file.handle, offset, linux.SEEK.SET);
3945 var sync: CancelRegion.Sync = try .init(ev);
3946 defer sync.deinit(ev);
3947 try ev.lseek(&sync, file.handle, offset, linux.SEEK.SET);
38203948}
38213949
38223950fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
......@@ -3858,14 +3986,12 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
38583986
38593987fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
38603988 const ev: *Evented = @ptrCast(@alignCast(userdata));
3861 _ = ev;
3862 var cancel_region: CancelRegion = .init();
3863 defer cancel_region.deinit();
3989 var sync: CancelRegion.Sync = try .init(ev);
3990 defer sync.deinit(ev);
38643991 while (true) {
3865 try cancel_region.await(.nothing);
3992 try sync.cancel_region.await(.nothing);
38663993 var wsz: winsize = undefined;
3867 const fd: usize = @bitCast(@as(isize, file.handle));
3868 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
3994 const rc = linux.ioctl(file.handle, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
38693995 switch (linux.errno(rc)) {
38703996 .SUCCESS => return true,
38713997 .INTR => continue,
......@@ -3923,10 +4049,10 @@ fn fileSetOwner(
39234049 group: ?File.Gid,
39244050) File.SetOwnerError!void {
39254051 const ev: *Evented = @ptrCast(@alignCast(userdata));
3926 var cancel_region: CancelRegion = .init();
3927 defer cancel_region.deinit();
4052 var sync: CancelRegion.Sync = try .init(ev);
4053 defer sync.deinit(ev);
39284054 try ev.fchownat(
3929 &cancel_region,
4055 &sync,
39304056 file.handle,
39314057 "",
39324058 owner orelse std.math.maxInt(linux.uid_t),
......@@ -3941,10 +4067,10 @@ fn fileSetPermissions(
39414067 permissions: File.Permissions,
39424068) File.SetPermissionsError!void {
39434069 const ev: *Evented = @ptrCast(@alignCast(userdata));
3944 var cancel_region: CancelRegion = .init();
3945 defer cancel_region.deinit();
4070 var sync: CancelRegion.Sync = try .init(ev);
4071 defer sync.deinit(ev);
39464072 ev.fchmodat(
3947 &cancel_region,
4073 &sync,
39484074 file.handle,
39494075 "",
39504076 permissions.toMode(),
......@@ -3965,10 +4091,10 @@ fn fileSetTimestamps(
39654091 options: File.SetTimestampsOptions,
39664092) File.SetTimestampsError!void {
39674093 const ev: *Evented = @ptrCast(@alignCast(userdata));
3968 var cancel_region: CancelRegion = .init();
3969 defer cancel_region.deinit();
4094 var sync: CancelRegion.Sync = try .init(ev);
4095 defer sync.deinit(ev);
39704096 try ev.utimensat(
3971 &cancel_region,
4097 &sync,
39724098 file.handle,
39734099 "",
39744100 if (options.modify_timestamp != .now or options.access_timestamp != .now) &.{
......@@ -3981,9 +4107,9 @@ fn fileSetTimestamps(
39814107
39824108fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {
39834109 const ev: *Evented = @ptrCast(@alignCast(userdata));
3984 var cancel_region: CancelRegion = .init();
3985 defer cancel_region.deinit();
3986 ev.flock(&cancel_region, file.handle, lock, .blocking) catch |err| switch (err) {
4110 var sync: CancelRegion.Sync = try .init(ev);
4111 defer sync.deinit(ev);
4112 ev.flock(&sync, file.handle, lock, .blocking) catch |err| switch (err) {
39874113 error.WouldBlock => unreachable, // blocking
39884114 else => |e| return e,
39894115 };
......@@ -3991,9 +4117,9 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
39914117
39924118fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {
39934119 const ev: *Evented = @ptrCast(@alignCast(userdata));
3994 var cancel_region: CancelRegion = .init();
3995 defer cancel_region.deinit();
3996 ev.flock(&cancel_region, file.handle, lock, switch (lock) {
4120 var sync: CancelRegion.Sync = try .init(ev);
4121 defer sync.deinit(ev);
4122 ev.flock(&sync, file.handle, lock, switch (lock) {
39974123 .none => .blocking,
39984124 .shared, .exclusive => .nonblocking,
39994125 }) catch |err| switch (err) {
......@@ -4005,9 +4131,9 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
40054131
40064132fn fileUnlock(userdata: ?*anyopaque, file: File) void {
40074133 const ev: *Evented = @ptrCast(@alignCast(userdata));
4008 var cancel_region: CancelRegion = .initBlocked();
4009 defer cancel_region.deinit();
4010 ev.flock(&cancel_region, file.handle, .none, .blocking) catch |err| switch (err) {
4134 var sync: CancelRegion.Sync = .initBlocked(ev);
4135 defer sync.deinit(ev);
4136 ev.flock(&sync, file.handle, .none, .blocking) catch |err| switch (err) {
40114137 error.Canceled => unreachable, // blocked
40124138 error.WouldBlock => unreachable, // blocking
40134139 error.SystemResources => return recoverableOsBugDetected(), // Resource deallocation.
......@@ -4018,9 +4144,9 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
40184144
40194145fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {
40204146 const ev: *Evented = @ptrCast(@alignCast(userdata));
4021 var cancel_region: CancelRegion = .init();
4022 defer cancel_region.deinit();
4023 ev.flock(&cancel_region, file.handle, .shared, .nonblocking) catch |err| switch (err) {
4147 var sync: CancelRegion.Sync = try .init(ev);
4148 defer sync.deinit(ev);
4149 ev.flock(&sync, file.handle, .shared, .nonblocking) catch |err| switch (err) {
40244150 error.WouldBlock => return errnoBug(.AGAIN), // File was not locked in exclusive mode.
40254151 error.SystemResources => return errnoBug(.NOLCK), // Lock already obtained.
40264152 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Lock already obtained.
......@@ -4030,9 +4156,9 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
40304156
40314157fn fileRealPath(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
40324158 const ev: *Evented = @ptrCast(@alignCast(userdata));
4033 var cancel_region: CancelRegion = .init();
4034 defer cancel_region.deinit();
4035 return ev.realPath(&cancel_region, file.handle, out_buffer);
4159 var sync: CancelRegion.Sync = try .init(ev);
4160 defer sync.deinit(ev);
4161 return ev.realPath(&sync, file.handle, out_buffer);
40364162}
40374163
40384164fn fileHardLink(
......@@ -4065,7 +4191,7 @@ fn fileMemoryMapCreate(
40654191 options: File.MemoryMap.CreateOptions,
40664192) File.MemoryMap.CreateError!File.MemoryMap {
40674193 const ev: *Evented = @ptrCast(@alignCast(userdata));
4068 _ = ev;
4194
40694195 const prot: linux.PROT = .{
40704196 .READ = options.protection.read,
40714197 .WRITE = options.protection.write,
......@@ -4078,10 +4204,10 @@ fn fileMemoryMapCreate(
40784204
40794205 const page_align = std.heap.page_size_min;
40804206
4081 var cancel_region: CancelRegion = .init();
4082 defer cancel_region.deinit();
4207 var sync: CancelRegion.Sync = try .init(ev);
4208 defer sync.deinit(ev);
40834209 const contents = while (true) {
4084 try cancel_region.await(.nothing);
4210 try sync.cancel_region.await(.nothing);
40854211 const casted_offset = std.math.cast(i64, options.offset) orelse return error.Unseekable;
40864212 const rc = linux.mmap(null, options.len, prot, flags, file.handle, casted_offset);
40874213 switch (linux.errno(rc)) {
......@@ -4189,11 +4315,10 @@ fn unlockStderr(userdata: ?*anyopaque) void {
41894315
41904316fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {
41914317 const ev: *Evented = @ptrCast(@alignCast(userdata));
4192 _ = ev;
4193 var cancel_region: CancelRegion = .init();
4194 defer cancel_region.deinit();
4318 var sync: CancelRegion.Sync = try .init(ev);
4319 defer sync.deinit(ev);
41954320 while (true) {
4196 try cancel_region.await(.nothing);
4321 try sync.cancel_region.await(.nothing);
41974322 switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {
41984323 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
41994324 .INTR => continue,
......@@ -4208,48 +4333,19 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
42084333
42094334fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void {
42104335 const ev: *Evented = @ptrCast(@alignCast(userdata));
4211 _ = ev;
42124336 if (dir.handle == linux.AT.FDCWD) return;
4213 var cancel_region: CancelRegion = .init();
4214 defer cancel_region.deinit();
4215 while (true) {
4216 try cancel_region.await(.nothing);
4217 switch (linux.errno(linux.fchdir(dir.handle))) {
4218 .SUCCESS => return,
4219 .INTR => continue,
4220 .ACCES => return error.AccessDenied,
4221 .NOTDIR => return error.NotDir,
4222 .IO => return error.FileSystem,
4223 .BADF => |err| return errnoBug(err),
4224 else => |err| return unexpectedErrno(err),
4225 }
4226 }
4337 var sync: CancelRegion.Sync = try .init(ev);
4338 defer sync.deinit(ev);
4339 return ev.fchdir(&sync, dir.handle);
42274340}
42284341
42294342fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {
42304343 const ev: *Evented = @ptrCast(@alignCast(userdata));
4231 _ = ev;
42324344 var path_buffer: [PATH_MAX]u8 = undefined;
42334345 const dir_path_posix = try pathToPosix(dir_path, &path_buffer);
4234 var cancel_region: CancelRegion = .init();
4235 defer cancel_region.deinit();
4236 while (true) {
4237 try cancel_region.await(.nothing);
4238 switch (linux.errno(linux.chdir(dir_path_posix))) {
4239 .SUCCESS => return,
4240 .INTR => continue,
4241 .ACCES => return error.AccessDenied,
4242 .IO => return error.FileSystem,
4243 .LOOP => return error.SymLinkLoop,
4244 .NAMETOOLONG => return error.NameTooLong,
4245 .NOENT => return error.FileNotFound,
4246 .NOMEM => return error.SystemResources,
4247 .NOTDIR => return error.NotDir,
4248 .ILSEQ => return error.BadPathName,
4249 .FAULT => |err| return errnoBug(err),
4250 else => |err| return unexpectedErrno(err),
4251 }
4252 }
4346 var sync: CancelRegion.Sync = try .init(ev);
4347 defer sync.deinit(ev);
4348 return ev.chdir(&sync, dir_path_posix);
42534349}
42544350
42554351fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError {
......@@ -4275,7 +4371,9 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42754371 });
42764372 };
42774373
4278 return execv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
4374 var sync: CancelRegion.Sync = try .init(ev);
4375 defer sync.deinit(ev);
4376 return ev.execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
42794377}
42804378
42814379fn processReplacePath(
......@@ -4293,12 +4391,12 @@ fn processReplacePath(
42934391fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
42944392 const ev: *Evented = @ptrCast(@alignCast(userdata));
42954393 const spawned = try ev.spawn(options);
4296 defer ev.close(spawned.err_fd);
4394 var cancel_region: CancelRegion = .initBlocked();
4395 defer cancel_region.deinit();
4396 defer ev.close(&cancel_region, spawned.err_fd);
42974397
42984398 // Wait for the child to report any errors in or before `execvpe`.
42994399 var child_err: ForkBailError = undefined;
4300 var cancel_region: CancelRegion = .initBlocked();
4301 defer cancel_region.deinit();
43024400 ev.readAll(&cancel_region, spawned.err_fd, @ptrCast(&child_err)) catch |read_err| {
43034401 switch (read_err) {
43044402 error.Canceled => unreachable, // blocked
......@@ -4336,6 +4434,8 @@ fn processSpawnPath(
43364434 @panic("TODO processSpawnPath");
43374435}
43384436
4437const prog_fileno = 3;
4438
43394439const Spawned = struct {
43404440 pid: pid_t,
43414441 err_fd: fd_t,
......@@ -4344,6 +4444,9 @@ const Spawned = struct {
43444444 stderr: ?File,
43454445};
43464446fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned {
4447 var cancel_region: CancelRegion = .init();
4448 defer cancel_region.deinit();
4449
43474450 // The child process does need to access (one end of) these pipes. However,
43484451 // we must initially set CLOEXEC to avoid a race condition. If another thread
43494452 // is racing to spawn a different child process, we don't want it to inherit
......@@ -4358,28 +4461,24 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
43584461
43594462 const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined;
43604463 errdefer if (options.stdin == .pipe) {
4361 ev.destroyPipe(stdin_pipe);
4464 ev.destroyPipe(&cancel_region, stdin_pipe);
43624465 };
43634466
43644467 const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined;
43654468 errdefer if (options.stdout == .pipe) {
4366 ev.destroyPipe(stdout_pipe);
4469 ev.destroyPipe(&cancel_region, stdout_pipe);
43674470 };
43684471
43694472 const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined;
43704473 errdefer if (options.stderr == .pipe) {
4371 ev.destroyPipe(stderr_pipe);
4474 ev.destroyPipe(&cancel_region, stderr_pipe);
43724475 };
43734476
43744477 const any_ignore =
43754478 options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore;
4376 const dev_null_fd = if (any_ignore) dev_null_fd: {
4377 var cancel_region: CancelRegion = .init();
4378 defer cancel_region.deinit();
4379 break :dev_null_fd try ev.null_fd.open(ev, &cancel_region, "/dev/null", .{
4380 .ACCMODE = .RDWR,
4381 });
4382 } else undefined;
4479 const dev_null_fd = if (any_ignore) try ev.null_fd.open(ev, &cancel_region, "/dev/null", .{
4480 .ACCMODE = .RDWR,
4481 }) else undefined;
43834482
43844483 const prog_pipe: [2]fd_t = if (options.progress_node.index != .none) pipe: {
43854484 // We use CLOEXEC for the same reason as in `pipe_flags`.
......@@ -4387,7 +4486,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
43874486 _ = linux.fcntl(pipe[0], linux.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2));
43884487 break :pipe pipe;
43894488 } else .{ -1, -1 };
4390 errdefer ev.destroyPipe(prog_pipe);
4489 errdefer ev.destroyPipe(&cancel_region, prog_pipe);
43914490
43924491 var arena_allocator = std.heap.ArenaAllocator.init(ev.allocator());
43934492 defer arena_allocator.deinit();
......@@ -4405,7 +4504,6 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
44054504 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
44064505 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
44074506
4408 const prog_fileno = 3;
44094507 comptime assert(@max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO) + 1 == prog_fileno);
44104508
44114509 const env_block = env_block: {
......@@ -4421,7 +4519,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
44214519 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
44224520 // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.
44234521 const err_pipe: [2]fd_t = try pipe2(.{ .CLOEXEC = true });
4424 errdefer ev.destroyPipe(err_pipe);
4522 errdefer ev.destroyPipe(&cancel_region, err_pipe);
44254523
44264524 try ev.scanEnviron(); // for PATH
44274525 const PATH = ev.environ.string.PATH orelse default_PATH;
......@@ -4439,78 +4537,33 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
44394537
44404538 if (pid_result == 0) {
44414539 defer comptime unreachable; // We are the child.
4442 _ = swapCancelProtection(ev, .blocked);
4443 const ep1 = err_pipe[1];
4444
4445 ev.setUpChildIo(options.stdin, stdin_pipe[0], linux.STDIN_FILENO, dev_null_fd) catch |err|
4446 ev.forkBail(ep1, err);
4447 ev.setUpChildIo(options.stdout, stdout_pipe[1], linux.STDOUT_FILENO, dev_null_fd) catch |err|
4448 ev.forkBail(ep1, err);
4449 ev.setUpChildIo(options.stderr, stderr_pipe[1], linux.STDERR_FILENO, dev_null_fd) catch |err|
4450 ev.forkBail(ep1, err);
4451
4452 switch (options.cwd) {
4453 .inherit => {},
4454 .dir => |cwd| processSetCurrentDir(ev, cwd) catch |err| ev.forkBail(ep1, err),
4455 .path => |cwd| processSetCurrentPath(ev, cwd) catch |err| ev.forkBail(ep1, err),
4456 }
4457
4458 // Must happen after fchdir above, the cwd file descriptor might be
4459 // equal to prog_fileno and be clobbered by this dup2 call.
4460 if (prog_pipe[1] != -1) dup2(prog_pipe[1], prog_fileno) catch |err| ev.forkBail(ep1, err);
4461
4462 if (options.gid) |gid| {
4463 switch (linux.errno(linux.setregid(gid, gid))) {
4464 .SUCCESS => {},
4465 .AGAIN => ev.forkBail(ep1, error.ResourceLimitReached),
4466 .INVAL => ev.forkBail(ep1, error.InvalidUserId),
4467 .PERM => ev.forkBail(ep1, error.PermissionDenied),
4468 else => ev.forkBail(ep1, error.Unexpected),
4469 }
4470 }
4471
4472 if (options.uid) |uid| {
4473 switch (linux.errno(linux.setreuid(uid, uid))) {
4474 .SUCCESS => {},
4475 .AGAIN => ev.forkBail(ep1, error.ResourceLimitReached),
4476 .INVAL => ev.forkBail(ep1, error.InvalidUserId),
4477 .PERM => ev.forkBail(ep1, error.PermissionDenied),
4478 else => ev.forkBail(ep1, error.Unexpected),
4479 }
4480 }
4481
4482 if (options.pgid) |pid| {
4483 switch (linux.errno(linux.setpgid(0, pid))) {
4484 .SUCCESS => {},
4485 .ACCES => ev.forkBail(ep1, error.ProcessAlreadyExec),
4486 .INVAL => ev.forkBail(ep1, error.InvalidProcessGroupId),
4487 .PERM => ev.forkBail(ep1, error.PermissionDenied),
4488 else => ev.forkBail(ep1, error.Unexpected),
4489 }
4490 }
4491
4492 if (options.start_suspended) {
4493 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
4494 .SUCCESS => {},
4495 .PERM => ev.forkBail(ep1, error.PermissionDenied),
4496 else => ev.forkBail(ep1, error.Unexpected),
4497 }
4498 }
4499
4500 const err = execv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
4501 ev.forkBail(ep1, err);
4540 var sync: CancelRegion.Sync = .{ .cancel_region = .initBlocked() };
4541 const err = ev.setUpChild(&sync, .{
4542 .stdin_pipe = stdin_pipe[0],
4543 .stdout_pipe = stdout_pipe[1],
4544 .stderr_pipe = stderr_pipe[1],
4545 .dev_null_fd = dev_null_fd,
4546 .prog_pipe = prog_pipe[1],
4547 .argv_buf = argv_buf,
4548 .env_block = env_block,
4549 .PATH = PATH,
4550 .spawn = options,
4551 });
4552 ev.writeAll(&sync.cancel_region, err_pipe[1], @ptrCast(&err)) catch {};
4553 const exit = if (builtin.single_threaded) linux.exit else linux.exit_group;
4554 exit(1);
45024555 }
45034556
45044557 const pid: pid_t = @intCast(pid_result); // We are the parent.
45054558 errdefer comptime unreachable; // The child is forked; we must not error from now on
45064559
4507 ev.close(err_pipe[1]); // make sure only the child holds the write end open
4560 ev.close(&cancel_region, err_pipe[1]); // make sure only the child holds the write end open
45084561
4509 if (options.stdin == .pipe) ev.close(stdin_pipe[0]);
4510 if (options.stdout == .pipe) ev.close(stdout_pipe[1]);
4511 if (options.stderr == .pipe) ev.close(stderr_pipe[1]);
4562 if (options.stdin == .pipe) ev.close(&cancel_region, stdin_pipe[0]);
4563 if (options.stdout == .pipe) ev.close(&cancel_region, stdout_pipe[1]);
4564 if (options.stderr == .pipe) ev.close(&cancel_region, stderr_pipe[1]);
45124565
4513 if (prog_pipe[1] != -1) ev.close(prog_pipe[1]);
4566 if (prog_pipe[1] != -1) ev.close(&cancel_region, prog_pipe[1]);
45144567
45154568 options.progress_node.setIpcFile(ev, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
45164569
......@@ -4546,24 +4599,127 @@ pub fn pipe2(flags: linux.O) PipeError![2]fd_t {
45464599 else => |err| return unexpectedErrno(err),
45474600 }
45484601}
4549fn destroyPipe(ev: *Evented, pipe: [2]fd_t) void {
4550 if (pipe[0] != -1) ev.close(pipe[0]);
4551 if (pipe[0] != pipe[1]) ev.close(pipe[1]);
4602fn destroyPipe(ev: *Evented, cancel_region: *CancelRegion, pipe: [2]fd_t) void {
4603 if (pipe[0] != -1) ev.close(cancel_region, pipe[0]);
4604 if (pipe[0] != pipe[1]) ev.close(cancel_region, pipe[1]);
4605}
4606
4607/// Errors that can occur between fork() and execv()
4608const ForkBailError = process.SetCurrentDirError || ChdirError ||
4609 process.SpawnError || process.ReplaceError;
4610fn setUpChild(
4611 ev: *Evented,
4612 sync: *CancelRegion.Sync,
4613 options: struct {
4614 stdin_pipe: fd_t,
4615 stdout_pipe: fd_t,
4616 stderr_pipe: fd_t,
4617 dev_null_fd: fd_t,
4618 prog_pipe: fd_t,
4619 argv_buf: [:null]?[*:0]const u8,
4620 env_block: process.Environ.Block,
4621 PATH: []const u8,
4622 spawn: process.SpawnOptions,
4623 },
4624) ForkBailError {
4625 try ev.setUpChildIo(
4626 sync,
4627 options.spawn.stdin,
4628 options.stdin_pipe,
4629 linux.STDIN_FILENO,
4630 options.dev_null_fd,
4631 );
4632 try ev.setUpChildIo(
4633 sync,
4634 options.spawn.stdout,
4635 options.stdout_pipe,
4636 linux.STDOUT_FILENO,
4637 options.dev_null_fd,
4638 );
4639 try ev.setUpChildIo(
4640 sync,
4641 options.spawn.stderr,
4642 options.stderr_pipe,
4643 linux.STDERR_FILENO,
4644 options.dev_null_fd,
4645 );
4646
4647 switch (options.spawn.cwd) {
4648 .inherit => {},
4649 .dir => |cwd_dir| try ev.fchdir(sync, cwd_dir.handle),
4650 .path => |cwd_path| {
4651 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4652 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4653 try ev.chdir(sync, cwd_path_posix);
4654 },
4655 }
4656
4657 // Must happen after fchdir above, the cwd file descriptor might be
4658 // equal to prog_fileno and be clobbered by this dup2 call.
4659 if (options.prog_pipe != -1) try ev.dup2(sync, options.prog_pipe, prog_fileno);
4660
4661 if (options.spawn.gid) |gid| {
4662 switch (linux.errno(linux.setregid(gid, gid))) {
4663 .SUCCESS => {},
4664 .AGAIN => return error.ResourceLimitReached,
4665 .INVAL => return error.InvalidUserId,
4666 .PERM => return error.PermissionDenied,
4667 else => return error.Unexpected,
4668 }
4669 }
4670
4671 if (options.spawn.uid) |uid| {
4672 switch (linux.errno(linux.setreuid(uid, uid))) {
4673 .SUCCESS => {},
4674 .AGAIN => return error.ResourceLimitReached,
4675 .INVAL => return error.InvalidUserId,
4676 .PERM => return error.PermissionDenied,
4677 else => return error.Unexpected,
4678 }
4679 }
4680
4681 if (options.spawn.pgid) |pid| {
4682 switch (linux.errno(linux.setpgid(0, pid))) {
4683 .SUCCESS => {},
4684 .ACCES => return error.ProcessAlreadyExec,
4685 .INVAL => return error.InvalidProcessGroupId,
4686 .PERM => return error.PermissionDenied,
4687 else => return error.Unexpected,
4688 }
4689 }
4690
4691 if (options.spawn.start_suspended) {
4692 switch (linux.errno(linux.kill(linux.getpid(), .STOP))) {
4693 .SUCCESS => {},
4694 .PERM => return error.PermissionDenied,
4695 else => return error.Unexpected,
4696 }
4697 }
4698
4699 return ev.execv(
4700 sync,
4701 options.spawn.expand_arg0,
4702 options.argv_buf.ptr[0].?,
4703 options.argv_buf.ptr,
4704 options.env_block,
4705 options.PATH,
4706 );
45524707}
45534708
45544709fn setUpChildIo(
45554710 ev: *Evented,
4711 sync: *CancelRegion.Sync,
45564712 stdio: process.SpawnOptions.StdIo,
45574713 pipe_fd: fd_t,
45584714 std_fileno: i32,
45594715 dev_null_fd: fd_t,
45604716) !void {
45614717 switch (stdio) {
4562 .pipe => try dup2(pipe_fd, std_fileno),
4563 .close => ev.close(std_fileno),
4718 .pipe => try ev.dup2(sync, pipe_fd, std_fileno),
4719 .close => ev.close(&sync.cancel_region, std_fileno),
45644720 .inherit => {},
4565 .ignore => try dup2(dev_null_fd, std_fileno),
4566 .file => |file| try dup2(file.handle, std_fileno),
4721 .ignore => try ev.dup2(sync, dev_null_fd, std_fileno),
4722 .file => |file| try ev.dup2(sync, file.handle, std_fileno),
45674723 }
45684724}
45694725
......@@ -4571,11 +4727,10 @@ pub const DupError = error{
45714727 ProcessFdQuotaExceeded,
45724728 SystemResources,
45734729} || Io.UnexpectedError || Io.Cancelable;
4574pub fn dup2(old_fd: fd_t, new_fd: fd_t) DupError!void {
4575 var cancel_region: CancelRegion = .init();
4576 defer cancel_region.deinit();
4730pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void {
4731 _ = ev;
45774732 while (true) {
4578 try cancel_region.await(.nothing);
4733 try sync.cancel_region.await(.nothing);
45794734 switch (linux.errno(linux.dup2(old_fd, new_fd))) {
45804735 .SUCCESS => {},
45814736 .BUSY, .INTR => continue,
......@@ -4588,20 +4743,9 @@ pub fn dup2(old_fd: fd_t, new_fd: fd_t) DupError!void {
45884743 }
45894744}
45904745
4591/// Errors that can occur between fork() and execv()
4592const ForkBailError = process.SetCurrentDirError || ChdirError ||
4593 process.SpawnError || process.ReplaceError;
4594/// Child of fork calls this to report an error to the fork parent. Then the
4595/// child exits.
4596fn forkBail(ev: *Evented, fd: fd_t, err: ForkBailError) noreturn {
4597 var cancel_region: CancelRegion = .initBlocked();
4598 defer cancel_region.deinit();
4599 ev.writeAll(&cancel_region, fd, @ptrCast(&err)) catch {};
4600 const exit = if (builtin.single_threaded) linux.exit else linux.exit_group;
4601 exit(1);
4602}
4603
46044746fn execv(
4747 ev: *Evented,
4748 sync: *CancelRegion.Sync,
46054749 arg0_expand: process.ArgExpansion,
46064750 file: [*:0]const u8,
46074751 child_argv: [*:null]?[*:0]const u8,
......@@ -4609,7 +4753,7 @@ fn execv(
46094753 PATH: []const u8,
46104754) process.ReplaceError {
46114755 const file_slice = std.mem.sliceTo(file, 0);
4612 if (std.mem.findScalar(u8, file_slice, '/') != null) return execvPath(file, child_argv, env_block);
4756 if (std.mem.findScalar(u8, file_slice, '/') != null) return ev.execvPath(sync, file, child_argv, env_block);
46134757
46144758 // Use of PATH_MAX here is valid as the path_buf will be passed
46154759 // directly to the operating system in posixExecvPath.
......@@ -4637,7 +4781,7 @@ fn execv(
46374781 .expand => child_argv[0] = full_path,
46384782 .no_expand => {},
46394783 }
4640 err = execvPath(full_path, child_argv, env_block);
4784 err = ev.execvPath(sync, full_path, child_argv, env_block);
46414785 switch (err) {
46424786 error.AccessDenied => seen_eacces = true,
46434787 error.FileNotFound, error.NotDir => {},
......@@ -4649,13 +4793,14 @@ fn execv(
46494793}
46504794/// This function ignores PATH environment variable.
46514795pub fn execvPath(
4796 ev: *Evented,
4797 sync: *CancelRegion.Sync,
46524798 path: [*:0]const u8,
46534799 child_argv: [*:null]const ?[*:0]const u8,
46544800 env_block: process.Environ.PosixBlock,
46554801) process.ReplaceError {
4656 var cancel_region: CancelRegion = .init();
4657 defer cancel_region.deinit();
4658 try cancel_region.await(.nothing);
4802 _ = ev;
4803 try sync.cancel_region.await(.nothing);
46594804 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {
46604805 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
46614806 .@"2BIG" => return error.SystemResources,
......@@ -4680,14 +4825,15 @@ pub fn execvPath(
46804825
46814826fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term {
46824827 const ev: *Evented = @ptrCast(@alignCast(userdata));
4683 defer ev.childCleanup(child);
4828
4829 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
4830 defer maybe_sync.deinit(ev);
4831 defer ev.childCleanup(maybe_sync.cancelRegion(), child);
46844832
46854833 const pid = child.id.?;
46864834 var info: linux.siginfo_t = undefined;
4687 var cancel_region: CancelRegion = .init();
4688 defer cancel_region.deinit();
46894835 while (true) {
4690 const thread = try cancel_region.awaitIoUring();
4836 const thread = try maybe_sync.cancel_region.awaitIoUring();
46914837 thread.enqueue().* = .{
46924838 .opcode = .WAITID,
46934839 .flags = 0,
......@@ -4697,7 +4843,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
46974843 .addr = 0,
46984844 .len = @intFromEnum(linux.P.PID),
46994845 .rw_flags = 0,
4700 .user_data = @intFromPtr(cancel_region.fiber),
4846 .user_data = @intFromPtr(maybe_sync.cancel_region.fiber),
47014847 .buf_index = 0,
47024848 .personality = 0,
47034849 .splice_fd_in = linux.W.EXITED |
......@@ -4706,27 +4852,30 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
47064852 .resv = 0,
47074853 };
47084854 ev.yield(null, .nothing);
4709 switch (cancel_region.errno()) {
4855 switch (maybe_sync.cancel_region.errno()) {
47104856 .SUCCESS => {
4711 if (child.request_resource_usage_statistics) while (true) {
4712 try cancel_region.await(.nothing);
4713 var rusage: linux.rusage = undefined;
4714 switch (linux.errno(linux.waitid(
4715 .PID,
4716 pid,
4717 &info,
4718 linux.W.EXITED | linux.W.NOHANG,
4719 &rusage,
4720 ))) {
4721 .SUCCESS => {
4722 child.resource_usage_statistics.rusage = rusage;
4723 break;
4724 },
4725 .INTR, .CANCELED => continue,
4726 .CHILD => |err| return errnoBug(err), // Double-free.
4727 else => |err| return unexpectedErrno(err),
4857 if (child.request_resource_usage_statistics) {
4858 const sync = try maybe_sync.enterSync(ev);
4859 while (true) {
4860 try sync.cancel_region.await(.nothing);
4861 var rusage: linux.rusage = undefined;
4862 switch (linux.errno(linux.waitid(
4863 .PID,
4864 pid,
4865 &info,
4866 linux.W.EXITED | linux.W.NOHANG,
4867 &rusage,
4868 ))) {
4869 .SUCCESS => {
4870 child.resource_usage_statistics.rusage = rusage;
4871 break;
4872 },
4873 .INTR, .CANCELED => continue,
4874 .CHILD => |err| return errnoBug(err), // Double-free.
4875 else => |err| return unexpectedErrno(err),
4876 }
47284877 }
4729 };
4878 }
47304879 const status: u32 = @bitCast(info.fields.common.second.sigchld.status);
47314880 const code: linux.CLD = @enumFromInt(info.code);
47324881 return switch (code) {
......@@ -4745,11 +4894,12 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
47454894
47464895fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
47474896 const ev: *Evented = @ptrCast(@alignCast(userdata));
4748 defer ev.childCleanup(child);
4897
4898 var maybe_sync: CancelRegion.Sync.Maybe = .{ .sync = .initBlocked(ev) };
4899 defer maybe_sync.deinit(ev);
4900 defer ev.childCleanup(maybe_sync.cancelRegion(), child);
47494901
47504902 const pid = child.id.?;
4751 var cancel_region: CancelRegion = .initBlocked();
4752 defer cancel_region.deinit();
47534903 while (true) switch (linux.errno(linux.kill(pid, .TERM))) {
47544904 .SUCCESS => break,
47554905 .INTR => continue,
......@@ -4758,10 +4908,11 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
47584908 .SRCH => |err| return errnoBug(err) catch {},
47594909 else => |err| return unexpectedErrno(err) catch {},
47604910 };
4911 maybe_sync.leaveSync(ev);
47614912
47624913 var info: linux.siginfo_t = undefined;
47634914 while (true) {
4764 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {
4915 const thread = maybe_sync.cancel_region.awaitIoUring() catch |err| switch (err) {
47654916 error.Canceled => unreachable, // blocked
47664917 };
47674918 thread.enqueue().* = .{
......@@ -4773,7 +4924,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
47734924 .addr = 0,
47744925 .len = @intFromEnum(linux.P.PID),
47754926 .rw_flags = 0,
4776 .user_data = @intFromPtr(cancel_region.fiber),
4927 .user_data = @intFromPtr(maybe_sync.cancel_region.fiber),
47774928 .buf_index = 0,
47784929 .personality = 0,
47794930 .splice_fd_in = linux.W.EXITED,
......@@ -4781,7 +4932,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
47814932 .resv = 0,
47824933 };
47834934 ev.yield(null, .nothing);
4784 switch (cancel_region.errno()) {
4935 switch (maybe_sync.cancel_region.errno()) {
47854936 .SUCCESS => return,
47864937 .INTR, .CANCELED => continue,
47874938 .CHILD => |err| return errnoBug(err) catch {}, // Double-free.
......@@ -4790,17 +4941,17 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
47904941 }
47914942}
47924943
4793fn childCleanup(ev: *Evented, child: *process.Child) void {
4944fn childCleanup(ev: *Evented, cancel_region: *CancelRegion, child: *process.Child) void {
47944945 if (child.stdin) |*stdin| {
4795 ev.close(stdin.handle);
4946 ev.close(cancel_region, stdin.handle);
47964947 child.stdin = null;
47974948 }
47984949 if (child.stdout) |*stdout| {
4799 ev.close(stdout.handle);
4950 ev.close(cancel_region, stdout.handle);
48004951 child.stdout = null;
48014952 }
48024953 if (child.stderr) |*stderr| {
4803 ev.close(stderr.handle);
4954 ev.close(cancel_region, stderr.handle);
48044955 child.stderr = null;
48054956 }
48064957 child.id = null;
......@@ -4985,14 +5136,14 @@ fn netBindIp(
49855136) net.IpAddress.BindError!net.Socket {
49865137 const ev: *Evented = @ptrCast(@alignCast(userdata));
49875138 const family = posixAddressFamily(address);
4988 var cancel_region: CancelRegion = .init();
4989 defer cancel_region.deinit();
4990 const socket_fd = try ev.socket(&cancel_region, family, options);
4991 errdefer ev.close(socket_fd);
5139 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
5140 defer maybe_sync.deinit(ev);
5141 const socket_fd = try ev.socket(&maybe_sync.cancel_region, family, options);
5142 errdefer ev.close(maybe_sync.cancelRegion(), socket_fd);
49925143 var storage: PosixAddress = undefined;
49935144 var addr_len = addressToPosix(address, &storage);
4994 try ev.bind(&cancel_region, socket_fd, &storage.any, addr_len);
4995 try ev.getsockname(&cancel_region, socket_fd, &storage.any, &addr_len);
5145 try ev.bind(&maybe_sync.cancel_region, socket_fd, &storage.any, addr_len);
5146 try ev.getsockname(try maybe_sync.enterSync(ev), socket_fd, &storage.any, &addr_len);
49965147 return .{
49975148 .handle = socket_fd,
49985149 .address = addressFromPosix(&storage),
......@@ -5268,7 +5419,9 @@ fn netWriteFileUnavailable(
52685419
52695420fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
52705421 const ev: *Evented = @ptrCast(@alignCast(userdata));
5271 for (handles) |handle| ev.close(handle);
5422 var cancel_region: CancelRegion = .init();
5423 defer cancel_region.deinit();
5424 for (handles) |handle| ev.close(&cancel_region, handle);
52725425}
52735426
52745427fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
......@@ -5407,9 +5560,28 @@ fn bind(
54075560 }
54085561}
54095562
5410fn close(ev: *Evented, fd: fd_t) void {
5411 var cancel_region: CancelRegion = .initBlocked();
5412 defer cancel_region.deinit();
5563fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {
5564 _ = ev;
5565 while (true) {
5566 try sync.cancel_region.await(.nothing);
5567 switch (linux.errno(linux.chdir(path))) {
5568 .SUCCESS => return,
5569 .INTR => continue,
5570 .ACCES => return error.AccessDenied,
5571 .IO => return error.FileSystem,
5572 .LOOP => return error.SymLinkLoop,
5573 .NAMETOOLONG => return error.NameTooLong,
5574 .NOENT => return error.FileNotFound,
5575 .NOMEM => return error.SystemResources,
5576 .NOTDIR => return error.NotDir,
5577 .ILSEQ => return error.BadPathName,
5578 .FAULT => |err| return errnoBug(err),
5579 else => |err| return unexpectedErrno(err),
5580 }
5581 }
5582}
5583
5584fn close(ev: *Evented, cancel_region: *CancelRegion, fd: fd_t) void {
54135585 while (true) {
54145586 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {
54155587 error.Canceled => unreachable, // blocked
......@@ -5440,9 +5612,26 @@ fn close(ev: *Evented, fd: fd_t) void {
54405612 }
54415613}
54425614
5615fn fchdir(ev: *Evented, sync: *CancelRegion.Sync, dir: fd_t) process.SetCurrentDirError!void {
5616 _ = ev;
5617 if (dir == linux.AT.FDCWD) return;
5618 while (true) {
5619 try sync.cancel_region.await(.nothing);
5620 switch (linux.errno(linux.fchdir(dir))) {
5621 .SUCCESS => return,
5622 .INTR => continue,
5623 .ACCES => return error.AccessDenied,
5624 .NOTDIR => return error.NotDir,
5625 .IO => return error.FileSystem,
5626 .BADF => |err| return errnoBug(err),
5627 else => |err| return unexpectedErrno(err),
5628 }
5629 }
5630}
5631
54435632fn fchmodat(
54445633 ev: *Evented,
5445 cancel_region: *CancelRegion,
5634 sync: *CancelRegion.Sync,
54465635 dir: fd_t,
54475636 path: [*:0]const u8,
54485637 mode: linux.mode_t,
......@@ -5450,7 +5639,7 @@ fn fchmodat(
54505639) Dir.SetFilePermissionsError!void {
54515640 _ = ev;
54525641 while (true) {
5453 try cancel_region.await(.nothing);
5642 try sync.cancel_region.await(.nothing);
54545643 switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {
54555644 .SUCCESS => return,
54565645 .INTR => continue,
......@@ -5473,7 +5662,7 @@ fn fchmodat(
54735662
54745663fn fchownat(
54755664 ev: *Evented,
5476 cancel_region: *CancelRegion,
5665 sync: *CancelRegion.Sync,
54775666 dir: fd_t,
54785667 path: [*:0]const u8,
54795668 owner: linux.uid_t,
......@@ -5482,7 +5671,7 @@ fn fchownat(
54825671) File.SetOwnerError!void {
54835672 _ = ev;
54845673 while (true) {
5485 try cancel_region.await(.nothing);
5674 try sync.cancel_region.await(.nothing);
54865675 switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {
54875676 .SUCCESS => return,
54885677 .INTR => continue,
......@@ -5504,13 +5693,13 @@ fn fchownat(
55045693
55055694fn flock(
55065695 ev: *Evented,
5507 cancel_region: *CancelRegion,
5696 sync: *CancelRegion.Sync,
55085697 fd: fd_t,
55095698 op: File.Lock,
55105699 blocking: enum { blocking, nonblocking },
55115700) (File.LockError || error{WouldBlock})!void {
55125701 while (true) {
5513 try cancel_region.await(.nothing);
5702 try sync.cancel_region.await(.nothing);
55145703 switch (linux.errno(linux.flock(fd, LOCK.NB | @as(i32, switch (op) {
55155704 .none => LOCK.UN,
55165705 .shared => LOCK.SH,
......@@ -5522,7 +5711,7 @@ fn flock(
55225711 .INVAL => |err| return errnoBug(err), // invalid parameters
55235712 .NOLCK => return error.SystemResources,
55245713 .AGAIN => {
5525 const thread = try cancel_region.awaitIoUring();
5714 const thread = try sync.cancel_region.awaitIoUring();
55265715 thread.enqueue().* = .{
55275716 .opcode = .NOP,
55285717 .flags = 0,
......@@ -5532,7 +5721,7 @@ fn flock(
55325721 .addr = 0,
55335722 .len = 0,
55345723 .rw_flags = 0,
5535 .user_data = @intFromPtr(cancel_region.fiber),
5724 .user_data = @intFromPtr(sync.cancel_region.fiber),
55365725 .buf_index = 0,
55375726 .personality = 0,
55385727 .splice_fd_in = 0,
......@@ -5540,7 +5729,7 @@ fn flock(
55405729 .resv = 0,
55415730 };
55425731 ev.yield(null, .nothing);
5543 switch (cancel_region.errno()) {
5732 switch (sync.cancel_region.errno()) {
55445733 .SUCCESS, .INTR, .CANCELED => {},
55455734 else => unreachable,
55465735 }
......@@ -5557,14 +5746,14 @@ fn flock(
55575746
55585747fn getsockname(
55595748 ev: *Evented,
5560 cancel_region: *CancelRegion,
5749 sync: *CancelRegion.Sync,
55615750 socket_fd: fd_t,
55625751 addr: *linux.sockaddr,
55635752 addr_len: *linux.socklen_t,
55645753) !void {
55655754 _ = ev;
55665755 while (true) {
5567 try cancel_region.await(.nothing);
5756 try sync.cancel_region.await(.nothing);
55685757 switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {
55695758 .SUCCESS => return,
55705759 .INTR => continue,
......@@ -5633,14 +5822,14 @@ fn linkat(
56335822
56345823fn lseek(
56355824 ev: *Evented,
5636 cancel_region: *CancelRegion,
5825 sync: *CancelRegion.Sync,
56375826 fd: fd_t,
56385827 offset: u64,
56395828 whence: u32,
56405829) File.SeekError!void {
56415830 _ = ev;
56425831 while (true) {
5643 try cancel_region.await(.nothing);
5832 try sync.cancel_region.await(.nothing);
56445833 var result: u64 = undefined;
56455834 switch (linux.errno(switch (@sizeOf(usize)) {
56465835 else => comptime unreachable,
......@@ -5837,7 +6026,7 @@ fn readAll(
58376026
58386027fn realPath(
58396028 ev: *Evented,
5840 cancel_region: *CancelRegion,
6029 sync: *CancelRegion.Sync,
58416030 fd: fd_t,
58426031 out_buffer: []u8,
58436032) File.RealPathError!usize {
......@@ -5846,7 +6035,7 @@ fn realPath(
58466035 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{fd}, 0) catch
58476036 unreachable;
58486037 while (true) {
5849 try cancel_region.await(.nothing);
6038 try sync.cancel_region.await(.nothing);
58506039 const rc = linux.readlink(proc_path, out_buffer.ptr, out_buffer.len);
58516040 switch (linux.errno(rc)) {
58526041 .SUCCESS => return rc,
......@@ -6025,7 +6214,7 @@ fn socket(
60256214 else => |err| return unexpectedErrno(err),
60266215 }
60276216 };
6028 errdefer ev.close(socket_fd);
6217 errdefer ev.close(cancel_region, socket_fd);
60296218
60306219 if (options.ip6_only) {
60316220 if (linux.IPV6 == void) return error.OptionUnsupported;
......@@ -6103,7 +6292,7 @@ fn urandomReadAll(
61036292
61046293fn utimensat(
61056294 ev: *Evented,
6106 cancel_region: *CancelRegion,
6295 sync: *CancelRegion.Sync,
61076296 dir: fd_t,
61086297 path: [*:0]const u8,
61096298 times: ?*const [2]linux.timespec,
......@@ -6111,7 +6300,7 @@ fn utimensat(
61116300) File.SetTimestampsError!void {
61126301 _ = ev;
61136302 while (true) {
6114 try cancel_region.await(.nothing);
6303 try sync.cancel_region.await(.nothing);
61156304 switch (linux.errno(linux.utimensat(dir, path, times, flags))) {
61166305 .SUCCESS => return,
61176306 .INTR => continue,
lib/std/Io/Threaded.zig+3-4
......@@ -1543,7 +1543,7 @@ pub const InitOptions = struct {
15431543 /// this limit, calls to `Io.async` when all threads are busy run the task
15441544 /// immediately.
15451545 ///
1546 /// Defaults to a number equal to logical CPU cores.
1546 /// Defaults to one less than the number of logical CPU cores.
15471547 ///
15481548 /// Protected by `Threaded.mutex` once the I/O instance is already in use. See
15491549 /// `setAsyncLimit`.
......@@ -1552,8 +1552,7 @@ pub const InitOptions = struct {
15521552 /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread
15531553 /// pool size.
15541554 ///
1555 /// concurrent tasks. After this number, calls to `Io.concurrent` return
1556 /// `error.ConcurrencyUnavailable`.
1555 /// After this number, calls to `Io.concurrent` return `error.ConcurrencyUnavailable`.
15571556 concurrent_limit: Io.Limit = .unlimited,
15581557 /// Affects the following operations:
15591558 /// * `processExecutablePath` on OpenBSD and Haiku.
......@@ -1562,7 +1561,7 @@ pub const InitOptions = struct {
15621561 /// * `fileIsTty`
15631562 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
15641563 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
1565 environ: process.Environ,
1564 environ: process.Environ = .empty,
15661565 /// If set to `true`, `File.MemoryMap` APIs will always take the fallback path.
15671566 disable_memory_mapping: bool = false,
15681567};