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,...@@ -59,7 +59,9 @@ backing_allocator: Allocator,
59main_fiber_buffer: [59main_fiber_buffer: [
60 std.mem.alignForward(usize, @sizeOf(Fiber), @alignOf(Completion)) + @sizeOf(Completion)60 std.mem.alignForward(usize, @sizeOf(Fiber), @alignOf(Completion)) + @sizeOf(Completion)
61]u8 align(@max(@alignOf(Fiber), @alignOf(Completion))),61]u8 align(@max(@alignOf(Fiber), @alignOf(Completion))),
62log2_ring_entries: u4,
62threads: Thread.List,63threads: Thread.List,
64sync_limit: ?Io.Semaphore,
6365
64stderr_mutex: Io.Mutex,66stderr_mutex: Io.Mutex,
65stderr_writer: File.Writer = .{67stderr_writer: File.Writer = .{
...@@ -84,10 +86,9 @@ csprng: Csprng,...@@ -84,10 +86,9 @@ csprng: Csprng,
84/// Empirically saw glibc complain about 256KB.86/// Empirically saw glibc complain about 256KB.
85const idle_stack_size = 512 * 1024;87const idle_stack_size = 512 * 1024;
8688
87const max_idle_search = 4;89const max_idle_search = 1;
88const max_steal_ready_search = 4;90const max_steal_ready_search = 2;
8991const max_steal_free_search = 4;
90const io_uring_entries = 64;
9192
92const Thread = struct {93const Thread = struct {
93 required_align: void align(4),94 required_align: void align(4),
...@@ -95,9 +96,11 @@ const Thread = struct {...@@ -95,9 +96,11 @@ const Thread = struct {
95 idle_context: Context,96 idle_context: Context,
96 current_context: *Context,97 current_context: *Context,
97 ready_queue: ?*Fiber,98 ready_queue: ?*Fiber,
99 free_queue: ?*Fiber,
98 io_uring: IoUring,100 io_uring: IoUring,
99 idle_search_index: u32,101 idle_search_index: u32,
100 steal_ready_search_index: u32,102 steal_ready_search_index: u32,
103 steal_free_search_index: u32,
101 name_arena: if (tracy.enable) std.heap.ArenaAllocator.State else struct {},104 name_arena: if (tracy.enable) std.heap.ArenaAllocator.State else struct {},
102 csprng: Csprng,105 csprng: Csprng,
103106
...@@ -107,6 +110,15 @@ const Thread = struct {...@@ -107,6 +110,15 @@ const Thread = struct {
107 return self.?;110 return self.?;
108 }111 }
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
110 fn currentFiber(thread: *Thread) *Fiber {122 fn currentFiber(thread: *Thread) *Fiber {
111 assert(thread.current_context != &thread.idle_context);123 assert(thread.current_context != &thread.idle_context);
112 return @fieldParentPtr("context", thread.current_context);124 return @fieldParentPtr("context", thread.current_context);
...@@ -144,6 +156,7 @@ const Fiber = struct {...@@ -144,6 +156,7 @@ const Fiber = struct {
144 status: union(enum) {156 status: union(enum) {
145 queue_next: ?*Fiber,157 queue_next: ?*Fiber,
146 awaiting_group: Group,158 awaiting_group: Group,
159 free_next: ?*Fiber,
147 },160 },
148 cancel_status: CancelStatus,161 cancel_status: CancelStatus,
149 cancel_protection: CancelProtection,162 cancel_protection: CancelProtection,
...@@ -235,7 +248,7 @@ const Fiber = struct {...@@ -235,7 +248,7 @@ const Fiber = struct {
235 }248 }
236 };249 };
237250
238 const finished: ?*Fiber = @ptrFromInt(@alignOf(Thread));251 const finished: ?*Fiber = @ptrFromInt(@alignOf(Fiber));
239252
240 const max_result_align: Alignment = .@"16";253 const max_result_align: Alignment = .@"16";
241 const max_result_size = max_result_align.forward(512);254 const max_result_size = max_result_align.forward(512);
...@@ -259,13 +272,49 @@ const Fiber = struct {...@@ -259,13 +272,49 @@ const Fiber = struct {
259 }272 }
260273
261 fn create(ev: *Evented) error{OutOfMemory}!*Fiber {274 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);
262 return @ptrCast(try ev.allocator().alignedAlloc(u8, .of(Fiber), allocation_size));303 return @ptrCast(try ev.allocator().alignedAlloc(u8, .of(Fiber), allocation_size));
263 }304 }
264305
265 fn destroy(fiber: *Fiber, gpa: std.mem.Allocator) void {306 fn destroy(fiber: *Fiber) void {
266 log.debug("destroying {*}", .{fiber});307 const thread: *Thread = .current();
267 assert(fiber.status.queue_next == null);308 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;
269 }318 }
270319
271 fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 {320 fn allocatedSlice(f: *Fiber) []align(@alignOf(Fiber)) u8 {
...@@ -416,6 +465,62 @@ const CancelRegion = struct {...@@ -416,6 +465,62 @@ const CancelRegion = struct {
416 fn errno(cancel_region: *const CancelRegion) linux.E {465 fn errno(cancel_region: *const CancelRegion) linux.E {
417 return cancel_region.completion().errno();466 return cancel_region.completion().errno();
418 }467 }
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 };
419};524};
420525
421const CachedFd = struct {526const CachedFd = struct {
...@@ -685,7 +790,7 @@ fn fileMemoryMapSetLength(...@@ -685,7 +790,7 @@ fn fileMemoryMapSetLength(
685 new_len: usize,790 new_len: usize,
686) File.MemoryMap.SetLengthError!void {791) File.MemoryMap.SetLengthError!void {
687 const ev: *Evented = @ptrCast(@alignCast(userdata));792 const ev: *Evented = @ptrCast(@alignCast(userdata));
688 _ = ev;793
689 const page_size = std.heap.pageSize();794 const page_size = std.heap.pageSize();
690 const alignment: Alignment = .fromByteUnits(page_size);795 const alignment: Alignment = .fromByteUnits(page_size);
691 const page_align = std.heap.page_size_min;796 const page_align = std.heap.page_size_min;
...@@ -695,12 +800,12 @@ fn fileMemoryMapSetLength(...@@ -695,12 +800,12 @@ fn fileMemoryMapSetLength(
695 mm.memory.len = new_len;800 mm.memory.len = new_len;
696 return;801 return;
697 }802 }
698 var cancel_region: CancelRegion = .init();
699 defer cancel_region.deinit();
700 const flags: linux.MREMAP = .{ .MAYMOVE = true };803 const flags: linux.MREMAP = .{ .MAYMOVE = true };
701 const addr_hint: ?[*]const u8 = null;804 const addr_hint: ?[*]const u8 = null;
805 var sync: CancelRegion.Sync = try .init(ev);
806 defer sync.deinit(ev);
702 const new_memory = while (true) {807 const new_memory = while (true) {
703 try cancel_region.await(.nothing);808 try sync.cancel_region.await(.nothing);
704 const rc = linux.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);809 const rc = linux.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);
705 switch (linux.errno(rc)) {810 switch (linux.errno(rc)) {
706 .SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len],811 .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...@@ -730,17 +835,28 @@ fn fileMemoryMapWrite(userdata: ?*anyopaque, mm: *File.MemoryMap) File.WritePosi
730pub const InitOptions = struct {835pub const InitOptions = struct {
731 backing_allocator_needs_mutex: bool = true,836 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
733 /// Affects the following operations:846 /// Affects the following operations:
734 /// * `processExecutablePath` on OpenBSD and Haiku.847 /// * `processExecutablePath` on OpenBSD and Haiku.
735 argv0: Argv0 = .empty,848 argv0: Argv0 = .empty,
736 /// Affects the following operations:849 /// Affects the following operations:
737 /// * `fileIsTty`850 /// * `fileIsTty`
738 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`851 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
739 environ: process.Environ,852 environ: process.Environ = .empty,
740};853};
741854
742pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !void {855pub 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);
744 const idle_stack_end_offset =860 const idle_stack_end_offset =
745 std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max);861 std.mem.alignForward(usize, threads_size + idle_stack_size, std.heap.page_size_max);
746 const allocated_slice = try backing_allocator.alignedAlloc(u8, .of(Thread), idle_stack_end_offset);862 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...@@ -750,11 +866,13 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
750 .backing_allocator_mutex = .init,866 .backing_allocator_mutex = .init,
751 .backing_allocator = backing_allocator,867 .backing_allocator = backing_allocator,
752 .main_fiber_buffer = undefined,868 .main_fiber_buffer = undefined,
869 .log2_ring_entries = options.log2_ring_entries,
753 .threads = .{870 .threads = .{
754 .allocated = @ptrCast(allocated_slice[0..threads_size]),871 .allocated = @ptrCast(allocated_slice[0..threads_size]),
755 .reserved = 1,872 .reserved = 1,
756 .active = 1,873 .active = 1,
757 },874 },
875 .sync_limit = if (options.sync_limit.toInt()) |sync_limit| .{ .permits = sync_limit } else null,
758876
759 .stderr_mutex = .init,877 .stderr_mutex = .init,
760 .stderr_writer = .{878 .stderr_writer = .{
...@@ -809,18 +927,18 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v...@@ -809,18 +927,18 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
809 },927 },
810 .current_context = &main_fiber.context,928 .current_context = &main_fiber.context,
811 .ready_queue = null,929 .ready_queue = null,
930 .free_queue = null,
812 .io_uring = try .init(931 .io_uring = try .init(
813 io_uring_entries,932 @as(u16, 1) << ev.log2_ring_entries,
814 linux.IORING_SETUP_COOP_TASKRUN | linux.IORING_SETUP_SINGLE_ISSUER,933 linux.IORING_SETUP_COOP_TASKRUN | linux.IORING_SETUP_SINGLE_ISSUER,
815 ),934 ),
816 .idle_search_index = 1,935 .idle_search_index = 1,
817 .steal_ready_search_index = 1,936 .steal_ready_search_index = 1,
937 .steal_free_search_index = 1,
818 .name_arena = .{},938 .name_arena = .{},
819 .csprng = .uninitialized,939 .csprng = .uninitialized,
820 };940 };
821 errdefer main_thread.io_uring.deinit();941 errdefer main_thread.io_uring.deinit();
822 log.debug("created main idle {*}", .{&main_thread.idle_context});
823 log.debug("created main {*}", .{main_fiber});
824 if (tracy.enable) tracy.fiberEnter(main_fiber.name);942 if (tracy.enable) tracy.fiberEnter(main_fiber.name);
825}943}
826944
...@@ -831,7 +949,7 @@ pub fn deinit(ev: *Evented) void {...@@ -831,7 +949,7 @@ pub fn deinit(ev: *Evented) void {
831 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async949 assert(ready_fiber == null or ready_fiber == Fiber.finished); // pending async
832 }950 }
833 ev.yield(null, .exit);951 ev.yield(null, .exit);
834 ev.threads.allocated[0].io_uring.deinit();952 ev.threads.allocated[0].deinit(ev.allocator());
835 ev.null_fd.close();953 ev.null_fd.close();
836 ev.random_fd.close();954 ev.random_fd.close();
837 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(ev.threads.allocated.ptr));955 const allocated_ptr: [*]align(@alignOf(Thread)) u8 = @ptrCast(@alignCast(ev.threads.allocated.ptr));
...@@ -848,6 +966,7 @@ pub fn deinit(ev: *Evented) void {...@@ -848,6 +966,7 @@ pub fn deinit(ev: *Evented) void {
848966
849fn findReadyFiber(ev: *Evented, thread: *Thread) ?*Fiber {967fn findReadyFiber(ev: *Evented, thread: *Thread) ?*Fiber {
850 if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| {968 if (@atomicRmw(?*Fiber, &thread.ready_queue, .Xchg, Fiber.finished, .acquire)) |ready_fiber| {
969 assert(ready_fiber != Fiber.finished);
851 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.status.queue_next, .release);970 @atomicStore(?*Fiber, &thread.ready_queue, ready_fiber.status.queue_next, .release);
852 ready_fiber.status.queue_next = null;971 ready_fiber.status.queue_next = null;
853 return ready_fiber;972 return ready_fiber;
...@@ -860,7 +979,7 @@ fn findReadyFiber(ev: *Evented, thread: *Thread) ?*Fiber {...@@ -860,7 +979,7 @@ fn findReadyFiber(ev: *Evented, thread: *Thread) ?*Fiber {
860 &ev.threads.allocated[0..active_threads][thread.steal_ready_search_index];979 &ev.threads.allocated[0..active_threads][thread.steal_ready_search_index];
861 if (steal_ready_search_thread == thread) continue;980 if (steal_ready_search_thread == thread) continue;
862 const ready_fiber =981 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;
864 if (ready_fiber == Fiber.finished) continue;983 if (ready_fiber == Fiber.finished) continue;
865 if (@cmpxchgWeak(984 if (@cmpxchgWeak(
866 ?*Fiber,985 ?*Fiber,
...@@ -892,19 +1011,10 @@ fn yield(ev: *Evented, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.P...@@ -892,19 +1011,10 @@ fn yield(ev: *Evented, maybe_ready_fiber: ?*Fiber, pending_task: SwitchMessage.P
892 },1011 },
893 .pending_task = pending_task,1012 .pending_task = pending_task,
894 };1013 };
895 log.debug("switching from {*} to {*}", .{ message.contexts.prev, message.contexts.ready });
896 contextSwitch(&message).handle(ev);1014 contextSwitch(&message).handle(ev);
897}1015}
8981016
899fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {1017fn 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 }
908 // shared fields of previous `Thread` must be initialized before later ones are marked as active1018 // shared fields of previous `Thread` must be initialized before later ones are marked as active
909 const new_thread_index = @atomicLoad(u32, &ev.threads.active, .acquire);1019 const new_thread_index = @atomicLoad(u32, &ev.threads.active, .acquire);
910 for (0..@min(max_idle_search, new_thread_index)) |_| {1020 for (0..@min(max_idle_search, new_thread_index)) |_| {
...@@ -963,7 +1073,8 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {...@@ -963,7 +1073,8 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {
963 .idle_context = undefined,1073 .idle_context = undefined,
964 .current_context = &new_thread.idle_context,1074 .current_context = &new_thread.idle_context,
965 .ready_queue = ready_queue.head,1075 .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| {
967 @atomicStore(u32, &ev.threads.reserved, new_thread_index, .release);1078 @atomicStore(u32, &ev.threads.reserved, new_thread_index, .release);
968 // no more access to `thread` after giving up reservation1079 // no more access to `thread` after giving up reservation
969 log.warn("unable to create worker thread due to io_uring init failure: {s}", .{1080 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 {...@@ -973,6 +1084,7 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {
973 },1084 },
974 .idle_search_index = 0,1085 .idle_search_index = 0,
975 .steal_ready_search_index = 0,1086 .steal_ready_search_index = 0,
1087 .steal_free_search_index = 0,
976 .name_arena = .{},1088 .name_arena = .{},
977 .csprng = .uninitialized,1089 .csprng = .uninitialized,
978 };1090 };
...@@ -991,14 +1103,14 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {...@@ -991,14 +1103,14 @@ fn schedule(ev: *Evented, thread: *Thread, ready_queue: Fiber.Queue) bool {
991 return false;1103 return false;
992 }1104 }
993 // nobody wanted it, so just queue it on ourselves1105 // nobody wanted it, so just queue it on ourselves
994 while (@cmpxchgWeak(1106 while (true) ready_queue.tail.status.queue_next = @cmpxchgWeak(
995 ?*Fiber,1107 ?*Fiber,
996 &thread.ready_queue,1108 &thread.ready_queue,
997 ready_queue.tail.status.queue_next,1109 ready_queue.tail.status.queue_next,
998 ready_queue.head,1110 ready_queue.head,
999 .acq_rel,1111 .acq_rel,
1000 .acquire,1112 .acquire,
1001 )) |old_head| ready_queue.tail.status.queue_next = old_head;1113 ) orelse break;
1002 return false;1114 return false;
1003}1115}
10041116
...@@ -1015,8 +1127,7 @@ fn mainIdle(...@@ -1015,8 +1127,7 @@ fn mainIdle(
1015fn threadEntry(ev: *Evented, index: u32) void {1127fn threadEntry(ev: *Evented, index: u32) void {
1016 const thread: *Thread = &ev.threads.allocated[index];1128 const thread: *Thread = &ev.threads.allocated[index];
1017 Thread.self = thread;1129 Thread.self = thread;
1018 defer thread.io_uring.deinit();1130 defer thread.deinit(ev.allocator());
1019 log.debug("created thread idle {*}", .{&thread.idle_context});
1020 switch (linux.errno(linux.io_uring_register(thread.io_uring.fd, .REGISTER_ENABLE_RINGS, null, 0))) {1131 switch (linux.errno(linux.io_uring_register(thread.io_uring.fd, .REGISTER_ENABLE_RINGS, null, 0))) {
1021 .SUCCESS => ev.idle(thread),1132 .SUCCESS => ev.idle(thread),
1022 else => |err| @panic(@tagName(err)),1133 else => |err| @panic(@tagName(err)),
...@@ -1054,103 +1165,107 @@ fn idle(ev: *Evented, thread: *Thread) void {...@@ -1054,103 +1165,107 @@ fn idle(ev: *Evented, thread: *Thread) void {
1054 error.SignalInterrupt => {},1165 error.SignalInterrupt => {},
1055 else => |e| @panic(@errorName(e)),1166 else => |e| @panic(@errorName(e)),
1056 };1167 };
1057 var cqes_buffer: [io_uring_entries]linux.io_uring_cqe = undefined;
1058 var maybe_ready_queue: ?Fiber.Queue = null;1168 var maybe_ready_queue: ?Fiber.Queue = null;
1059 for (cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) {1169 while (true) {
1060 error.SignalInterrupt => 0,1170 var cqes_buffer: [1 << 8]linux.io_uring_cqe = undefined;
1061 else => |e| @panic(@errorName(e)),1171 const cqes = cqes_buffer[0 .. thread.io_uring.copy_cqes(&cqes_buffer, 0) catch |err| switch (err) {
1062 }]) |cqe| if (cqe.flags & linux.IORING_CQE_F_SKIP == 0) switch (@as(1172 error.SignalInterrupt => 0,
1063 Completion.UserData,1173 else => |e| @panic(@errorName(e)),
1064 @enumFromInt(cqe.user_data),1174 }];
1065 )) {1175 if (cqes.len == 0) break;
1066 .unused => unreachable, // bad submission queued?1176 for (cqes) |cqe| if (cqe.flags & linux.IORING_CQE_F_SKIP == 0) switch (@as(
1067 .wakeup => {},1177 Completion.UserData,
1068 .futex_wake => switch (Completion.errno(.{ .result = cqe.res, .flags = cqe.flags })) {1178 @enumFromInt(cqe.user_data),
1069 .SUCCESS => recoverableOsBugDetected(), // success is skipped1179 )) {
1070 .INVAL => {}, // invalid futex_wait() on ptr done elsewhere1180 .unused => unreachable, // bad submission queued?
1071 .INTR, .CANCELED => recoverableOsBugDetected(), // `Completion.UserData.futex_wake` is not cancelable1181 .wakeup => {},
1072 .FAULT => {}, // pointer became invalid while doing the wake1182 .futex_wake => switch (Completion.errno(.{ .result = cqe.res, .flags = cqe.flags })) {
1073 else => recoverableOsBugDetected(), // deadlock due to operating system bug1183 .SUCCESS => recoverableOsBugDetected(), // success is skipped
1074 },1184 .INVAL => {}, // invalid futex_wait() on ptr done elsewhere
1075 .cleanup => @panic("failed to notify other threads that we are exiting"),1185 .INTR, .CANCELED => recoverableOsBugDetected(), // `Completion.UserData.futex_wake` is not cancelable
1076 .exit => {1186 .FAULT => {}, // pointer became invalid while doing the wake
1077 assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async1187 else => recoverableOsBugDetected(), // deadlock due to operating system bug
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;
1107 },1188 },
1108 0b10 => {1189 .cleanup => @panic("failed to notify other threads that we are exiting"),
1109 const context: *Io.Operation.Storage.Pending.Context =1190 .exit => {
1110 @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));1191 assert(maybe_ready_fiber == null and maybe_ready_queue == null); // pending async
1111 const batch: *Io.Batch = @ptrFromInt(context[0]);1192 return;
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 };
1129 },1193 },
1130 0b11 => switch (Completion.errno(.{ .result = cqe.res, .flags = cqe.flags })) {1194 _ => if (@as(?*Fiber, ready_fiber: switch (@as(u2, @truncate(cqe.user_data))) {
1131 .SUCCESS => unreachable, // no event count specified1195 0b00 => {
1132 .TIME => {1196 const ready_fiber: *Fiber = @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));
1133 const context: *usize = @ptrFromInt(cqe.user_data & ~@as(usize, 0b11));1197 ready_fiber.resultPointer(Completion).* = .{
1134 const fiber = @atomicRmw(usize, context, .Add, 0b01, .acquire);1198 .result = cqe.res,
1135 break :ready_fiber switch (@as(u2, @truncate(fiber))) {1199 .flags = cqe.flags,
1136 else => unreachable, // timeout completed multiple times1200 };
1137 0b00 => @ptrFromInt(fiber & ~@as(usize, 0b11)),1201 break :ready_fiber ready_fiber;
1138 0b10 => null,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,
1139 };1219 };
1220 break :ready_fiber null;
1140 },1221 },
1141 .CANCELED => null, // user data may have been invalidated1222 0b10 => {
1142 else => |err| unexpectedErrno(err) catch null,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 };
1143 },1266 },
1144 })) |ready_fiber| {1267 };
1145 assert(ready_fiber.status.queue_next == null);1268 }
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 };
1154 if (maybe_ready_queue) |ready_queue| _ = ev.schedule(thread, ready_queue);1269 if (maybe_ready_queue) |ready_queue| _ = ev.schedule(thread, ready_queue);
1155 }1270 }
1156}1271}
...@@ -1220,8 +1335,7 @@ const SwitchMessage = struct {...@@ -1220,8 +1335,7 @@ const SwitchMessage = struct {
1220 },1335 },
1221 .destroy => {1336 .destroy => {
1222 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));1337 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.prev));
1223 fiber.destroy(ev.backing_allocator);1338 fiber.destroy();
1224 ev.backing_allocator_mutex.unlock(ev.io());
1225 },1339 },
1226 .exit => for (1340 .exit => for (
1227 ev.threads.allocated[0..@atomicLoad(u32, &ev.threads.active, .acquire)],1341 ev.threads.allocated[0..@atomicLoad(u32, &ev.threads.active, .acquire)],
...@@ -1295,7 +1409,6 @@ inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {...@@ -1295,7 +1409,6 @@ inline fn contextSwitch(message: *const SwitchMessage) *const SwitchMessage {
1295 .x15 = true,1409 .x15 = true,
1296 .x16 = true,1410 .x16 = true,
1297 .x17 = true,1411 .x17 = true,
1298 .x18 = true,
1299 .x19 = true,1412 .x19 = true,
1300 .x20 = true,1413 .x20 = true,
1301 .x21 = true,1414 .x21 = true,
...@@ -1497,7 +1610,6 @@ const AsyncClosure = struct {...@@ -1497,7 +1610,6 @@ const AsyncClosure = struct {
1497 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {1610 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
1498 message.handle(closure.ev);1611 message.handle(closure.ev);
1499 const fiber = closure.fiber;1612 const fiber = closure.fiber;
1500 log.debug("{*} performing async", .{fiber});
1501 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));1613 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
1502 closure.ev.yield(1614 closure.ev.yield(
1503 if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|1615 if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|
...@@ -1542,7 +1654,6 @@ fn concurrent(...@@ -1542,7 +1654,6 @@ fn concurrent(
1542 const fiber = Fiber.create(ev) catch |err| switch (err) {1654 const fiber = Fiber.create(ev) catch |err| switch (err) {
1543 error.OutOfMemory => return error.ConcurrencyUnavailable,1655 error.OutOfMemory => return error.ConcurrencyUnavailable,
1544 };1656 };
1545 log.debug("allocated {*}", .{fiber});
15461657
1547 const closure: *AsyncClosure = .fromFiber(fiber);1658 const closure: *AsyncClosure = .fromFiber(fiber);
1548 fiber.* = .{1659 fiber.* = .{
...@@ -1608,7 +1719,7 @@ fn await(...@@ -1608,7 +1719,7 @@ fn await(
1608 assert(awaiter == fiber); // spurious wakeup1719 assert(awaiter == fiber); // spurious wakeup
1609 }1720 }
1610 @memcpy(result, future_fiber.resultBytes(result_alignment));1721 @memcpy(result, future_fiber.resultBytes(result_alignment));
1611 future_fiber.destroy(ev.allocator());1722 future_fiber.destroy();
1612}1723}
16131724
1614fn cancel(1725fn cancel(
...@@ -1866,7 +1977,6 @@ const Group = struct {...@@ -1866,7 +1977,6 @@ const Group = struct {
1866 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {1977 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
1867 message.handle(closure.ev);1978 message.handle(closure.ev);
1868 assert(closure.fiber.status.queue_next == null);1979 assert(closure.fiber.status.queue_next == null);
1869 log.debug("{*} performing group async", .{closure.fiber});
1870 const result = closure.start(closure.contextPointer());1980 const result = closure.start(closure.contextPointer());
1871 const ev = closure.ev;1981 const ev = closure.ev;
1872 const group = closure.group;1982 const group = closure.group;
...@@ -1877,9 +1987,7 @@ const Group = struct {...@@ -1877,9 +1987,7 @@ const Group = struct {
1877 } else |err| switch (err) {1987 } else |err| switch (err) {
1878 error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled1988 error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled
1879 }1989 }
1880 const awaiter = group.removeFiber(ev, fiber);1990 ev.yield(group.removeFiber(ev, fiber), .destroy);
1881 ev.backing_allocator_mutex.lockUncancelable(ev.io());
1882 ev.yield(awaiter, .destroy);
1883 unreachable; // switched to dead fiber1991 unreachable; // switched to dead fiber
1884 }1992 }
1885 };1993 };
...@@ -1930,7 +2038,6 @@ fn groupConcurrent(...@@ -1930,7 +2038,6 @@ fn groupConcurrent(
1930 const fiber = Fiber.create(ev) catch |err| switch (err) {2038 const fiber = Fiber.create(ev) catch |err| switch (err) {
1931 error.OutOfMemory => return error.ConcurrencyUnavailable,2039 error.OutOfMemory => return error.ConcurrencyUnavailable,
1932 };2040 };
1933 log.debug("allocated {*}", .{fiber});
19342041
1935 const closure: *Group.AsyncClosure = .fromFiber(fiber);2042 const closure: *Group.AsyncClosure = .fromFiber(fiber);
1936 fiber.* = .{2043 fiber.* = .{
...@@ -2080,7 +2187,6 @@ fn futexWait(...@@ -2080,7 +2187,6 @@ fn futexWait(
2080 timeout: Io.Timeout,2187 timeout: Io.Timeout,
2081) Io.Cancelable!void {2188) Io.Cancelable!void {
2082 const ev: *Evented = @ptrCast(@alignCast(userdata));2189 const ev: *Evented = @ptrCast(@alignCast(userdata));
2083 if (builtin.single_threaded) unreachable; // Deadlock.
2084 const timespec: ?linux.kernel_timespec, const clock: Io.Clock, const timeout_flags: u32 = timespec: switch (timeout) {2190 const timespec: ?linux.kernel_timespec, const clock: Io.Clock, const timeout_flags: u32 = timespec: switch (timeout) {
2085 .none => .{2191 .none => .{
2086 null,2192 null,
...@@ -2163,7 +2269,6 @@ fn futexWait(...@@ -2163,7 +2269,6 @@ fn futexWait(
21632269
2164fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {2270fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) void {
2165 const ev: *Evented = @ptrCast(@alignCast(userdata));2271 const ev: *Evented = @ptrCast(@alignCast(userdata));
2166 if (builtin.single_threaded) unreachable; // Deadlock.
2167 var cancel_region: CancelRegion = .initBlocked();2272 var cancel_region: CancelRegion = .initBlocked();
2168 defer cancel_region.deinit();2273 defer cancel_region.deinit();
2169 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {2274 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {
...@@ -2199,7 +2304,6 @@ fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32)...@@ -2199,7 +2304,6 @@ fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32)
2199fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {2304fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
2200 const ev: *Evented = @ptrCast(@alignCast(userdata));2305 const ev: *Evented = @ptrCast(@alignCast(userdata));
2201 _ = ev;2306 _ = ev;
2202 if (builtin.single_threaded) unreachable; // Nothing to wake up.
2203 const thread: *Thread = .current();2307 const thread: *Thread = .current();
2204 thread.enqueue().* = .{2308 thread.enqueue().* = .{
2205 .opcode = .FUTEX_WAKE,2309 .opcode = .FUTEX_WAKE,
...@@ -2222,24 +2326,43 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {...@@ -2222,24 +2326,43 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
22222326
2223fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result {2327fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result {
2224 const ev: *Evented = @ptrCast(@alignCast(userdata));2328 const ev: *Evented = @ptrCast(@alignCast(userdata));
2225 switch (operation) {2329 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
2226 .file_read_streaming => |o| return .{2330 defer maybe_sync.deinit(ev);
2227 .file_read_streaming = ev.fileReadStreaming(o.file, o.data) catch |err| switch (err) {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) {
2228 error.Canceled => |e| return e,2338 error.Canceled => |e| return e,
2229 else => |e| e,2339 else => |e| e,
2230 },2340 },
2231 },2341 },
2232 .file_write_streaming => |o| return .{2342 .file_write_streaming => |o| .{
2233 .file_write_streaming = ev.fileWriteStreaming(o.file, o.header, o.data, o.splat) catch |err| switch (err) {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) {
2234 error.Canceled => |e| return e,2350 error.Canceled => |e| return e,
2235 else => |e| e,2351 else => |e| e,
2236 },2352 },
2237 },2353 },
2238 .device_io_control => |*o| return .{ .device_io_control = try deviceIoControl(o) },2354 .device_io_control => |o| .{
2239 }2355 .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o),
2356 },
2357 };
2240}2358}
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 {
2243 var iovecs_buffer: [max_iovecs_len]iovec = undefined;2366 var iovecs_buffer: [max_iovecs_len]iovec = undefined;
2244 var i: usize = 0;2367 var i: usize = 0;
2245 for (data) |buf| {2368 for (data) |buf| {
...@@ -2252,13 +2375,13 @@ fn fileReadStreaming(ev: *Evented, file: File, data: []const []u8) File.Reader.E...@@ -2252,13 +2375,13 @@ fn fileReadStreaming(ev: *Evented, file: File, data: []const []u8) File.Reader.E
2252 const dest = iovecs_buffer[0..i];2375 const dest = iovecs_buffer[0..i];
2253 assert(dest[0].len > 0);2376 assert(dest[0].len > 0);
22542377
2255 var cancel_region: CancelRegion = .init();2378 const n = try ev.preadv(cancel_region, file.handle, dest, null);
2256 defer cancel_region.deinit();2379 return if (n == 0) error.EndOfStream else n;
2257 return ev.preadv(&cancel_region, file.handle, dest, null);
2258}2380}
22592381
2260fn fileWriteStreaming(2382fn fileWriteStreaming(
2261 ev: *Evented,2383 ev: *Evented,
2384 cancel_region: *CancelRegion,
2262 file: File,2385 file: File,
2263 header: []const u8,2386 header: []const u8,
2264 data: []const []const u8,2387 data: []const []const u8,
...@@ -2294,17 +2417,17 @@ fn fileWriteStreaming(...@@ -2294,17 +2417,17 @@ fn fileWriteStreaming(
2294 },2417 },
2295 },2418 },
2296 };2419 };
22972420 return ev.pwritev(cancel_region, file.handle, iovecs[0..iovlen], null);
2298 var cancel_region: CancelRegion = .init();
2299 defer cancel_region.deinit();
2300 return ev.pwritev(&cancel_region, file.handle, iovecs[0..iovlen], null);
2301}2421}
23022422
2303fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!i32 {2423fn deviceIoControl(
2304 var cancel_region: CancelRegion = .init();2424 ev: *Evented,
2305 defer cancel_region.deinit();2425 sync: *CancelRegion.Sync,
2426 o: Io.Operation.DeviceIoControl,
2427) Io.Cancelable!i32 {
2428 _ = ev;
2306 while (true) {2429 while (true) {
2307 try cancel_region.await(.nothing);2430 try sync.cancel_region.await(.nothing);
2308 const rc = linux.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));2431 const rc = linux.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));
2309 switch (linux.errno(rc)) {2432 switch (linux.errno(rc)) {
2310 .SUCCESS => return @bitCast(@as(u32, @truncate(rc))),2433 .SUCCESS => return @bitCast(@as(u32, @truncate(rc))),
...@@ -2316,12 +2439,13 @@ fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!i32 {...@@ -2316,12 +2439,13 @@ fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!i32 {
23162439
2317fn batchAwaitAsync(userdata: ?*anyopaque, batch: *Io.Batch) Io.Cancelable!void {2440fn batchAwaitAsync(userdata: ?*anyopaque, batch: *Io.Batch) Io.Cancelable!void {
2318 const ev: *Evented = @ptrCast(@alignCast(userdata));2441 const ev: *Evented = @ptrCast(@alignCast(userdata));
2319 var cancel_region: CancelRegion = .init();2442 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
2320 defer cancel_region.deinit();2443 defer maybe_sync.deinit(ev);
2321 batchDrainSubmitted(batch, &cancel_region, false) catch |err| switch (err) {2444 ev.batchDrainSubmitted(&maybe_sync, batch, false) catch |err| switch (err) {
2322 error.ConcurrencyUnavailable => unreachable, // passed concurrency=false2445 error.ConcurrencyUnavailable => unreachable, // passed concurrency=false
2323 else => |e| return e,2446 else => |e| return e,
2324 };2447 };
2448 maybe_sync.leaveSync(ev);
2325 while (true) {2449 while (true) {
2326 batchDrainReady(batch) catch |err| switch (err) {2450 batchDrainReady(batch) catch |err| switch (err) {
2327 error.Timeout => unreachable, // no timeout2451 error.Timeout => unreachable, // no timeout
...@@ -2337,9 +2461,10 @@ fn batchAwaitConcurrent(...@@ -2337,9 +2461,10 @@ fn batchAwaitConcurrent(
2337 timeout: Io.Timeout,2461 timeout: Io.Timeout,
2338) Io.Batch.AwaitConcurrentError!void {2462) Io.Batch.AwaitConcurrentError!void {
2339 const ev: *Evented = @ptrCast(@alignCast(userdata));2463 const ev: *Evented = @ptrCast(@alignCast(userdata));
2340 var cancel_region: CancelRegion = .init();2464 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
2341 defer cancel_region.deinit();2465 defer maybe_sync.deinit(ev);
2342 try batchDrainSubmitted(batch, &cancel_region, true);2466 try ev.batchDrainSubmitted(&maybe_sync, batch, true);
2467 maybe_sync.leaveSync(ev);
2343 const timespec: linux.kernel_timespec, const clock: Io.Clock, const timeout_flags: u32 = while (true) {2468 const timespec: linux.kernel_timespec, const clock: Io.Clock, const timeout_flags: u32 = while (true) {
2344 batchDrainReady(batch) catch |err| switch (err) {2469 batchDrainReady(batch) catch |err| switch (err) {
2345 error.Timeout => unreachable, // no timeout2470 error.Timeout => unreachable, // no timeout
...@@ -2372,7 +2497,7 @@ fn batchAwaitConcurrent(...@@ -2372,7 +2497,7 @@ fn batchAwaitConcurrent(
2372 }2497 }
2373 };2498 };
2374 {2499 {
2375 const thread = try cancel_region.awaitIoUring();2500 const thread = try maybe_sync.cancel_region.awaitIoUring();
2376 thread.enqueue().* = .{2501 thread.enqueue().* = .{
2377 .opcode = .TIMEOUT,2502 .opcode = .TIMEOUT,
2378 .flags = 0,2503 .flags = 0,
...@@ -2401,7 +2526,7 @@ fn batchAwaitConcurrent(...@@ -2401,7 +2526,7 @@ fn batchAwaitConcurrent(
2401 };2526 };
2402 if (batch.completed.head == .none) continue;2527 if (batch.completed.head == .none) continue;
2403 }2528 }
2404 const thread = try cancel_region.awaitIoUring();2529 const thread = try maybe_sync.cancel_region.awaitIoUring();
2405 thread.enqueue().* = .{2530 thread.enqueue().* = .{
2406 .opcode = .TIMEOUT_REMOVE,2531 .opcode = .TIMEOUT_REMOVE,
2407 .flags = 0,2532 .flags = 0,
...@@ -2411,7 +2536,7 @@ fn batchAwaitConcurrent(...@@ -2411,7 +2536,7 @@ fn batchAwaitConcurrent(
2411 .addr = @intFromPtr(&batch.context) | 0b11,2536 .addr = @intFromPtr(&batch.context) | 0b11,
2412 .len = 0,2537 .len = 0,
2413 .rw_flags = 0,2538 .rw_flags = 0,
2414 .user_data = @intFromPtr(cancel_region.fiber),2539 .user_data = @intFromPtr(maybe_sync.cancel_region.fiber),
2415 .buf_index = 0,2540 .buf_index = 0,
2416 .personality = 0,2541 .personality = 0,
2417 .splice_fd_in = 0,2542 .splice_fd_in = 0,
...@@ -2419,7 +2544,7 @@ fn batchAwaitConcurrent(...@@ -2419,7 +2544,7 @@ fn batchAwaitConcurrent(
2419 .resv = 0,2544 .resv = 0,
2420 };2545 };
2421 ev.yield(null, .nothing);2546 ev.yield(null, .nothing);
2422 switch (cancel_region.errno()) {2547 switch (maybe_sync.cancel_region.errno()) {
2423 .SUCCESS => return,2548 .SUCCESS => return,
2424 .BUSY, .NOENT => {},2549 .BUSY, .NOENT => {},
2425 else => |err| unexpectedErrno(err) catch {},2550 else => |err| unexpectedErrno(err) catch {},
...@@ -2434,22 +2559,23 @@ fn batchAwaitConcurrent(...@@ -2434,22 +2559,23 @@ fn batchAwaitConcurrent(
24342559
2435/// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable.2560/// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable.
2436fn batchDrainSubmitted(2561fn batchDrainSubmitted(
2562 ev: *Evented,
2563 maybe_sync: *CancelRegion.Sync.Maybe,
2437 batch: *Io.Batch,2564 batch: *Io.Batch,
2438 cancel_region: *CancelRegion,
2439 concurrency: bool,2565 concurrency: bool,
2440) (Io.ConcurrentError || Io.Cancelable)!void {2566) (Io.ConcurrentError || Io.Cancelable)!void {
2441 var index = batch.submitted.head;2567 var index = batch.submitted.head;
2442 if (index == .none) return;2568 if (index == .none) return;
2443 errdefer batch.submitted.head = index;2569 errdefer batch.submitted.head = index;
2444 const thread = try cancel_region.awaitIoUring();2570 const thread = try maybe_sync.cancelRegion().awaitIoUring();
2445 while (index != .none) {2571 while (index != .none) {
2446 const storage = &batch.storage[index.toIndex()];2572 const storage = &batch.storage[index.toIndex()];
2447 const next_index = storage.submission.node.next;2573 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) {
2449 .file_read_streaming => |o| {2575 .file_read_streaming => |o| {
2450 const buffer = for (o.data) |buffer| {2576 const buffer = for (o.data) |buffer| {
2451 if (buffer.len != 0) break buffer;2577 if (buffer.len != 0) break buffer;
2452 } else break :operation .{ .file_read_streaming = 0 };2578 } else break :result .{ .file_read_streaming = 0 };
2453 const fd = o.file.handle;2579 const fd = o.file.handle;
2454 storage.* = .{ .pending = .{2580 storage.* = .{ .pending = .{
2455 .node = .{ .prev = batch.pending.tail, .next = .none },2581 .node = .{ .prev = batch.pending.tail, .next = .none },
...@@ -2472,7 +2598,7 @@ fn batchDrainSubmitted(...@@ -2472,7 +2598,7 @@ fn batchDrainSubmitted(
2472 .addr3 = 0,2598 .addr3 = 0,
2473 .resv = 0,2599 .resv = 0,
2474 };2600 };
2475 break :operation null;2601 break :result null;
2476 },2602 },
2477 .file_write_streaming => |o| {2603 .file_write_streaming => |o| {
2478 const buffer = buffer: {2604 const buffer = buffer: {
...@@ -2481,7 +2607,7 @@ fn batchDrainSubmitted(...@@ -2481,7 +2607,7 @@ fn batchDrainSubmitted(
2481 if (buffer.len != 0) break :buffer buffer;2607 if (buffer.len != 0) break :buffer buffer;
2482 }2608 }
2483 if (o.splat > 0) break :buffer o.data[o.data.len - 1];2609 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 };
2485 };2611 };
2486 const fd = o.file.handle;2612 const fd = o.file.handle;
2487 storage.* = .{ .pending = .{2613 storage.* = .{ .pending = .{
...@@ -2505,12 +2631,12 @@ fn batchDrainSubmitted(...@@ -2505,12 +2631,12 @@ fn batchDrainSubmitted(
2505 .addr3 = 0,2631 .addr3 = 0,
2506 .resv = 0,2632 .resv = 0,
2507 };2633 };
2508 break :operation null;2634 break :result null;
2509 },2635 },
2510 .device_io_control => |o| if (concurrency)2636 .device_io_control => |o| if (concurrency)
2511 return error.ConcurrencyUnavailable2637 return error.ConcurrencyUnavailable
2512 else2638 else
2513 .{ .device_io_control = try deviceIoControl(&o) },2639 .{ .device_io_control = try ev.deviceIoControl(try maybe_sync.enterSync(ev), o) },
2514 })) |result| {2640 })) |result| {
2515 switch (batch.completed.tail) {2641 switch (batch.completed.tail) {
2516 .none => batch.completed.head = index,2642 .none => batch.completed.head = index,
...@@ -2838,7 +2964,6 @@ fn dirAccess(...@@ -2838,7 +2964,6 @@ fn dirAccess(
2838 options: Dir.AccessOptions,2964 options: Dir.AccessOptions,
2839) Dir.AccessError!void {2965) Dir.AccessError!void {
2840 const ev: *Evented = @ptrCast(@alignCast(userdata));2966 const ev: *Evented = @ptrCast(@alignCast(userdata));
2841 _ = ev;
28422967
2843 var path_buffer: [PATH_MAX]u8 = undefined;2968 var path_buffer: [PATH_MAX]u8 = undefined;
2844 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);2969 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
...@@ -2849,10 +2974,10 @@ fn dirAccess(...@@ -2849,10 +2974,10 @@ fn dirAccess(
2849 @as(u32, if (options.execute) linux.X_OK else 0);2974 @as(u32, if (options.execute) linux.X_OK else 0);
2850 const flags: u32 = if (options.follow_symlinks) 0 else linux.AT.SYMLINK_NOFOLLOW;2975 const flags: u32 = if (options.follow_symlinks) 0 else linux.AT.SYMLINK_NOFOLLOW;
28512976
2852 var cancel_region: CancelRegion = .init();2977 var sync: CancelRegion.Sync = try .init(ev);
2853 defer cancel_region.deinit();2978 defer sync.deinit(ev);
2854 while (true) {2979 while (true) {
2855 try cancel_region.await(.nothing);2980 try sync.cancel_region.await(.nothing);
2856 switch (linux.errno(linux.faccessat(dir.handle, sub_path_posix, mode, flags))) {2981 switch (linux.errno(linux.faccessat(dir.handle, sub_path_posix, mode, flags))) {
2857 .SUCCESS => return,2982 .SUCCESS => return,
2858 .INTR => continue,2983 .INTR => continue,
...@@ -2885,21 +3010,21 @@ fn dirCreateFile(...@@ -2885,21 +3010,21 @@ fn dirCreateFile(
2885 var path_buffer: [PATH_MAX]u8 = undefined;3010 var path_buffer: [PATH_MAX]u8 = undefined;
2886 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);3011 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
28873012
2888 var cancel_region: CancelRegion = .init();3013 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
2889 defer cancel_region.deinit();3014 defer maybe_sync.deinit(ev);
2890 const fd = try ev.openat(&cancel_region, dir.handle, sub_path_posix, .{3015 const fd = try ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{
2891 .ACCMODE = if (flags.read) .RDWR else .WRONLY,3016 .ACCMODE = if (flags.read) .RDWR else .WRONLY,
2892 .CREAT = true,3017 .CREAT = true,
2893 .TRUNC = flags.truncate,3018 .TRUNC = flags.truncate,
2894 .EXCL = flags.exclusive,3019 .EXCL = flags.exclusive,
2895 .CLOEXEC = true,3020 .CLOEXEC = true,
2896 }, flags.permissions.toMode());3021 }, flags.permissions.toMode());
2897 errdefer ev.close(fd);3022 errdefer ev.close(maybe_sync.cancelRegion(), fd);
28983023
2899 switch (flags.lock) {3024 switch (flags.lock) {
2900 .none => {},3025 .none => {},
2901 .shared, .exclusive => try ev.flock(3026 .shared, .exclusive => try ev.flock(
2902 &cancel_region,3027 try maybe_sync.enterSync(ev),
2903 fd,3028 fd,
2904 flags.lock,3029 flags.lock,
2905 if (flags.lock_nonblocking) .nonblocking else .blocking,3030 if (flags.lock_nonblocking) .nonblocking else .blocking,
...@@ -3069,9 +3194,9 @@ fn dirOpenFile(...@@ -3069,9 +3194,9 @@ fn dirOpenFile(
3069 var path_buffer: [PATH_MAX]u8 = undefined;3194 var path_buffer: [PATH_MAX]u8 = undefined;
3070 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);3195 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
30713196
3072 var cancel_region: CancelRegion = .init();3197 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
3073 defer cancel_region.deinit();3198 defer maybe_sync.deinit(ev);
3074 const fd = try ev.openat(&cancel_region, dir.handle, sub_path_posix, .{3199 const fd = try ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{
3075 .ACCMODE = switch (flags.mode) {3200 .ACCMODE = switch (flags.mode) {
3076 .read_only => .RDONLY,3201 .read_only => .RDONLY,
3077 .write_only => .WRONLY,3202 .write_only => .WRONLY,
...@@ -3082,11 +3207,11 @@ fn dirOpenFile(...@@ -3082,11 +3207,11 @@ fn dirOpenFile(
3082 .CLOEXEC = true,3207 .CLOEXEC = true,
3083 .PATH = flags.path_only,3208 .PATH = flags.path_only,
3084 }, 0);3209 }, 0);
3085 errdefer ev.close(fd);3210 errdefer ev.close(maybe_sync.cancelRegion(), fd);
30863211
3087 if (!flags.allow_directory) {3212 if (!flags.allow_directory) {
3088 const is_dir = is_dir: {3213 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) {
3090 // The directory-ness is either unknown or unknowable3215 // The directory-ness is either unknown or unknowable
3091 error.Streaming => break :is_dir false,3216 error.Streaming => break :is_dir false,
3092 else => |e| return e,3217 else => |e| return e,
...@@ -3099,7 +3224,7 @@ fn dirOpenFile(...@@ -3099,7 +3224,7 @@ fn dirOpenFile(
3099 switch (flags.lock) {3224 switch (flags.lock) {
3100 .none => {},3225 .none => {},
3101 .shared, .exclusive => try ev.flock(3226 .shared, .exclusive => try ev.flock(
3102 &cancel_region,3227 try maybe_sync.enterSync(ev),
3103 fd,3228 fd,
3104 flags.lock,3229 flags.lock,
3105 if (flags.lock_nonblocking) .nonblocking else .blocking,3230 if (flags.lock_nonblocking) .nonblocking else .blocking,
...@@ -3111,7 +3236,9 @@ fn dirOpenFile(...@@ -3111,7 +3236,9 @@ fn dirOpenFile(
31113236
3112fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {3237fn dirClose(userdata: ?*anyopaque, dirs: []const Dir) void {
3113 const ev: *Evented = @ptrCast(@alignCast(userdata));3238 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);
3115}3242}
31163243
3117fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Reader.Error!usize {3244fn 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...@@ -3122,17 +3249,17 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read
3122 // Refill the buffer, unless we've already created references to3249 // Refill the buffer, unless we've already created references to
3123 // buffered data.3250 // buffered data.
3124 if (buffer_index != 0) break;3251 if (buffer_index != 0) break;
3125 var cancel_region: CancelRegion = .init();3252 var sync: CancelRegion.Sync = try .init(ev);
3126 defer cancel_region.deinit();3253 defer sync.deinit(ev);
3127 if (dr.state == .reset) {3254 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) {
3129 error.Unseekable => return error.Unexpected,3256 error.Unseekable => return error.Unexpected,
3130 else => |e| return e,3257 else => |e| return e,
3131 };3258 };
3132 dr.state = .reading;3259 dr.state = .reading;
3133 }3260 }
3134 const n = while (true) {3261 const n = while (true) {
3135 try cancel_region.await(.nothing);3262 try sync.cancel_region.await(.nothing);
3136 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);3263 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3137 switch (linux.errno(rc)) {3264 switch (linux.errno(rc)) {
3138 .SUCCESS => break rc,3265 .SUCCESS => break rc,
...@@ -3203,9 +3330,9 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read...@@ -3203,9 +3330,9 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read
32033330
3204fn dirRealPath(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {3331fn dirRealPath(userdata: ?*anyopaque, dir: Dir, out_buffer: []u8) Dir.RealPathError!usize {
3205 const ev: *Evented = @ptrCast(@alignCast(userdata));3332 const ev: *Evented = @ptrCast(@alignCast(userdata));
3206 var cancel_region: CancelRegion = .init();3333 var sync: CancelRegion.Sync = try .init(ev);
3207 defer cancel_region.deinit();3334 defer sync.deinit(ev);
3208 return ev.realPath(&cancel_region, dir.handle, out_buffer);3335 return ev.realPath(&sync, dir.handle, out_buffer);
3209}3336}
32103337
3211fn dirRealPathFile(3338fn dirRealPathFile(
...@@ -3219,9 +3346,9 @@ fn dirRealPathFile(...@@ -3219,9 +3346,9 @@ fn dirRealPathFile(
3219 var path_buffer: [PATH_MAX]u8 = undefined;3346 var path_buffer: [PATH_MAX]u8 = undefined;
3220 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);3347 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
32213348
3222 var cancel_region: CancelRegion = .init();3349 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
3223 defer cancel_region.deinit();3350 defer maybe_sync.deinit(ev);
3224 const fd = ev.openat(&cancel_region, dir.handle, sub_path_posix, .{3351 const fd = ev.openat(&maybe_sync.cancel_region, dir.handle, sub_path_posix, .{
3225 .CLOEXEC = true,3352 .CLOEXEC = true,
3226 .PATH = true,3353 .PATH = true,
3227 }, 0) catch |err| switch (err) {3354 }, 0) catch |err| switch (err) {
...@@ -3229,8 +3356,8 @@ fn dirRealPathFile(...@@ -3229,8 +3356,8 @@ fn dirRealPathFile(
3229 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.3356 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.
3230 else => |e| return e,3357 else => |e| return e,
3231 };3358 };
3232 defer ev.close(fd);3359 defer ev.close(maybe_sync.cancelRegion(), fd);
3233 return ev.realPath(&cancel_region, fd, out_buffer);3360 return ev.realPath(try maybe_sync.enterSync(ev), fd, out_buffer);
3234}3361}
32353362
3236fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {3363fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.DeleteFileError!void {
...@@ -3458,15 +3585,14 @@ fn dirReadLink(...@@ -3458,15 +3585,14 @@ fn dirReadLink(
3458 buffer: []u8,3585 buffer: []u8,
3459) Dir.ReadLinkError!usize {3586) Dir.ReadLinkError!usize {
3460 const ev: *Evented = @ptrCast(@alignCast(userdata));3587 const ev: *Evented = @ptrCast(@alignCast(userdata));
3461 _ = ev;
34623588
3463 var sub_path_buffer: [PATH_MAX]u8 = undefined;3589 var sub_path_buffer: [PATH_MAX]u8 = undefined;
3464 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);3590 const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer);
34653591
3466 var cancel_region: CancelRegion = .init();3592 var sync: CancelRegion.Sync = try .init(ev);
3467 defer cancel_region.deinit();3593 defer sync.deinit(ev);
3468 while (true) {3594 while (true) {
3469 try cancel_region.await(.nothing);3595 try sync.cancel_region.await(.nothing);
3470 const rc = linux.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);3596 const rc = linux.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
3471 switch (linux.errno(rc)) {3597 switch (linux.errno(rc)) {
3472 .SUCCESS => {3598 .SUCCESS => {
...@@ -3496,10 +3622,10 @@ fn dirSetOwner(...@@ -3496,10 +3622,10 @@ fn dirSetOwner(
3496 group: ?File.Gid,3622 group: ?File.Gid,
3497) Dir.SetOwnerError!void {3623) Dir.SetOwnerError!void {
3498 const ev: *Evented = @ptrCast(@alignCast(userdata));3624 const ev: *Evented = @ptrCast(@alignCast(userdata));
3499 var cancel_region: CancelRegion = .init();3625 var sync: CancelRegion.Sync = try .init(ev);
3500 defer cancel_region.deinit();3626 defer sync.deinit(ev);
3501 try ev.fchownat(3627 try ev.fchownat(
3502 &cancel_region,3628 &sync,
3503 dir.handle,3629 dir.handle,
3504 "",3630 "",
3505 owner orelse std.math.maxInt(linux.uid_t),3631 owner orelse std.math.maxInt(linux.uid_t),
...@@ -3519,10 +3645,10 @@ fn dirSetFileOwner(...@@ -3519,10 +3645,10 @@ fn dirSetFileOwner(
3519 const ev: *Evented = @ptrCast(@alignCast(userdata));3645 const ev: *Evented = @ptrCast(@alignCast(userdata));
3520 var path_buffer: [PATH_MAX]u8 = undefined;3646 var path_buffer: [PATH_MAX]u8 = undefined;
3521 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);3647 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3522 var cancel_region: CancelRegion = .init();3648 var sync: CancelRegion.Sync = try .init(ev);
3523 defer cancel_region.deinit();3649 defer sync.deinit(ev);
3524 try ev.fchownat(3650 try ev.fchownat(
3525 &cancel_region,3651 &sync,
3526 dir.handle,3652 dir.handle,
3527 sub_path_posix,3653 sub_path_posix,
3528 owner orelse std.math.maxInt(linux.uid_t),3654 owner orelse std.math.maxInt(linux.uid_t),
...@@ -3537,10 +3663,10 @@ fn dirSetPermissions(...@@ -3537,10 +3663,10 @@ fn dirSetPermissions(
3537 permissions: Dir.Permissions,3663 permissions: Dir.Permissions,
3538) Dir.SetPermissionsError!void {3664) Dir.SetPermissionsError!void {
3539 const ev: *Evented = @ptrCast(@alignCast(userdata));3665 const ev: *Evented = @ptrCast(@alignCast(userdata));
3540 var cancel_region: CancelRegion = .init();3666 var sync: CancelRegion.Sync = try .init(ev);
3541 defer cancel_region.deinit();3667 defer sync.deinit(ev);
3542 ev.fchmodat(3668 ev.fchmodat(
3543 &cancel_region,3669 &sync,
3544 dir.handle,3670 dir.handle,
3545 "",3671 "",
3546 permissions.toMode(),3672 permissions.toMode(),
...@@ -3565,10 +3691,10 @@ fn dirSetFilePermissions(...@@ -3565,10 +3691,10 @@ fn dirSetFilePermissions(
3565 const ev: *Evented = @ptrCast(@alignCast(userdata));3691 const ev: *Evented = @ptrCast(@alignCast(userdata));
3566 var path_buffer: [PATH_MAX]u8 = undefined;3692 var path_buffer: [PATH_MAX]u8 = undefined;
3567 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);3693 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3568 var cancel_region: CancelRegion = .init();3694 var sync: CancelRegion.Sync = try .init(ev);
3569 defer cancel_region.deinit();3695 defer sync.deinit(ev);
3570 try ev.fchmodat(3696 try ev.fchmodat(
3571 &cancel_region,3697 &sync,
3572 dir.handle,3698 dir.handle,
3573 sub_path_posix,3699 sub_path_posix,
3574 permissions.toMode(),3700 permissions.toMode(),
...@@ -3585,8 +3711,8 @@ fn dirSetTimestamps(...@@ -3585,8 +3711,8 @@ fn dirSetTimestamps(
3585 const ev: *Evented = @ptrCast(@alignCast(userdata));3711 const ev: *Evented = @ptrCast(@alignCast(userdata));
3586 var path_buffer: [PATH_MAX]u8 = undefined;3712 var path_buffer: [PATH_MAX]u8 = undefined;
3587 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);3713 const sub_path_posix = try pathToPosix(sub_path, &path_buffer);
3588 var cancel_region: CancelRegion = .init();3714 var cancel_region: CancelRegion.Sync = try .init(ev);
3589 defer cancel_region.deinit();3715 defer cancel_region.deinit(ev);
3590 try ev.utimensat(3716 try ev.utimensat(
3591 &cancel_region,3717 &cancel_region,
3592 dir.handle,3718 dir.handle,
...@@ -3680,7 +3806,9 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {...@@ -3680,7 +3806,9 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
36803806
3681fn fileClose(userdata: ?*anyopaque, files: []const File) void {3807fn fileClose(userdata: ?*anyopaque, files: []const File) void {
3682 const ev: *Evented = @ptrCast(@alignCast(userdata));3808 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);
3684}3812}
36853813
3686fn fileWritePositional(3814fn fileWritePositional(
...@@ -3807,16 +3935,16 @@ fn fileReadPositional(...@@ -3807,16 +3935,16 @@ fn fileReadPositional(
38073935
3808fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {3936fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void {
3809 const ev: *Evented = @ptrCast(@alignCast(userdata));3937 const ev: *Evented = @ptrCast(@alignCast(userdata));
3810 var cancel_region: CancelRegion = .init();3938 var sync: CancelRegion.Sync = try .init(ev);
3811 defer cancel_region.deinit();3939 defer sync.deinit(ev);
3812 try ev.lseek(&cancel_region, file.handle, @bitCast(offset), linux.SEEK.CUR);3940 try ev.lseek(&sync, file.handle, @bitCast(offset), linux.SEEK.CUR);
3813}3941}
38143942
3815fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {3943fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void {
3816 const ev: *Evented = @ptrCast(@alignCast(userdata));3944 const ev: *Evented = @ptrCast(@alignCast(userdata));
3817 var cancel_region: CancelRegion = .init();3945 var sync: CancelRegion.Sync = try .init(ev);
3818 defer cancel_region.deinit();3946 defer sync.deinit(ev);
3819 try ev.lseek(&cancel_region, file.handle, offset, linux.SEEK.SET);3947 try ev.lseek(&sync, file.handle, offset, linux.SEEK.SET);
3820}3948}
38213949
3822fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {3950fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
...@@ -3858,14 +3986,12 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {...@@ -3858,14 +3986,12 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
38583986
3859fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {3987fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
3860 const ev: *Evented = @ptrCast(@alignCast(userdata));3988 const ev: *Evented = @ptrCast(@alignCast(userdata));
3861 _ = ev;3989 var sync: CancelRegion.Sync = try .init(ev);
3862 var cancel_region: CancelRegion = .init();3990 defer sync.deinit(ev);
3863 defer cancel_region.deinit();
3864 while (true) {3991 while (true) {
3865 try cancel_region.await(.nothing);3992 try sync.cancel_region.await(.nothing);
3866 var wsz: winsize = undefined;3993 var wsz: winsize = undefined;
3867 const fd: usize = @bitCast(@as(isize, file.handle));3994 const rc = linux.ioctl(file.handle, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
3868 const rc = linux.syscall3(.ioctl, fd, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
3869 switch (linux.errno(rc)) {3995 switch (linux.errno(rc)) {
3870 .SUCCESS => return true,3996 .SUCCESS => return true,
3871 .INTR => continue,3997 .INTR => continue,
...@@ -3923,10 +4049,10 @@ fn fileSetOwner(...@@ -3923,10 +4049,10 @@ fn fileSetOwner(
3923 group: ?File.Gid,4049 group: ?File.Gid,
3924) File.SetOwnerError!void {4050) File.SetOwnerError!void {
3925 const ev: *Evented = @ptrCast(@alignCast(userdata));4051 const ev: *Evented = @ptrCast(@alignCast(userdata));
3926 var cancel_region: CancelRegion = .init();4052 var sync: CancelRegion.Sync = try .init(ev);
3927 defer cancel_region.deinit();4053 defer sync.deinit(ev);
3928 try ev.fchownat(4054 try ev.fchownat(
3929 &cancel_region,4055 &sync,
3930 file.handle,4056 file.handle,
3931 "",4057 "",
3932 owner orelse std.math.maxInt(linux.uid_t),4058 owner orelse std.math.maxInt(linux.uid_t),
...@@ -3941,10 +4067,10 @@ fn fileSetPermissions(...@@ -3941,10 +4067,10 @@ fn fileSetPermissions(
3941 permissions: File.Permissions,4067 permissions: File.Permissions,
3942) File.SetPermissionsError!void {4068) File.SetPermissionsError!void {
3943 const ev: *Evented = @ptrCast(@alignCast(userdata));4069 const ev: *Evented = @ptrCast(@alignCast(userdata));
3944 var cancel_region: CancelRegion = .init();4070 var sync: CancelRegion.Sync = try .init(ev);
3945 defer cancel_region.deinit();4071 defer sync.deinit(ev);
3946 ev.fchmodat(4072 ev.fchmodat(
3947 &cancel_region,4073 &sync,
3948 file.handle,4074 file.handle,
3949 "",4075 "",
3950 permissions.toMode(),4076 permissions.toMode(),
...@@ -3965,10 +4091,10 @@ fn fileSetTimestamps(...@@ -3965,10 +4091,10 @@ fn fileSetTimestamps(
3965 options: File.SetTimestampsOptions,4091 options: File.SetTimestampsOptions,
3966) File.SetTimestampsError!void {4092) File.SetTimestampsError!void {
3967 const ev: *Evented = @ptrCast(@alignCast(userdata));4093 const ev: *Evented = @ptrCast(@alignCast(userdata));
3968 var cancel_region: CancelRegion = .init();4094 var sync: CancelRegion.Sync = try .init(ev);
3969 defer cancel_region.deinit();4095 defer sync.deinit(ev);
3970 try ev.utimensat(4096 try ev.utimensat(
3971 &cancel_region,4097 &sync,
3972 file.handle,4098 file.handle,
3973 "",4099 "",
3974 if (options.modify_timestamp != .now or options.access_timestamp != .now) &.{4100 if (options.modify_timestamp != .now or options.access_timestamp != .now) &.{
...@@ -3981,9 +4107,9 @@ fn fileSetTimestamps(...@@ -3981,9 +4107,9 @@ fn fileSetTimestamps(
39814107
3982fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {4108fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!void {
3983 const ev: *Evented = @ptrCast(@alignCast(userdata));4109 const ev: *Evented = @ptrCast(@alignCast(userdata));
3984 var cancel_region: CancelRegion = .init();4110 var sync: CancelRegion.Sync = try .init(ev);
3985 defer cancel_region.deinit();4111 defer sync.deinit(ev);
3986 ev.flock(&cancel_region, file.handle, lock, .blocking) catch |err| switch (err) {4112 ev.flock(&sync, file.handle, lock, .blocking) catch |err| switch (err) {
3987 error.WouldBlock => unreachable, // blocking4113 error.WouldBlock => unreachable, // blocking
3988 else => |e| return e,4114 else => |e| return e,
3989 };4115 };
...@@ -3991,9 +4117,9 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v...@@ -3991,9 +4117,9 @@ fn fileLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!v
39914117
3992fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {4118fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockError!bool {
3993 const ev: *Evented = @ptrCast(@alignCast(userdata));4119 const ev: *Evented = @ptrCast(@alignCast(userdata));
3994 var cancel_region: CancelRegion = .init();4120 var sync: CancelRegion.Sync = try .init(ev);
3995 defer cancel_region.deinit();4121 defer sync.deinit(ev);
3996 ev.flock(&cancel_region, file.handle, lock, switch (lock) {4122 ev.flock(&sync, file.handle, lock, switch (lock) {
3997 .none => .blocking,4123 .none => .blocking,
3998 .shared, .exclusive => .nonblocking,4124 .shared, .exclusive => .nonblocking,
3999 }) catch |err| switch (err) {4125 }) catch |err| switch (err) {
...@@ -4005,9 +4131,9 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro...@@ -4005,9 +4131,9 @@ fn fileTryLock(userdata: ?*anyopaque, file: File, lock: File.Lock) File.LockErro
40054131
4006fn fileUnlock(userdata: ?*anyopaque, file: File) void {4132fn fileUnlock(userdata: ?*anyopaque, file: File) void {
4007 const ev: *Evented = @ptrCast(@alignCast(userdata));4133 const ev: *Evented = @ptrCast(@alignCast(userdata));
4008 var cancel_region: CancelRegion = .initBlocked();4134 var sync: CancelRegion.Sync = .initBlocked(ev);
4009 defer cancel_region.deinit();4135 defer sync.deinit(ev);
4010 ev.flock(&cancel_region, file.handle, .none, .blocking) catch |err| switch (err) {4136 ev.flock(&sync, file.handle, .none, .blocking) catch |err| switch (err) {
4011 error.Canceled => unreachable, // blocked4137 error.Canceled => unreachable, // blocked
4012 error.WouldBlock => unreachable, // blocking4138 error.WouldBlock => unreachable, // blocking
4013 error.SystemResources => return recoverableOsBugDetected(), // Resource deallocation.4139 error.SystemResources => return recoverableOsBugDetected(), // Resource deallocation.
...@@ -4018,9 +4144,9 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {...@@ -4018,9 +4144,9 @@ fn fileUnlock(userdata: ?*anyopaque, file: File) void {
40184144
4019fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {4145fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!void {
4020 const ev: *Evented = @ptrCast(@alignCast(userdata));4146 const ev: *Evented = @ptrCast(@alignCast(userdata));
4021 var cancel_region: CancelRegion = .init();4147 var sync: CancelRegion.Sync = try .init(ev);
4022 defer cancel_region.deinit();4148 defer sync.deinit(ev);
4023 ev.flock(&cancel_region, file.handle, .shared, .nonblocking) catch |err| switch (err) {4149 ev.flock(&sync, file.handle, .shared, .nonblocking) catch |err| switch (err) {
4024 error.WouldBlock => return errnoBug(.AGAIN), // File was not locked in exclusive mode.4150 error.WouldBlock => return errnoBug(.AGAIN), // File was not locked in exclusive mode.
4025 error.SystemResources => return errnoBug(.NOLCK), // Lock already obtained.4151 error.SystemResources => return errnoBug(.NOLCK), // Lock already obtained.
4026 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Lock already obtained.4152 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Lock already obtained.
...@@ -4030,9 +4156,9 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!...@@ -4030,9 +4156,9 @@ fn fileDowngradeLock(userdata: ?*anyopaque, file: File) File.DowngradeLockError!
40304156
4031fn fileRealPath(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {4157fn fileRealPath(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.RealPathError!usize {
4032 const ev: *Evented = @ptrCast(@alignCast(userdata));4158 const ev: *Evented = @ptrCast(@alignCast(userdata));
4033 var cancel_region: CancelRegion = .init();4159 var sync: CancelRegion.Sync = try .init(ev);
4034 defer cancel_region.deinit();4160 defer sync.deinit(ev);
4035 return ev.realPath(&cancel_region, file.handle, out_buffer);4161 return ev.realPath(&sync, file.handle, out_buffer);
4036}4162}
40374163
4038fn fileHardLink(4164fn fileHardLink(
...@@ -4065,7 +4191,7 @@ fn fileMemoryMapCreate(...@@ -4065,7 +4191,7 @@ fn fileMemoryMapCreate(
4065 options: File.MemoryMap.CreateOptions,4191 options: File.MemoryMap.CreateOptions,
4066) File.MemoryMap.CreateError!File.MemoryMap {4192) File.MemoryMap.CreateError!File.MemoryMap {
4067 const ev: *Evented = @ptrCast(@alignCast(userdata));4193 const ev: *Evented = @ptrCast(@alignCast(userdata));
4068 _ = ev;4194
4069 const prot: linux.PROT = .{4195 const prot: linux.PROT = .{
4070 .READ = options.protection.read,4196 .READ = options.protection.read,
4071 .WRITE = options.protection.write,4197 .WRITE = options.protection.write,
...@@ -4078,10 +4204,10 @@ fn fileMemoryMapCreate(...@@ -4078,10 +4204,10 @@ fn fileMemoryMapCreate(
40784204
4079 const page_align = std.heap.page_size_min;4205 const page_align = std.heap.page_size_min;
40804206
4081 var cancel_region: CancelRegion = .init();4207 var sync: CancelRegion.Sync = try .init(ev);
4082 defer cancel_region.deinit();4208 defer sync.deinit(ev);
4083 const contents = while (true) {4209 const contents = while (true) {
4084 try cancel_region.await(.nothing);4210 try sync.cancel_region.await(.nothing);
4085 const casted_offset = std.math.cast(i64, options.offset) orelse return error.Unseekable;4211 const casted_offset = std.math.cast(i64, options.offset) orelse return error.Unseekable;
4086 const rc = linux.mmap(null, options.len, prot, flags, file.handle, casted_offset);4212 const rc = linux.mmap(null, options.len, prot, flags, file.handle, casted_offset);
4087 switch (linux.errno(rc)) {4213 switch (linux.errno(rc)) {
...@@ -4189,11 +4315,10 @@ fn unlockStderr(userdata: ?*anyopaque) void {...@@ -4189,11 +4315,10 @@ fn unlockStderr(userdata: ?*anyopaque) void {
41894315
4190fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {4316fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize {
4191 const ev: *Evented = @ptrCast(@alignCast(userdata));4317 const ev: *Evented = @ptrCast(@alignCast(userdata));
4192 _ = ev;4318 var sync: CancelRegion.Sync = try .init(ev);
4193 var cancel_region: CancelRegion = .init();4319 defer sync.deinit(ev);
4194 defer cancel_region.deinit();
4195 while (true) {4320 while (true) {
4196 try cancel_region.await(.nothing);4321 try sync.cancel_region.await(.nothing);
4197 switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {4322 switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {
4198 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,4323 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
4199 .INTR => continue,4324 .INTR => continue,
...@@ -4208,48 +4333,19 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr...@@ -4208,48 +4333,19 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
42084333
4209fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void {4334fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void {
4210 const ev: *Evented = @ptrCast(@alignCast(userdata));4335 const ev: *Evented = @ptrCast(@alignCast(userdata));
4211 _ = ev;
4212 if (dir.handle == linux.AT.FDCWD) return;4336 if (dir.handle == linux.AT.FDCWD) return;
4213 var cancel_region: CancelRegion = .init();4337 var sync: CancelRegion.Sync = try .init(ev);
4214 defer cancel_region.deinit();4338 defer sync.deinit(ev);
4215 while (true) {4339 return ev.fchdir(&sync, dir.handle);
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 }
4227}4340}
42284341
4229fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {4342fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {
4230 const ev: *Evented = @ptrCast(@alignCast(userdata));4343 const ev: *Evented = @ptrCast(@alignCast(userdata));
4231 _ = ev;
4232 var path_buffer: [PATH_MAX]u8 = undefined;4344 var path_buffer: [PATH_MAX]u8 = undefined;
4233 const dir_path_posix = try pathToPosix(dir_path, &path_buffer);4345 const dir_path_posix = try pathToPosix(dir_path, &path_buffer);
4234 var cancel_region: CancelRegion = .init();4346 var sync: CancelRegion.Sync = try .init(ev);
4235 defer cancel_region.deinit();4347 defer sync.deinit(ev);
4236 while (true) {4348 return ev.chdir(&sync, dir_path_posix);
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 }
4253}4349}
42544350
4255fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError {4351fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) process.ReplaceError {
...@@ -4275,7 +4371,9 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -4275,7 +4371,9 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
4275 });4371 });
4276 };4372 };
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);
4279}4377}
42804378
4281fn processReplacePath(4379fn processReplacePath(
...@@ -4293,12 +4391,12 @@ fn processReplacePath(...@@ -4293,12 +4391,12 @@ fn processReplacePath(
4293fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {4391fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child {
4294 const ev: *Evented = @ptrCast(@alignCast(userdata));4392 const ev: *Evented = @ptrCast(@alignCast(userdata));
4295 const spawned = try ev.spawn(options);4393 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
4298 // Wait for the child to report any errors in or before `execvpe`.4398 // Wait for the child to report any errors in or before `execvpe`.
4299 var child_err: ForkBailError = undefined;4399 var child_err: ForkBailError = undefined;
4300 var cancel_region: CancelRegion = .initBlocked();
4301 defer cancel_region.deinit();
4302 ev.readAll(&cancel_region, spawned.err_fd, @ptrCast(&child_err)) catch |read_err| {4400 ev.readAll(&cancel_region, spawned.err_fd, @ptrCast(&child_err)) catch |read_err| {
4303 switch (read_err) {4401 switch (read_err) {
4304 error.Canceled => unreachable, // blocked4402 error.Canceled => unreachable, // blocked
...@@ -4336,6 +4434,8 @@ fn processSpawnPath(...@@ -4336,6 +4434,8 @@ fn processSpawnPath(
4336 @panic("TODO processSpawnPath");4434 @panic("TODO processSpawnPath");
4337}4435}
43384436
4437const prog_fileno = 3;
4438
4339const Spawned = struct {4439const Spawned = struct {
4340 pid: pid_t,4440 pid: pid_t,
4341 err_fd: fd_t,4441 err_fd: fd_t,
...@@ -4344,6 +4444,9 @@ const Spawned = struct {...@@ -4344,6 +4444,9 @@ const Spawned = struct {
4344 stderr: ?File,4444 stderr: ?File,
4345};4445};
4346fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned {4446fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned {
4447 var cancel_region: CancelRegion = .init();
4448 defer cancel_region.deinit();
4449
4347 // The child process does need to access (one end of) these pipes. However,4450 // The child process does need to access (one end of) these pipes. However,
4348 // we must initially set CLOEXEC to avoid a race condition. If another thread4451 // we must initially set CLOEXEC to avoid a race condition. If another thread
4349 // is racing to spawn a different child process, we don't want it to inherit4452 // 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...@@ -4358,28 +4461,24 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
43584461
4359 const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined;4462 const stdin_pipe = if (options.stdin == .pipe) try pipe2(pipe_flags) else undefined;
4360 errdefer if (options.stdin == .pipe) {4463 errdefer if (options.stdin == .pipe) {
4361 ev.destroyPipe(stdin_pipe);4464 ev.destroyPipe(&cancel_region, stdin_pipe);
4362 };4465 };
43634466
4364 const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined;4467 const stdout_pipe = if (options.stdout == .pipe) try pipe2(pipe_flags) else undefined;
4365 errdefer if (options.stdout == .pipe) {4468 errdefer if (options.stdout == .pipe) {
4366 ev.destroyPipe(stdout_pipe);4469 ev.destroyPipe(&cancel_region, stdout_pipe);
4367 };4470 };
43684471
4369 const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined;4472 const stderr_pipe = if (options.stderr == .pipe) try pipe2(pipe_flags) else undefined;
4370 errdefer if (options.stderr == .pipe) {4473 errdefer if (options.stderr == .pipe) {
4371 ev.destroyPipe(stderr_pipe);4474 ev.destroyPipe(&cancel_region, stderr_pipe);
4372 };4475 };
43734476
4374 const any_ignore =4477 const any_ignore =
4375 options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore;4478 options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore;
4376 const dev_null_fd = if (any_ignore) dev_null_fd: {4479 const dev_null_fd = if (any_ignore) try ev.null_fd.open(ev, &cancel_region, "/dev/null", .{
4377 var cancel_region: CancelRegion = .init();4480 .ACCMODE = .RDWR,
4378 defer cancel_region.deinit();4481 }) else undefined;
4379 break :dev_null_fd try ev.null_fd.open(ev, &cancel_region, "/dev/null", .{
4380 .ACCMODE = .RDWR,
4381 });
4382 } else undefined;
43834482
4384 const prog_pipe: [2]fd_t = if (options.progress_node.index != .none) pipe: {4483 const prog_pipe: [2]fd_t = if (options.progress_node.index != .none) pipe: {
4385 // We use CLOEXEC for the same reason as in `pipe_flags`.4484 // 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...@@ -4387,7 +4486,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
4387 _ = linux.fcntl(pipe[0], linux.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2));4486 _ = linux.fcntl(pipe[0], linux.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2));
4388 break :pipe pipe;4487 break :pipe pipe;
4389 } else .{ -1, -1 };4488 } else .{ -1, -1 };
4390 errdefer ev.destroyPipe(prog_pipe);4489 errdefer ev.destroyPipe(&cancel_region, prog_pipe);
43914490
4392 var arena_allocator = std.heap.ArenaAllocator.init(ev.allocator());4491 var arena_allocator = std.heap.ArenaAllocator.init(ev.allocator());
4393 defer arena_allocator.deinit();4492 defer arena_allocator.deinit();
...@@ -4405,7 +4504,6 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4405,7 +4504,6 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
4405 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);4504 const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null);
4406 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;4505 for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr;
44074506
4408 const prog_fileno = 3;
4409 comptime assert(@max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO) + 1 == prog_fileno);4507 comptime assert(@max(linux.STDIN_FILENO, linux.STDOUT_FILENO, linux.STDERR_FILENO) + 1 == prog_fileno);
44104508
4411 const env_block = env_block: {4509 const env_block = env_block: {
...@@ -4421,7 +4519,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4421,7 +4519,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
4421 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.4519 // This pipe communicates to the parent errors in the child between `fork` and `execvpe`.
4422 // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.4520 // It is closed by the child (via CLOEXEC) without writing if `execvpe` succeeds.
4423 const err_pipe: [2]fd_t = try pipe2(.{ .CLOEXEC = true });4521 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
4426 try ev.scanEnviron(); // for PATH4524 try ev.scanEnviron(); // for PATH
4427 const PATH = ev.environ.string.PATH orelse default_PATH;4525 const PATH = ev.environ.string.PATH orelse default_PATH;
...@@ -4439,78 +4537,33 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4439,78 +4537,33 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
44394537
4440 if (pid_result == 0) {4538 if (pid_result == 0) {
4441 defer comptime unreachable; // We are the child.4539 defer comptime unreachable; // We are the child.
4442 _ = swapCancelProtection(ev, .blocked);4540 var sync: CancelRegion.Sync = .{ .cancel_region = .initBlocked() };
4443 const ep1 = err_pipe[1];4541 const err = ev.setUpChild(&sync, .{
44444542 .stdin_pipe = stdin_pipe[0],
4445 ev.setUpChildIo(options.stdin, stdin_pipe[0], linux.STDIN_FILENO, dev_null_fd) catch |err|4543 .stdout_pipe = stdout_pipe[1],
4446 ev.forkBail(ep1, err);4544 .stderr_pipe = stderr_pipe[1],
4447 ev.setUpChildIo(options.stdout, stdout_pipe[1], linux.STDOUT_FILENO, dev_null_fd) catch |err|4545 .dev_null_fd = dev_null_fd,
4448 ev.forkBail(ep1, err);4546 .prog_pipe = prog_pipe[1],
4449 ev.setUpChildIo(options.stderr, stderr_pipe[1], linux.STDERR_FILENO, dev_null_fd) catch |err|4547 .argv_buf = argv_buf,
4450 ev.forkBail(ep1, err);4548 .env_block = env_block,
44514549 .PATH = PATH,
4452 switch (options.cwd) {4550 .spawn = options,
4453 .inherit => {},4551 });
4454 .dir => |cwd| processSetCurrentDir(ev, cwd) catch |err| ev.forkBail(ep1, err),4552 ev.writeAll(&sync.cancel_region, err_pipe[1], @ptrCast(&err)) catch {};
4455 .path => |cwd| processSetCurrentPath(ev, cwd) catch |err| ev.forkBail(ep1, err),4553 const exit = if (builtin.single_threaded) linux.exit else linux.exit_group;
4456 }4554 exit(1);
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);
4502 }4555 }
45034556
4504 const pid: pid_t = @intCast(pid_result); // We are the parent.4557 const pid: pid_t = @intCast(pid_result); // We are the parent.
4505 errdefer comptime unreachable; // The child is forked; we must not error from now on4558 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 open4560 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]);4562 if (options.stdin == .pipe) ev.close(&cancel_region, stdin_pipe[0]);
4510 if (options.stdout == .pipe) ev.close(stdout_pipe[1]);4563 if (options.stdout == .pipe) ev.close(&cancel_region, stdout_pipe[1]);
4511 if (options.stderr == .pipe) ev.close(stderr_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
4515 options.progress_node.setIpcFile(ev, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });4568 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 {...@@ -4546,24 +4599,127 @@ pub fn pipe2(flags: linux.O) PipeError![2]fd_t {
4546 else => |err| return unexpectedErrno(err),4599 else => |err| return unexpectedErrno(err),
4547 }4600 }
4548}4601}
4549fn destroyPipe(ev: *Evented, pipe: [2]fd_t) void {4602fn destroyPipe(ev: *Evented, cancel_region: *CancelRegion, pipe: [2]fd_t) void {
4550 if (pipe[0] != -1) ev.close(pipe[0]);4603 if (pipe[0] != -1) ev.close(cancel_region, pipe[0]);
4551 if (pipe[0] != pipe[1]) ev.close(pipe[1]);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 );
4552}4707}
45534708
4554fn setUpChildIo(4709fn setUpChildIo(
4555 ev: *Evented,4710 ev: *Evented,
4711 sync: *CancelRegion.Sync,
4556 stdio: process.SpawnOptions.StdIo,4712 stdio: process.SpawnOptions.StdIo,
4557 pipe_fd: fd_t,4713 pipe_fd: fd_t,
4558 std_fileno: i32,4714 std_fileno: i32,
4559 dev_null_fd: fd_t,4715 dev_null_fd: fd_t,
4560) !void {4716) !void {
4561 switch (stdio) {4717 switch (stdio) {
4562 .pipe => try dup2(pipe_fd, std_fileno),4718 .pipe => try ev.dup2(sync, pipe_fd, std_fileno),
4563 .close => ev.close(std_fileno),4719 .close => ev.close(&sync.cancel_region, std_fileno),
4564 .inherit => {},4720 .inherit => {},
4565 .ignore => try dup2(dev_null_fd, std_fileno),4721 .ignore => try ev.dup2(sync, dev_null_fd, std_fileno),
4566 .file => |file| try dup2(file.handle, std_fileno),4722 .file => |file| try ev.dup2(sync, file.handle, std_fileno),
4567 }4723 }
4568}4724}
45694725
...@@ -4571,11 +4727,10 @@ pub const DupError = error{...@@ -4571,11 +4727,10 @@ pub const DupError = error{
4571 ProcessFdQuotaExceeded,4727 ProcessFdQuotaExceeded,
4572 SystemResources,4728 SystemResources,
4573} || Io.UnexpectedError || Io.Cancelable;4729} || Io.UnexpectedError || Io.Cancelable;
4574pub fn dup2(old_fd: fd_t, new_fd: fd_t) DupError!void {4730pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void {
4575 var cancel_region: CancelRegion = .init();4731 _ = ev;
4576 defer cancel_region.deinit();
4577 while (true) {4732 while (true) {
4578 try cancel_region.await(.nothing);4733 try sync.cancel_region.await(.nothing);
4579 switch (linux.errno(linux.dup2(old_fd, new_fd))) {4734 switch (linux.errno(linux.dup2(old_fd, new_fd))) {
4580 .SUCCESS => {},4735 .SUCCESS => {},
4581 .BUSY, .INTR => continue,4736 .BUSY, .INTR => continue,
...@@ -4588,20 +4743,9 @@ pub fn dup2(old_fd: fd_t, new_fd: fd_t) DupError!void {...@@ -4588,20 +4743,9 @@ pub fn dup2(old_fd: fd_t, new_fd: fd_t) DupError!void {
4588 }4743 }
4589}4744}
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
4604fn execv(4746fn execv(
4747 ev: *Evented,
4748 sync: *CancelRegion.Sync,
4605 arg0_expand: process.ArgExpansion,4749 arg0_expand: process.ArgExpansion,
4606 file: [*:0]const u8,4750 file: [*:0]const u8,
4607 child_argv: [*:null]?[*:0]const u8,4751 child_argv: [*:null]?[*:0]const u8,
...@@ -4609,7 +4753,7 @@ fn execv(...@@ -4609,7 +4753,7 @@ fn execv(
4609 PATH: []const u8,4753 PATH: []const u8,
4610) process.ReplaceError {4754) process.ReplaceError {
4611 const file_slice = std.mem.sliceTo(file, 0);4755 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
4614 // Use of PATH_MAX here is valid as the path_buf will be passed4758 // Use of PATH_MAX here is valid as the path_buf will be passed
4615 // directly to the operating system in posixExecvPath.4759 // directly to the operating system in posixExecvPath.
...@@ -4637,7 +4781,7 @@ fn execv(...@@ -4637,7 +4781,7 @@ fn execv(
4637 .expand => child_argv[0] = full_path,4781 .expand => child_argv[0] = full_path,
4638 .no_expand => {},4782 .no_expand => {},
4639 }4783 }
4640 err = execvPath(full_path, child_argv, env_block);4784 err = ev.execvPath(sync, full_path, child_argv, env_block);
4641 switch (err) {4785 switch (err) {
4642 error.AccessDenied => seen_eacces = true,4786 error.AccessDenied => seen_eacces = true,
4643 error.FileNotFound, error.NotDir => {},4787 error.FileNotFound, error.NotDir => {},
...@@ -4649,13 +4793,14 @@ fn execv(...@@ -4649,13 +4793,14 @@ fn execv(
4649}4793}
4650/// This function ignores PATH environment variable.4794/// This function ignores PATH environment variable.
4651pub fn execvPath(4795pub fn execvPath(
4796 ev: *Evented,
4797 sync: *CancelRegion.Sync,
4652 path: [*:0]const u8,4798 path: [*:0]const u8,
4653 child_argv: [*:null]const ?[*:0]const u8,4799 child_argv: [*:null]const ?[*:0]const u8,
4654 env_block: process.Environ.PosixBlock,4800 env_block: process.Environ.PosixBlock,
4655) process.ReplaceError {4801) process.ReplaceError {
4656 var cancel_region: CancelRegion = .init();4802 _ = ev;
4657 defer cancel_region.deinit();4803 try sync.cancel_region.await(.nothing);
4658 try cancel_region.await(.nothing);
4659 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {4804 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {
4660 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.4805 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
4661 .@"2BIG" => return error.SystemResources,4806 .@"2BIG" => return error.SystemResources,
...@@ -4680,14 +4825,15 @@ pub fn execvPath(...@@ -4680,14 +4825,15 @@ pub fn execvPath(
46804825
4681fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term {4826fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitError!process.Child.Term {
4682 const ev: *Evented = @ptrCast(@alignCast(userdata));4827 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
4685 const pid = child.id.?;4833 const pid = child.id.?;
4686 var info: linux.siginfo_t = undefined;4834 var info: linux.siginfo_t = undefined;
4687 var cancel_region: CancelRegion = .init();
4688 defer cancel_region.deinit();
4689 while (true) {4835 while (true) {
4690 const thread = try cancel_region.awaitIoUring();4836 const thread = try maybe_sync.cancel_region.awaitIoUring();
4691 thread.enqueue().* = .{4837 thread.enqueue().* = .{
4692 .opcode = .WAITID,4838 .opcode = .WAITID,
4693 .flags = 0,4839 .flags = 0,
...@@ -4697,7 +4843,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr...@@ -4697,7 +4843,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
4697 .addr = 0,4843 .addr = 0,
4698 .len = @intFromEnum(linux.P.PID),4844 .len = @intFromEnum(linux.P.PID),
4699 .rw_flags = 0,4845 .rw_flags = 0,
4700 .user_data = @intFromPtr(cancel_region.fiber),4846 .user_data = @intFromPtr(maybe_sync.cancel_region.fiber),
4701 .buf_index = 0,4847 .buf_index = 0,
4702 .personality = 0,4848 .personality = 0,
4703 .splice_fd_in = linux.W.EXITED |4849 .splice_fd_in = linux.W.EXITED |
...@@ -4706,27 +4852,30 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr...@@ -4706,27 +4852,30 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
4706 .resv = 0,4852 .resv = 0,
4707 };4853 };
4708 ev.yield(null, .nothing);4854 ev.yield(null, .nothing);
4709 switch (cancel_region.errno()) {4855 switch (maybe_sync.cancel_region.errno()) {
4710 .SUCCESS => {4856 .SUCCESS => {
4711 if (child.request_resource_usage_statistics) while (true) {4857 if (child.request_resource_usage_statistics) {
4712 try cancel_region.await(.nothing);4858 const sync = try maybe_sync.enterSync(ev);
4713 var rusage: linux.rusage = undefined;4859 while (true) {
4714 switch (linux.errno(linux.waitid(4860 try sync.cancel_region.await(.nothing);
4715 .PID,4861 var rusage: linux.rusage = undefined;
4716 pid,4862 switch (linux.errno(linux.waitid(
4717 &info,4863 .PID,
4718 linux.W.EXITED | linux.W.NOHANG,4864 pid,
4719 &rusage,4865 &info,
4720 ))) {4866 linux.W.EXITED | linux.W.NOHANG,
4721 .SUCCESS => {4867 &rusage,
4722 child.resource_usage_statistics.rusage = rusage;4868 ))) {
4723 break;4869 .SUCCESS => {
4724 },4870 child.resource_usage_statistics.rusage = rusage;
4725 .INTR, .CANCELED => continue,4871 break;
4726 .CHILD => |err| return errnoBug(err), // Double-free.4872 },
4727 else => |err| return unexpectedErrno(err),4873 .INTR, .CANCELED => continue,
4874 .CHILD => |err| return errnoBug(err), // Double-free.
4875 else => |err| return unexpectedErrno(err),
4876 }
4728 }4877 }
4729 };4878 }
4730 const status: u32 = @bitCast(info.fields.common.second.sigchld.status);4879 const status: u32 = @bitCast(info.fields.common.second.sigchld.status);
4731 const code: linux.CLD = @enumFromInt(info.code);4880 const code: linux.CLD = @enumFromInt(info.code);
4732 return switch (code) {4881 return switch (code) {
...@@ -4745,11 +4894,12 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr...@@ -4745,11 +4894,12 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
47454894
4746fn childKill(userdata: ?*anyopaque, child: *process.Child) void {4895fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4747 const ev: *Evented = @ptrCast(@alignCast(userdata));4896 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
4750 const pid = child.id.?;4902 const pid = child.id.?;
4751 var cancel_region: CancelRegion = .initBlocked();
4752 defer cancel_region.deinit();
4753 while (true) switch (linux.errno(linux.kill(pid, .TERM))) {4903 while (true) switch (linux.errno(linux.kill(pid, .TERM))) {
4754 .SUCCESS => break,4904 .SUCCESS => break,
4755 .INTR => continue,4905 .INTR => continue,
...@@ -4758,10 +4908,11 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4758,10 +4908,11 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4758 .SRCH => |err| return errnoBug(err) catch {},4908 .SRCH => |err| return errnoBug(err) catch {},
4759 else => |err| return unexpectedErrno(err) catch {},4909 else => |err| return unexpectedErrno(err) catch {},
4760 };4910 };
4911 maybe_sync.leaveSync(ev);
47614912
4762 var info: linux.siginfo_t = undefined;4913 var info: linux.siginfo_t = undefined;
4763 while (true) {4914 while (true) {
4764 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {4915 const thread = maybe_sync.cancel_region.awaitIoUring() catch |err| switch (err) {
4765 error.Canceled => unreachable, // blocked4916 error.Canceled => unreachable, // blocked
4766 };4917 };
4767 thread.enqueue().* = .{4918 thread.enqueue().* = .{
...@@ -4773,7 +4924,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4773,7 +4924,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4773 .addr = 0,4924 .addr = 0,
4774 .len = @intFromEnum(linux.P.PID),4925 .len = @intFromEnum(linux.P.PID),
4775 .rw_flags = 0,4926 .rw_flags = 0,
4776 .user_data = @intFromPtr(cancel_region.fiber),4927 .user_data = @intFromPtr(maybe_sync.cancel_region.fiber),
4777 .buf_index = 0,4928 .buf_index = 0,
4778 .personality = 0,4929 .personality = 0,
4779 .splice_fd_in = linux.W.EXITED,4930 .splice_fd_in = linux.W.EXITED,
...@@ -4781,7 +4932,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4781,7 +4932,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4781 .resv = 0,4932 .resv = 0,
4782 };4933 };
4783 ev.yield(null, .nothing);4934 ev.yield(null, .nothing);
4784 switch (cancel_region.errno()) {4935 switch (maybe_sync.cancel_region.errno()) {
4785 .SUCCESS => return,4936 .SUCCESS => return,
4786 .INTR, .CANCELED => continue,4937 .INTR, .CANCELED => continue,
4787 .CHILD => |err| return errnoBug(err) catch {}, // Double-free.4938 .CHILD => |err| return errnoBug(err) catch {}, // Double-free.
...@@ -4790,17 +4941,17 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4790,17 +4941,17 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4790 }4941 }
4791}4942}
47924943
4793fn childCleanup(ev: *Evented, child: *process.Child) void {4944fn childCleanup(ev: *Evented, cancel_region: *CancelRegion, child: *process.Child) void {
4794 if (child.stdin) |*stdin| {4945 if (child.stdin) |*stdin| {
4795 ev.close(stdin.handle);4946 ev.close(cancel_region, stdin.handle);
4796 child.stdin = null;4947 child.stdin = null;
4797 }4948 }
4798 if (child.stdout) |*stdout| {4949 if (child.stdout) |*stdout| {
4799 ev.close(stdout.handle);4950 ev.close(cancel_region, stdout.handle);
4800 child.stdout = null;4951 child.stdout = null;
4801 }4952 }
4802 if (child.stderr) |*stderr| {4953 if (child.stderr) |*stderr| {
4803 ev.close(stderr.handle);4954 ev.close(cancel_region, stderr.handle);
4804 child.stderr = null;4955 child.stderr = null;
4805 }4956 }
4806 child.id = null;4957 child.id = null;
...@@ -4985,14 +5136,14 @@ fn netBindIp(...@@ -4985,14 +5136,14 @@ fn netBindIp(
4985) net.IpAddress.BindError!net.Socket {5136) net.IpAddress.BindError!net.Socket {
4986 const ev: *Evented = @ptrCast(@alignCast(userdata));5137 const ev: *Evented = @ptrCast(@alignCast(userdata));
4987 const family = posixAddressFamily(address);5138 const family = posixAddressFamily(address);
4988 var cancel_region: CancelRegion = .init();5139 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
4989 defer cancel_region.deinit();5140 defer maybe_sync.deinit(ev);
4990 const socket_fd = try ev.socket(&cancel_region, family, options);5141 const socket_fd = try ev.socket(&maybe_sync.cancel_region, family, options);
4991 errdefer ev.close(socket_fd);5142 errdefer ev.close(maybe_sync.cancelRegion(), socket_fd);
4992 var storage: PosixAddress = undefined;5143 var storage: PosixAddress = undefined;
4993 var addr_len = addressToPosix(address, &storage);5144 var addr_len = addressToPosix(address, &storage);
4994 try ev.bind(&cancel_region, socket_fd, &storage.any, addr_len);5145 try ev.bind(&maybe_sync.cancel_region, socket_fd, &storage.any, addr_len);
4995 try ev.getsockname(&cancel_region, socket_fd, &storage.any, &addr_len);5146 try ev.getsockname(try maybe_sync.enterSync(ev), socket_fd, &storage.any, &addr_len);
4996 return .{5147 return .{
4997 .handle = socket_fd,5148 .handle = socket_fd,
4998 .address = addressFromPosix(&storage),5149 .address = addressFromPosix(&storage),
...@@ -5268,7 +5419,9 @@ fn netWriteFileUnavailable(...@@ -5268,7 +5419,9 @@ fn netWriteFileUnavailable(
52685419
5269fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {5420fn netClose(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
5270 const ev: *Evented = @ptrCast(@alignCast(userdata));5421 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);
5272}5425}
52735426
5274fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {5427fn netCloseUnavailable(userdata: ?*anyopaque, handles: []const net.Socket.Handle) void {
...@@ -5407,9 +5560,28 @@ fn bind(...@@ -5407,9 +5560,28 @@ fn bind(
5407 }5560 }
5408}5561}
54095562
5410fn close(ev: *Evented, fd: fd_t) void {5563fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {
5411 var cancel_region: CancelRegion = .initBlocked();5564 _ = ev;
5412 defer cancel_region.deinit();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 {
5413 while (true) {5585 while (true) {
5414 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {5586 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {
5415 error.Canceled => unreachable, // blocked5587 error.Canceled => unreachable, // blocked
...@@ -5440,9 +5612,26 @@ fn close(ev: *Evented, fd: fd_t) void {...@@ -5440,9 +5612,26 @@ fn close(ev: *Evented, fd: fd_t) void {
5440 }5612 }
5441}5613}
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
5443fn fchmodat(5632fn fchmodat(
5444 ev: *Evented,5633 ev: *Evented,
5445 cancel_region: *CancelRegion,5634 sync: *CancelRegion.Sync,
5446 dir: fd_t,5635 dir: fd_t,
5447 path: [*:0]const u8,5636 path: [*:0]const u8,
5448 mode: linux.mode_t,5637 mode: linux.mode_t,
...@@ -5450,7 +5639,7 @@ fn fchmodat(...@@ -5450,7 +5639,7 @@ fn fchmodat(
5450) Dir.SetFilePermissionsError!void {5639) Dir.SetFilePermissionsError!void {
5451 _ = ev;5640 _ = ev;
5452 while (true) {5641 while (true) {
5453 try cancel_region.await(.nothing);5642 try sync.cancel_region.await(.nothing);
5454 switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {5643 switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {
5455 .SUCCESS => return,5644 .SUCCESS => return,
5456 .INTR => continue,5645 .INTR => continue,
...@@ -5473,7 +5662,7 @@ fn fchmodat(...@@ -5473,7 +5662,7 @@ fn fchmodat(
54735662
5474fn fchownat(5663fn fchownat(
5475 ev: *Evented,5664 ev: *Evented,
5476 cancel_region: *CancelRegion,5665 sync: *CancelRegion.Sync,
5477 dir: fd_t,5666 dir: fd_t,
5478 path: [*:0]const u8,5667 path: [*:0]const u8,
5479 owner: linux.uid_t,5668 owner: linux.uid_t,
...@@ -5482,7 +5671,7 @@ fn fchownat(...@@ -5482,7 +5671,7 @@ fn fchownat(
5482) File.SetOwnerError!void {5671) File.SetOwnerError!void {
5483 _ = ev;5672 _ = ev;
5484 while (true) {5673 while (true) {
5485 try cancel_region.await(.nothing);5674 try sync.cancel_region.await(.nothing);
5486 switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {5675 switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {
5487 .SUCCESS => return,5676 .SUCCESS => return,
5488 .INTR => continue,5677 .INTR => continue,
...@@ -5504,13 +5693,13 @@ fn fchownat(...@@ -5504,13 +5693,13 @@ fn fchownat(
55045693
5505fn flock(5694fn flock(
5506 ev: *Evented,5695 ev: *Evented,
5507 cancel_region: *CancelRegion,5696 sync: *CancelRegion.Sync,
5508 fd: fd_t,5697 fd: fd_t,
5509 op: File.Lock,5698 op: File.Lock,
5510 blocking: enum { blocking, nonblocking },5699 blocking: enum { blocking, nonblocking },
5511) (File.LockError || error{WouldBlock})!void {5700) (File.LockError || error{WouldBlock})!void {
5512 while (true) {5701 while (true) {
5513 try cancel_region.await(.nothing);5702 try sync.cancel_region.await(.nothing);
5514 switch (linux.errno(linux.flock(fd, LOCK.NB | @as(i32, switch (op) {5703 switch (linux.errno(linux.flock(fd, LOCK.NB | @as(i32, switch (op) {
5515 .none => LOCK.UN,5704 .none => LOCK.UN,
5516 .shared => LOCK.SH,5705 .shared => LOCK.SH,
...@@ -5522,7 +5711,7 @@ fn flock(...@@ -5522,7 +5711,7 @@ fn flock(
5522 .INVAL => |err| return errnoBug(err), // invalid parameters5711 .INVAL => |err| return errnoBug(err), // invalid parameters
5523 .NOLCK => return error.SystemResources,5712 .NOLCK => return error.SystemResources,
5524 .AGAIN => {5713 .AGAIN => {
5525 const thread = try cancel_region.awaitIoUring();5714 const thread = try sync.cancel_region.awaitIoUring();
5526 thread.enqueue().* = .{5715 thread.enqueue().* = .{
5527 .opcode = .NOP,5716 .opcode = .NOP,
5528 .flags = 0,5717 .flags = 0,
...@@ -5532,7 +5721,7 @@ fn flock(...@@ -5532,7 +5721,7 @@ fn flock(
5532 .addr = 0,5721 .addr = 0,
5533 .len = 0,5722 .len = 0,
5534 .rw_flags = 0,5723 .rw_flags = 0,
5535 .user_data = @intFromPtr(cancel_region.fiber),5724 .user_data = @intFromPtr(sync.cancel_region.fiber),
5536 .buf_index = 0,5725 .buf_index = 0,
5537 .personality = 0,5726 .personality = 0,
5538 .splice_fd_in = 0,5727 .splice_fd_in = 0,
...@@ -5540,7 +5729,7 @@ fn flock(...@@ -5540,7 +5729,7 @@ fn flock(
5540 .resv = 0,5729 .resv = 0,
5541 };5730 };
5542 ev.yield(null, .nothing);5731 ev.yield(null, .nothing);
5543 switch (cancel_region.errno()) {5732 switch (sync.cancel_region.errno()) {
5544 .SUCCESS, .INTR, .CANCELED => {},5733 .SUCCESS, .INTR, .CANCELED => {},
5545 else => unreachable,5734 else => unreachable,
5546 }5735 }
...@@ -5557,14 +5746,14 @@ fn flock(...@@ -5557,14 +5746,14 @@ fn flock(
55575746
5558fn getsockname(5747fn getsockname(
5559 ev: *Evented,5748 ev: *Evented,
5560 cancel_region: *CancelRegion,5749 sync: *CancelRegion.Sync,
5561 socket_fd: fd_t,5750 socket_fd: fd_t,
5562 addr: *linux.sockaddr,5751 addr: *linux.sockaddr,
5563 addr_len: *linux.socklen_t,5752 addr_len: *linux.socklen_t,
5564) !void {5753) !void {
5565 _ = ev;5754 _ = ev;
5566 while (true) {5755 while (true) {
5567 try cancel_region.await(.nothing);5756 try sync.cancel_region.await(.nothing);
5568 switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {5757 switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {
5569 .SUCCESS => return,5758 .SUCCESS => return,
5570 .INTR => continue,5759 .INTR => continue,
...@@ -5633,14 +5822,14 @@ fn linkat(...@@ -5633,14 +5822,14 @@ fn linkat(
56335822
5634fn lseek(5823fn lseek(
5635 ev: *Evented,5824 ev: *Evented,
5636 cancel_region: *CancelRegion,5825 sync: *CancelRegion.Sync,
5637 fd: fd_t,5826 fd: fd_t,
5638 offset: u64,5827 offset: u64,
5639 whence: u32,5828 whence: u32,
5640) File.SeekError!void {5829) File.SeekError!void {
5641 _ = ev;5830 _ = ev;
5642 while (true) {5831 while (true) {
5643 try cancel_region.await(.nothing);5832 try sync.cancel_region.await(.nothing);
5644 var result: u64 = undefined;5833 var result: u64 = undefined;
5645 switch (linux.errno(switch (@sizeOf(usize)) {5834 switch (linux.errno(switch (@sizeOf(usize)) {
5646 else => comptime unreachable,5835 else => comptime unreachable,
...@@ -5837,7 +6026,7 @@ fn readAll(...@@ -5837,7 +6026,7 @@ fn readAll(
58376026
5838fn realPath(6027fn realPath(
5839 ev: *Evented,6028 ev: *Evented,
5840 cancel_region: *CancelRegion,6029 sync: *CancelRegion.Sync,
5841 fd: fd_t,6030 fd: fd_t,
5842 out_buffer: []u8,6031 out_buffer: []u8,
5843) File.RealPathError!usize {6032) File.RealPathError!usize {
...@@ -5846,7 +6035,7 @@ fn realPath(...@@ -5846,7 +6035,7 @@ fn realPath(
5846 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{fd}, 0) catch6035 const proc_path = std.fmt.bufPrintSentinel(&procfs_buf, "/proc/self/fd/{d}", .{fd}, 0) catch
5847 unreachable;6036 unreachable;
5848 while (true) {6037 while (true) {
5849 try cancel_region.await(.nothing);6038 try sync.cancel_region.await(.nothing);
5850 const rc = linux.readlink(proc_path, out_buffer.ptr, out_buffer.len);6039 const rc = linux.readlink(proc_path, out_buffer.ptr, out_buffer.len);
5851 switch (linux.errno(rc)) {6040 switch (linux.errno(rc)) {
5852 .SUCCESS => return rc,6041 .SUCCESS => return rc,
...@@ -6025,7 +6214,7 @@ fn socket(...@@ -6025,7 +6214,7 @@ fn socket(
6025 else => |err| return unexpectedErrno(err),6214 else => |err| return unexpectedErrno(err),
6026 }6215 }
6027 };6216 };
6028 errdefer ev.close(socket_fd);6217 errdefer ev.close(cancel_region, socket_fd);
60296218
6030 if (options.ip6_only) {6219 if (options.ip6_only) {
6031 if (linux.IPV6 == void) return error.OptionUnsupported;6220 if (linux.IPV6 == void) return error.OptionUnsupported;
...@@ -6103,7 +6292,7 @@ fn urandomReadAll(...@@ -6103,7 +6292,7 @@ fn urandomReadAll(
61036292
6104fn utimensat(6293fn utimensat(
6105 ev: *Evented,6294 ev: *Evented,
6106 cancel_region: *CancelRegion,6295 sync: *CancelRegion.Sync,
6107 dir: fd_t,6296 dir: fd_t,
6108 path: [*:0]const u8,6297 path: [*:0]const u8,
6109 times: ?*const [2]linux.timespec,6298 times: ?*const [2]linux.timespec,
...@@ -6111,7 +6300,7 @@ fn utimensat(...@@ -6111,7 +6300,7 @@ fn utimensat(
6111) File.SetTimestampsError!void {6300) File.SetTimestampsError!void {
6112 _ = ev;6301 _ = ev;
6113 while (true) {6302 while (true) {
6114 try cancel_region.await(.nothing);6303 try sync.cancel_region.await(.nothing);
6115 switch (linux.errno(linux.utimensat(dir, path, times, flags))) {6304 switch (linux.errno(linux.utimensat(dir, path, times, flags))) {
6116 .SUCCESS => return,6305 .SUCCESS => return,
6117 .INTR => continue,6306 .INTR => continue,
lib/std/Io/Threaded.zig+3-4
...@@ -1543,7 +1543,7 @@ pub const InitOptions = struct {...@@ -1543,7 +1543,7 @@ pub const InitOptions = struct {
1543 /// this limit, calls to `Io.async` when all threads are busy run the task1543 /// this limit, calls to `Io.async` when all threads are busy run the task
1544 /// immediately.1544 /// immediately.
1545 ///1545 ///
1546 /// Defaults to a number equal to logical CPU cores.1546 /// Defaults to one less than the number of logical CPU cores.
1547 ///1547 ///
1548 /// Protected by `Threaded.mutex` once the I/O instance is already in use. See1548 /// Protected by `Threaded.mutex` once the I/O instance is already in use. See
1549 /// `setAsyncLimit`.1549 /// `setAsyncLimit`.
...@@ -1552,8 +1552,7 @@ pub const InitOptions = struct {...@@ -1552,8 +1552,7 @@ pub const InitOptions = struct {
1552 /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread1552 /// tasks. Until this limit, calls to `Io.concurrent` will increase the thread
1553 /// pool size.1553 /// pool size.
1554 ///1554 ///
1555 /// concurrent tasks. After this number, calls to `Io.concurrent` return1555 /// After this number, calls to `Io.concurrent` return `error.ConcurrencyUnavailable`.
1556 /// `error.ConcurrencyUnavailable`.
1557 concurrent_limit: Io.Limit = .unlimited,1556 concurrent_limit: Io.Limit = .unlimited,
1558 /// Affects the following operations:1557 /// Affects the following operations:
1559 /// * `processExecutablePath` on OpenBSD and Haiku.1558 /// * `processExecutablePath` on OpenBSD and Haiku.
...@@ -1562,7 +1561,7 @@ pub const InitOptions = struct {...@@ -1562,7 +1561,7 @@ pub const InitOptions = struct {
1562 /// * `fileIsTty`1561 /// * `fileIsTty`
1563 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").1562 /// * `processExecutablePath` on OpenBSD and Haiku (observes "PATH").
1564 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`1563 /// * `processSpawn`, `processSpawnPath`, `processReplace`, `processReplacePath`
1565 environ: process.Environ,1564 environ: process.Environ = .empty,
1566 /// If set to `true`, `File.MemoryMap` APIs will always take the fallback path.1565 /// If set to `true`, `File.MemoryMap` APIs will always take the fallback path.
1567 disable_memory_mapping: bool = false,1566 disable_memory_mapping: bool = false,
1568};1567};