authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-15 19:29:21+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-02-15 19:29:21+01:00
logc8f54a2f07fd4b1bc6ba83c618d0e6f6de265b5c
tree460566d2e6d92c5c1916493b87e48d13a1e5b1d2
parentc6eeae8a8c2314fd35b5baddb18f33b0122d319f
parent5763f7dbcc65288d7d13236f82eec9a23d97bee7

Merge pull request 'std.Io: remove select function' (#31223) from remove-select into master

Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31223

7 files changed, 209 insertions(+), 483 deletions(-)

lib/std/Io.zig-38
...@@ -144,10 +144,6 @@ pub const VTable = struct {...@@ -144,10 +144,6 @@ pub const VTable = struct {
144 swapCancelProtection: *const fn (?*anyopaque, new: CancelProtection) CancelProtection,144 swapCancelProtection: *const fn (?*anyopaque, new: CancelProtection) CancelProtection,
145 checkCancel: *const fn (?*anyopaque) Cancelable!void,145 checkCancel: *const fn (?*anyopaque) Cancelable!void,
146146
147 /// Blocks until one of the futures from the list has a result ready, such
148 /// that awaiting it will not block. Returns that index.
149 select: *const fn (?*anyopaque, futures: []const *AnyFuture) Cancelable!usize,
150
151 futexWait: *const fn (?*anyopaque, ptr: *const u32, expected: u32, Timeout) Cancelable!void,147 futexWait: *const fn (?*anyopaque, ptr: *const u32, expected: u32, Timeout) Cancelable!void,
152 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,148 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
153 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,149 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
...@@ -2120,40 +2116,6 @@ pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void {...@@ -2120,40 +2116,6 @@ pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void {
2120 } });2116 } });
2121}2117}
21222118
2123/// Given a struct with each field a `*Future`, returns a union with the same
2124/// fields, each field type the future's result.
2125pub fn SelectUnion(S: type) type {
2126 const struct_fields = @typeInfo(S).@"struct".fields;
2127 var names: [struct_fields.len][]const u8 = undefined;
2128 var types: [struct_fields.len]type = undefined;
2129 for (struct_fields, &names, &types) |struct_field, *union_field_name, *UnionFieldType| {
2130 const FieldFuture = @typeInfo(struct_field.type).pointer.child;
2131 union_field_name.* = struct_field.name;
2132 UnionFieldType.* = @FieldType(FieldFuture, "result");
2133 }
2134 return @Union(.auto, std.meta.FieldEnum(S), &names, &types, &@splat(.{}));
2135}
2136
2137/// `s` is a struct with every field a `*Future(T)`, where `T` can be any type,
2138/// and can be different for each field.
2139pub fn select(io: Io, s: anytype) Cancelable!SelectUnion(@TypeOf(s)) {
2140 const U = SelectUnion(@TypeOf(s));
2141 const S = @TypeOf(s);
2142 const fields = @typeInfo(S).@"struct".fields;
2143 var futures: [fields.len]*AnyFuture = undefined;
2144 inline for (fields, &futures) |field, *any_future| {
2145 const future = @field(s, field.name);
2146 any_future.* = future.any_future orelse return @unionInit(U, field.name, future.result);
2147 }
2148 switch (try io.vtable.select(io.userdata, &futures)) {
2149 inline 0...(fields.len - 1) => |selected_index| {
2150 const field_name = fields[selected_index].name;
2151 return @unionInit(U, field_name, @field(s, field_name).await(io));
2152 },
2153 else => unreachable,
2154 }
2155}
2156
2157pub const LockedStderr = struct {2119pub const LockedStderr = struct {
2158 file_writer: *File.Writer,2120 file_writer: *File.Writer,
2159 terminal_mode: Terminal.Mode,2121 terminal_mode: Terminal.Mode,
lib/std/Io/Dispatch.zig+33-103
...@@ -100,15 +100,11 @@ const Fiber = struct {...@@ -100,15 +100,11 @@ const Fiber = struct {
100 required_align: void align(4),100 required_align: void align(4),
101 evented: *Evented,101 evented: *Evented,
102 context: Io.fiber.Context,102 context: Io.fiber.Context,
103 await_count: i32,
104 link: union {103 link: union {
105 awaiter: ?*Fiber,104 awaiter: ?*Fiber,
106 group: struct { prev: ?*Fiber, next: ?*Fiber },105 group: struct { prev: ?*Fiber, next: ?*Fiber },
107 },106 },
108 status: union(enum) {107 awaiting_group: Group,
109 queue_next: ?*Fiber,
110 awaiting_group: Group,
111 },
112 cancel_status: CancelStatus,108 cancel_status: CancelStatus,
113 cancel_protection: CancelProtection,109 cancel_protection: CancelProtection,
114110
...@@ -123,7 +119,6 @@ const Fiber = struct {...@@ -123,7 +119,6 @@ const Fiber = struct {
123 const Awaiting = enum(@Int(.unsigned, @bitSizeOf(usize) - shift)) {119 const Awaiting = enum(@Int(.unsigned, @bitSizeOf(usize) - shift)) {
124 nothing = 0,120 nothing = 0,
125 group = 1,121 group = 1,
126 select = 2,
127 _,122 _,
128123
129 const shift = 1;124 const shift = 1;
...@@ -216,7 +211,6 @@ const Fiber = struct {...@@ -216,7 +211,6 @@ const Fiber = struct {
216 }211 }
217212
218 fn destroy(fiber: *Fiber, ev: *Evented) void {213 fn destroy(fiber: *Fiber, ev: *Evented) void {
219 assert(fiber.status.queue_next == null);
220 ev.allocator().free(fiber.allocatedSlice());214 ev.allocator().free(fiber.allocatedSlice());
221 }215 }
222216
...@@ -272,14 +266,11 @@ const Fiber = struct {...@@ -272,14 +266,11 @@ const Fiber = struct {
272 .group => {266 .group => {
273 // The awaiter received a cancelation request while awaiting a group,267 // The awaiter received a cancelation request while awaiting a group,
274 // so propagate the cancelation to the group.268 // so propagate the cancelation to the group.
275 if (fiber.status.awaiting_group.cancel(ev, null)) {269 if (fiber.awaiting_group.cancel(ev, null)) {
276 fiber.status = .{ .queue_next = null };270 fiber.awaiting_group = undefined;
277 ev.queue.async(fiber, &Fiber.@"resume");271 ev.queue.async(fiber, &Fiber.@"resume");
278 }272 }
279 },273 },
280 .select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
281 ev.queue.async(fiber, &Fiber.@"resume");
282 },
283 _ => |awaiting| awaiting.toCancelable().async(),274 _ => |awaiting| awaiting.toCancelable().async(),
284 }275 }
285 }276 }
...@@ -370,8 +361,6 @@ pub fn io(ev: *Evented) Io {...@@ -370,8 +361,6 @@ pub fn io(ev: *Evented) Io {
370 .swapCancelProtection = swapCancelProtection,361 .swapCancelProtection = swapCancelProtection,
371 .checkCancel = checkCancel,362 .checkCancel = checkCancel,
372363
373 .select = select,
374
375 .futexWait = futexWait,364 .futexWait = futexWait,
376 .futexWaitUncancelable = futexWaitUncancelable,365 .futexWaitUncancelable = futexWaitUncancelable,
377 .futexWake = futexWake,366 .futexWake = futexWake,
...@@ -522,9 +511,8 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v...@@ -522,9 +511,8 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
522 .required_align = {},511 .required_align = {},
523 .evented = ev,512 .evented = ev,
524 .context = undefined,513 .context = undefined,
525 .await_count = 0,
526 .link = .{ .awaiter = null },514 .link = .{ .awaiter = null },
527 .status = .{ .queue_next = null },515 .awaiting_group = undefined,
528 .cancel_status = .unrequested,516 .cancel_status = .unrequested,
529 .cancel_protection = .unblocked,517 .cancel_protection = .unblocked,
530 },518 },
...@@ -642,7 +630,7 @@ const SwitchMessage = struct {...@@ -642,7 +630,7 @@ const SwitchMessage = struct {
642630
643 const PendingTask = union(enum) {631 const PendingTask = union(enum) {
644 nothing,632 nothing,
645 await: u31,633 await: *Fiber,
646 activate: c.dispatch.object_t,634 activate: c.dispatch.object_t,
647 @"resume": c.dispatch.object_t,635 @"resume": c.dispatch.object_t,
648 group_await: Group,636 group_await: Group,
...@@ -661,10 +649,10 @@ const SwitchMessage = struct {...@@ -661,10 +649,10 @@ const SwitchMessage = struct {
661 thread.current_context = message.contexts.new;649 thread.current_context = message.contexts.new;
662 switch (message.pending_task) {650 switch (message.pending_task) {
663 .nothing => {},651 .nothing => {},
664 .await => |count| {652 .await => |awaiting| {
665 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));653 const awaiter: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
666 if (@atomicRmw(i32, &fiber.await_count, .Sub, count, .monotonic) > 0)654 if (@atomicRmw(?*Fiber, &awaiting.link.awaiter, .Xchg, awaiter, .acq_rel) ==
667 ev.queue.async(fiber, &Fiber.@"resume");655 Fiber.finished) ev.queue.async(awaiter, &Fiber.@"resume");
668 },656 },
669 .activate => |object| object.activate(),657 .activate => |object| object.activate(),
670 .@"resume" => |object| object.@"resume"(),658 .@"resume" => |object| object.@"resume"(),
...@@ -998,7 +986,7 @@ fn crashHandler(userdata: ?*anyopaque) void {...@@ -998,7 +986,7 @@ fn crashHandler(userdata: ?*anyopaque) void {
998}986}
999987
1000const AsyncClosure = struct {988const AsyncClosure = struct {
1001 ev: *Evented,989 evented: *Evented,
1002 fiber: *Fiber,990 fiber: *Fiber,
1003 start: *const fn (context: *const anyopaque, result: *anyopaque) void,991 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
1004 result_align: Alignment,992 result_align: Alignment,
...@@ -1035,13 +1023,13 @@ const AsyncClosure = struct {...@@ -1035,13 +1023,13 @@ const AsyncClosure = struct {
1035 closure: *AsyncClosure,1023 closure: *AsyncClosure,
1036 message: *const SwitchMessage,1024 message: *const SwitchMessage,
1037 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {1025 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
1038 message.handle(closure.ev);1026 const ev = closure.evented;
1039 const fiber = closure.fiber;1027 const fiber = closure.fiber;
1028 message.handle(ev);
1040 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));1029 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
1041 if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|1030 if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|
1042 if (@atomicRmw(i32, &awaiter.await_count, .Add, 1, .monotonic) == -1)1031 ev.queue.async(awaiter, &Fiber.@"resume");
1043 closure.ev.queue.async(awaiter, &Fiber.@"resume");1032 ev.yield(.nothing);
1044 closure.ev.yield(.nothing);
1045 unreachable; // switched to dead fiber1033 unreachable; // switched to dead fiber
1046 }1034 }
1047};1035};
...@@ -1096,14 +1084,13 @@ fn concurrent(...@@ -1096,14 +1084,13 @@ fn concurrent(
1096 },1084 },
1097 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1085 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1098 },1086 },
1099 .await_count = 0,
1100 .link = .{ .awaiter = null },1087 .link = .{ .awaiter = null },
1101 .status = .{ .queue_next = null },1088 .awaiting_group = undefined,
1102 .cancel_status = .unrequested,1089 .cancel_status = .unrequested,
1103 .cancel_protection = .unblocked,1090 .cancel_protection = .unblocked,
1104 };1091 };
1105 closure.* = .{1092 closure.* = .{
1106 .ev = ev,1093 .evented = ev,
1107 .fiber = fiber,1094 .fiber = fiber,
1108 .start = start,1095 .start = start,
1109 .result_align = result_alignment,1096 .result_align = result_alignment,
...@@ -1121,18 +1108,11 @@ fn await(...@@ -1121,18 +1108,11 @@ fn await(
1121 result_alignment: Alignment,1108 result_alignment: Alignment,
1122) void {1109) void {
1123 const ev: *Evented = @ptrCast(@alignCast(userdata));1110 const ev: *Evented = @ptrCast(@alignCast(userdata));
1124 const fiber = Thread.current().currentFiber();1111 const awaiting: *Fiber = @ptrCast(@alignCast(future));
1125 const future_fiber: *Fiber = @ptrCast(@alignCast(future));1112 if (@atomicLoad(?*Fiber, &awaiting.link.awaiter, .acquire) != Fiber.finished)
1126 if (@atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, fiber, .acq_rel)) |awaiter| {1113 ev.yield(.{ .await = awaiting });
1127 assert(awaiter == Fiber.finished);1114 @memcpy(result, awaiting.resultBytes(result_alignment));
1128 } else while (true) {1115 awaiting.destroy(ev);
1129 ev.yield(.{ .await = 1 });
1130 const awaiter = @atomicLoad(?*Fiber, &future_fiber.link.awaiter, .acquire);
1131 if (awaiter == Fiber.finished) break;
1132 assert(awaiter == fiber); // spurious wakeup
1133 }
1134 @memcpy(result, future_fiber.resultBytes(result_alignment));
1135 future_fiber.destroy(ev);
1136}1116}
11371117
1138fn cancel(1118fn cancel(
...@@ -1267,8 +1247,8 @@ const Group = struct {...@@ -1267,8 +1247,8 @@ const Group = struct {
1267 .awaiter_delayed = false,1247 .awaiter_delayed = false,
1268 .fibers = .null,1248 .fibers = .null,
1269 }, .release);1249 }, .release);
1270 assert(awaiter.status.awaiting_group.ptr == group.ptr);1250 assert(awaiter.awaiting_group.ptr == group.ptr);
1271 awaiter.status = .{ .queue_next = null };1251 awaiter.awaiting_group = undefined;
1272 return awaiter;1252 return awaiter;
1273 }1253 }
1274 // Race with `Fiber.requestCancel`1254 // Race with `Fiber.requestCancel`
...@@ -1336,8 +1316,7 @@ const Group = struct {...@@ -1336,8 +1316,7 @@ const Group = struct {
13361316
1337 /// Assumes the mutex is held.1317 /// Assumes the mutex is held.
1338 fn registerAwaiter(group: Group, awaiter: *Fiber) bool {1318 fn registerAwaiter(group: Group, awaiter: *Fiber) bool {
1339 assert(awaiter.status.queue_next == null);1319 awaiter.awaiting_group = group;
1340 awaiter.status = .{ .awaiting_group = group };
1341 assert(@atomicRmw(1320 assert(@atomicRmw(
1342 Awaiter,1321 Awaiter,
1343 group.awaiterPtr(),1322 group.awaiterPtr(),
...@@ -1349,7 +1328,7 @@ const Group = struct {...@@ -1349,7 +1328,7 @@ const Group = struct {
1349 }1328 }
13501329
1351 const AsyncClosure = struct {1330 const AsyncClosure = struct {
1352 ev: *Evented,1331 evented: *Evented,
1353 group: Group,1332 group: Group,
1354 fiber: *Fiber,1333 fiber: *Fiber,
1355 start: *const fn (context: *const anyopaque) Io.Cancelable!void,1334 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
...@@ -1388,19 +1367,15 @@ const Group = struct {...@@ -1388,19 +1367,15 @@ const Group = struct {
1388 closure: *Group.AsyncClosure,1367 closure: *Group.AsyncClosure,
1389 message: *const SwitchMessage,1368 message: *const SwitchMessage,
1390 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {1369 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
1391 message.handle(closure.ev);1370 const ev = closure.evented;
1392 assert(closure.fiber.status.queue_next == null);
1393 const result = closure.start(closure.contextPointer());
1394 const ev = closure.ev;
1395 const group = closure.group;
1396 const fiber = closure.fiber;1371 const fiber = closure.fiber;
1397 const cancel_acknowledged = fiber.cancel_protection.acknowledged;1372 message.handle(ev);
1398 if (result) {1373 if (closure.start(closure.contextPointer())) {
1399 assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`1374 assert(!fiber.cancel_protection.acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
1400 } else |err| switch (err) {1375 } else |err| switch (err) {
1401 error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled1376 error.Canceled => assert(fiber.cancel_protection.acknowledged), // group task returned `error.Canceled` but was never canceled
1402 }1377 }
1403 if (group.removeFiber(ev, fiber)) |awaiter| ev.queue.async(awaiter, &Fiber.@"resume");1378 if (closure.group.removeFiber(ev, fiber)) |awaiter| ev.queue.async(awaiter, &Fiber.@"resume");
1404 ev.yield(.destroy);1379 ev.yield(.destroy);
1405 unreachable; // switched to dead fiber1380 unreachable; // switched to dead fiber
1406 }1381 }
...@@ -1470,14 +1445,13 @@ fn groupConcurrent(...@@ -1470,14 +1445,13 @@ fn groupConcurrent(
1470 },1445 },
1471 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1446 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1472 },1447 },
1473 .await_count = 0,
1474 .link = .{ .group = .{ .prev = null, .next = null } },1448 .link = .{ .group = .{ .prev = null, .next = null } },
1475 .status = .{ .queue_next = null },1449 .awaiting_group = undefined,
1476 .cancel_status = .unrequested,1450 .cancel_status = .unrequested,
1477 .cancel_protection = .unblocked,1451 .cancel_protection = .unblocked,
1478 };1452 };
1479 closure.* = .{1453 closure.* = .{
1480 .ev = ev,1454 .evented = ev,
1481 .group = group,1455 .group = group,
1482 .fiber = fiber,1456 .fiber = fiber,
1483 .start = start,1457 .start = start,
...@@ -1689,50 +1663,6 @@ fn futexForAddress(ev: *Evented, address: usize) *Futex {...@@ -1689,50 +1663,6 @@ fn futexForAddress(ev: *Evented, address: usize) *Futex {
1689 return &ev.futexes[hashed >> @clz(ev.futexes.len - 1)];1663 return &ev.futexes[hashed >> @clz(ev.futexes.len - 1)];
1690}1664}
16911665
1692fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
1693 const ev: *Evented = @ptrCast(@alignCast(userdata));
1694 const fiber = Thread.current().currentFiber();
1695 var await_count: u31, var result = for (futures, 0..) |future, future_index| {
1696 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1697 if (@atomicRmw(
1698 ?*Fiber,
1699 &future_fiber.link.awaiter,
1700 .Xchg,
1701 fiber,
1702 .acq_rel,
1703 )) |awaiter| {
1704 assert(awaiter == Fiber.finished);
1705 break .{ @intCast(future_index), future_index };
1706 }
1707 } else result: {
1708 const await_count: u31 = @intCast(futures.len);
1709 ev.yield(.{ .await = 1 });
1710 break :result .{ await_count - 1, futures.len };
1711 };
1712 for (futures[0..result], 0..) |future, future_index| {
1713 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1714 const awaiter = @atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, null, .monotonic);
1715 if (awaiter == Fiber.finished) {
1716 @atomicStore(?*Fiber, &future_fiber.link.awaiter, Fiber.finished, .monotonic);
1717 result = @min(future_index, result);
1718 } else {
1719 assert(awaiter == fiber);
1720 await_count -= 1;
1721 }
1722 }
1723 // Equivalent to `ev.yield(null, .{ .await = await_count });`,
1724 // but avoiding a context switch in the common case.
1725 switch (std.math.order(
1726 @atomicRmw(i32, &fiber.await_count, .Sub, await_count, .monotonic),
1727 await_count,
1728 )) {
1729 .lt => ev.yield(.{ .await = 0 }),
1730 .eq => {},
1731 .gt => unreachable,
1732 }
1733 return result;
1734}
1735
1736fn futexWait(1666fn futexWait(
1737 userdata: ?*anyopaque,1667 userdata: ?*anyopaque,
1738 ptr: *const u32,1668 ptr: *const u32,
lib/std/Io/Kqueue.zig-22
...@@ -491,7 +491,6 @@ const SwitchMessage = struct {...@@ -491,7 +491,6 @@ const SwitchMessage = struct {
491 reschedule,491 reschedule,
492 recycle: *Fiber,492 recycle: *Fiber,
493 register_awaiter: *?*Fiber,493 register_awaiter: *?*Fiber,
494 register_select: []const *Io.AnyFuture,
495 exit,494 exit,
496 };495 };
497496
...@@ -514,19 +513,6 @@ const SwitchMessage = struct {...@@ -514,19 +513,6 @@ const SwitchMessage = struct {
514 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)513 if (@atomicRmw(?*Fiber, awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished)
515 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });514 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
516 },515 },
517 .register_select => |futures| {
518 const prev_fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
519 assert(prev_fiber.queue_next == null);
520 for (futures) |any_future| {
521 const future_fiber: *Fiber = @ptrCast(@alignCast(any_future));
522 if (@atomicRmw(?*Fiber, &future_fiber.awaiter, .Xchg, prev_fiber, .acq_rel) == Fiber.finished) {
523 const closure: *AsyncClosure = .fromFiber(future_fiber);
524 if (!@atomicRmw(bool, &closure.already_awaited, .Xchg, true, .seq_cst)) {
525 k.schedule(thread, .{ .head = prev_fiber, .tail = prev_fiber });
526 }
527 }
528 }
529 },
530 .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {516 .exit => for (k.threads.allocated[0..@atomicLoad(u32, &k.threads.active, .acquire)]) |*each_thread| {
531 const changes = [_]posix.Kevent{517 const changes = [_]posix.Kevent{
532 .{518 .{
...@@ -628,7 +614,6 @@ pub fn io(k: *Kqueue) Io {...@@ -628,7 +614,6 @@ pub fn io(k: *Kqueue) Io {
628 .concurrent = concurrent,614 .concurrent = concurrent,
629 .await = await,615 .await = await,
630 .cancel = cancel,616 .cancel = cancel,
631 .select = select,
632617
633 .groupAsync = groupAsync,618 .groupAsync = groupAsync,
634 .groupConcurrent = groupConcurrent,619 .groupConcurrent = groupConcurrent,
...@@ -824,13 +809,6 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void...@@ -824,13 +809,6 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void
824 @panic("TODO");809 @panic("TODO");
825}810}
826811
827fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
828 const k: *Kqueue = @ptrCast(@alignCast(userdata));
829 _ = k;
830 _ = futures;
831 @panic("TODO");
832}
833
834fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {812fn dirCreateDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void {
835 const k: *Kqueue = @ptrCast(@alignCast(userdata));813 const k: *Kqueue = @ptrCast(@alignCast(userdata));
836 _ = k;814 _ = k;
lib/std/Io/Threaded.zig-70
...@@ -1772,7 +1772,6 @@ pub fn io(t: *Threaded) Io {...@@ -1772,7 +1772,6 @@ pub fn io(t: *Threaded) Io {
1772 .concurrent = concurrent,1772 .concurrent = concurrent,
1773 .await = await,1773 .await = await,
1774 .cancel = cancel,1774 .cancel = cancel,
1775 .select = select,
17761775
1777 .groupAsync = groupAsync,1776 .groupAsync = groupAsync,
1778 .groupConcurrent = groupConcurrent,1777 .groupConcurrent = groupConcurrent,
...@@ -1938,7 +1937,6 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1938,7 +1937,6 @@ pub fn ioBasic(t: *Threaded) Io {
1938 .concurrent = concurrent,1937 .concurrent = concurrent,
1939 .await = await,1938 .await = await,
1940 .cancel = cancel,1939 .cancel = cancel,
1941 .select = select,
19421940
1943 .groupAsync = groupAsync,1941 .groupAsync = groupAsync,
1944 .groupConcurrent = groupConcurrent,1942 .groupConcurrent = groupConcurrent,
...@@ -11727,74 +11725,6 @@ fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {...@@ -11727,74 +11725,6 @@ fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void {
11727 }11725 }
11728}11726}
1172911727
11730fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
11731 const t: *Threaded = @ptrCast(@alignCast(userdata));
11732 _ = t;
11733
11734 var num_completed: std.atomic.Value(u32) = .init(0);
11735
11736 for (futures, 0..) |any_future, i| {
11737 const future: *Future = @ptrCast(@alignCast(any_future));
11738 future.awaiter = &num_completed;
11739 const old_status = future.status.fetchOr(
11740 .{ .tag = .pending_awaited, .thread = .null },
11741 .release, // release `future.awaiter`
11742 );
11743 switch (old_status.tag) {
11744 .pending => {},
11745 .pending_awaited => unreachable, // `await` raced with `select`
11746 .pending_canceled => unreachable, // `cancel` raced with `select`
11747 .done => {
11748 future.status.store(old_status, .monotonic);
11749 _ = finishSelect(&num_completed, futures[0..i]);
11750 return i;
11751 },
11752 }
11753 }
11754
11755 errdefer _ = finishSelect(&num_completed, futures);
11756
11757 while (true) {
11758 const n = num_completed.load(.acquire);
11759 if (n > 0) break;
11760 assert(n < futures.len);
11761 try Thread.futexWait(&num_completed.raw, n, null);
11762 }
11763 return finishSelect(&num_completed, futures).?;
11764}
11765fn finishSelect(
11766 num_completed: *std.atomic.Value(u32),
11767 futures: []const *Io.AnyFuture,
11768) ?usize {
11769 var completed_index: ?usize = null;
11770 var expect_completed: u32 = 0;
11771 for (futures, 0..) |any_future, i| {
11772 const future: *Future = @ptrCast(@alignCast(any_future));
11773 // This operation will convert `.pending_awaited` to `.pending`, or leave `.done` untouched.
11774 switch (future.status.fetchAnd(
11775 .{ .tag = @enumFromInt(0b10), .thread = .all_ones },
11776 .monotonic,
11777 ).tag) {
11778 .pending_awaited => {},
11779 .pending => unreachable,
11780 .pending_canceled => unreachable,
11781 .done => {
11782 expect_completed += 1;
11783 completed_index = i;
11784 },
11785 }
11786 }
11787 // If any future has just finished, wait for it to signal `num_completed` to avoid dangling
11788 // references to stack memory.
11789 while (true) {
11790 const n = num_completed.load(.acquire);
11791 if (n == expect_completed) break;
11792 assert(n < expect_completed);
11793 Thread.futexWaitUncancelable(&num_completed.raw, n, null);
11794 }
11795 return completed_index;
11796}
11797
11798fn netListenIpPosix(11728fn netListenIpPosix(
11799 userdata: ?*anyopaque,11729 userdata: ?*anyopaque,
11800 address: IpAddress,11730 address: IpAddress,
lib/std/Io/Uring.zig+172-216
...@@ -150,7 +150,6 @@ const Thread = struct {...@@ -150,7 +150,6 @@ const Thread = struct {
150const Fiber = struct {150const Fiber = struct {
151 required_align: void align(4),151 required_align: void align(4),
152 context: Io.fiber.Context,152 context: Io.fiber.Context,
153 await_count: i32,
154 link: union {153 link: union {
155 awaiter: ?*Fiber,154 awaiter: ?*Fiber,
156 group: struct { prev: ?*Fiber, next: ?*Fiber },155 group: struct { prev: ?*Fiber, next: ?*Fiber },
...@@ -175,7 +174,6 @@ const Fiber = struct {...@@ -175,7 +174,6 @@ const Fiber = struct {
175 const Awaiting = enum(u31) {174 const Awaiting = enum(u31) {
176 nothing = std.math.maxInt(u31),175 nothing = std.math.maxInt(u31),
177 group = std.math.maxInt(u31) - 1,176 group = std.math.maxInt(u31) - 1,
178 select = std.math.maxInt(u31) - 2,
179 /// An io_uring fd.177 /// An io_uring fd.
180 _,178 _,
181179
...@@ -186,14 +184,14 @@ const Fiber = struct {...@@ -186,14 +184,14 @@ const Fiber = struct {
186 fn fromIoUringFd(fd: fd_t) Awaiting {184 fn fromIoUringFd(fd: fd_t) Awaiting {
187 const awaiting: Awaiting = @enumFromInt(fd);185 const awaiting: Awaiting = @enumFromInt(fd);
188 switch (awaiting) {186 switch (awaiting) {
189 .nothing, .group, .select => unreachable,187 .nothing, .group => unreachable,
190 _ => return awaiting,188 _ => return awaiting,
191 }189 }
192 }190 }
193191
194 fn toIoUringFd(awaiting: Awaiting) fd_t {192 fn toIoUringFd(awaiting: Awaiting) fd_t {
195 switch (awaiting) {193 switch (awaiting) {
196 .nothing, .group, .select => unreachable,194 .nothing, .group => unreachable,
197 _ => return @intFromEnum(awaiting),195 _ => return @intFromEnum(awaiting),
198 }196 }
199 }197 }
...@@ -376,9 +374,6 @@ const Fiber = struct {...@@ -376,9 +374,6 @@ const Fiber = struct {
376 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });374 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
377 }375 }
378 },376 },
379 .select => if (@atomicRmw(i32, &fiber.await_count, .Add, 1, .monotonic) == -1) {
380 _ = ev.schedule(.current(), .{ .head = fiber, .tail = fiber });
381 },
382 _ => |awaiting| {377 _ => |awaiting| {
383 const awaiting_io_uring_fd = awaiting.toIoUringFd();378 const awaiting_io_uring_fd = awaiting.toIoUringFd();
384 const thread: *Thread = .current();379 const thread: *Thread = .current();
...@@ -684,8 +679,6 @@ pub fn io(ev: *Evented) Io {...@@ -684,8 +679,6 @@ pub fn io(ev: *Evented) Io {
684 .swapCancelProtection = swapCancelProtection,679 .swapCancelProtection = swapCancelProtection,
685 .checkCancel = checkCancel,680 .checkCancel = checkCancel,
686681
687 .select = select,
688
689 .futexWait = futexWait,682 .futexWait = futexWait,
690 .futexWaitUncancelable = futexWaitUncancelable,683 .futexWaitUncancelable = futexWaitUncancelable,
691 .futexWake = futexWake,684 .futexWake = futexWake,
...@@ -862,7 +855,6 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v...@@ -862,7 +855,6 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
862 main_fiber.* = .{855 main_fiber.* = .{
863 .required_align = {},856 .required_align = {},
864 .context = undefined,857 .context = undefined,
865 .await_count = 0,
866 .link = .{ .awaiter = null },858 .link = .{ .awaiter = null },
867 .status = .{ .queue_next = null },859 .status = .{ .queue_next = null },
868 .cancel_status = .unrequested,860 .cancel_status = .unrequested,
...@@ -1266,7 +1258,7 @@ const SwitchMessage = struct {...@@ -1266,7 +1258,7 @@ const SwitchMessage = struct {
1266 const PendingTask = union(enum) {1258 const PendingTask = union(enum) {
1267 nothing,1259 nothing,
1268 reschedule,1260 reschedule,
1269 await: u31,1261 await: *Fiber,
1270 group_await: Group,1262 group_await: Group,
1271 group_cancel: Group,1263 group_cancel: Group,
1272 batch_await: *Io.Batch,1264 batch_await: *Io.Batch,
...@@ -1290,10 +1282,11 @@ const SwitchMessage = struct {...@@ -1290,10 +1282,11 @@ const SwitchMessage = struct {
1290 assert(fiber.status.queue_next == null);1282 assert(fiber.status.queue_next == null);
1291 _ = ev.schedule(thread, .{ .head = fiber, .tail = fiber });1283 _ = ev.schedule(thread, .{ .head = fiber, .tail = fiber });
1292 },1284 },
1293 .await => |count| {1285 .await => |awaiting| {
1294 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));1286 const awaiter: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
1295 if (@atomicRmw(i32, &fiber.await_count, .Sub, count, .monotonic) > 0)1287 assert(awaiter.status.queue_next == null);
1296 _ = ev.schedule(thread, .{ .head = fiber, .tail = fiber });1288 if (@atomicRmw(?*Fiber, &awaiting.link.awaiter, .Xchg, awaiter, .acq_rel) ==
1289 Fiber.finished) _ = ev.schedule(thread, .{ .head = awaiter, .tail = awaiter });
1297 },1290 },
1298 .group_await => |group| {1291 .group_await => |group| {
1299 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));1292 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
...@@ -1367,7 +1360,7 @@ fn crashHandler(userdata: ?*anyopaque) void {...@@ -1367,7 +1360,7 @@ fn crashHandler(userdata: ?*anyopaque) void {
1367}1360}
13681361
1369const AsyncClosure = struct {1362const AsyncClosure = struct {
1370 ev: *Evented,1363 evented: *Evented,
1371 fiber: *Fiber,1364 fiber: *Fiber,
1372 start: *const fn (context: *const anyopaque, result: *anyopaque) void,1365 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
1373 result_align: Alignment,1366 result_align: Alignment,
...@@ -1404,16 +1397,11 @@ const AsyncClosure = struct {...@@ -1404,16 +1397,11 @@ const AsyncClosure = struct {
1404 closure: *AsyncClosure,1397 closure: *AsyncClosure,
1405 message: *const SwitchMessage,1398 message: *const SwitchMessage,
1406 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {1399 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
1407 message.handle(closure.ev);1400 const ev = closure.evented;
1408 const fiber = closure.fiber;1401 const fiber = closure.fiber;
1402 message.handle(ev);
1409 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));1403 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
1410 closure.ev.yield(1404 ev.yield(@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel), .nothing);
1411 if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|
1412 if (@atomicRmw(i32, &awaiter.await_count, .Add, 1, .monotonic) == -1) awaiter else null
1413 else
1414 null,
1415 .nothing,
1416 );
1417 unreachable; // switched to dead fiber1405 unreachable; // switched to dead fiber
1418 }1406 }
1419};1407};
...@@ -1467,7 +1455,6 @@ fn concurrent(...@@ -1467,7 +1455,6 @@ fn concurrent(
1467 },1455 },
1468 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1456 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1469 },1457 },
1470 .await_count = 0,
1471 .link = .{ .awaiter = null },1458 .link = .{ .awaiter = null },
1472 .status = .{ .queue_next = null },1459 .status = .{ .queue_next = null },
1473 .cancel_status = .unrequested,1460 .cancel_status = .unrequested,
...@@ -1485,7 +1472,7 @@ fn concurrent(...@@ -1485,7 +1472,7 @@ fn concurrent(
1485 },1472 },
1486 };1473 };
1487 closure.* = .{1474 closure.* = .{
1488 .ev = ev,1475 .evented = ev,
1489 .fiber = fiber,1476 .fiber = fiber,
1490 .start = start,1477 .start = start,
1491 .result_align = result_alignment,1478 .result_align = result_alignment,
...@@ -1504,18 +1491,11 @@ fn await(...@@ -1504,18 +1491,11 @@ fn await(
1504 result_alignment: Alignment,1491 result_alignment: Alignment,
1505) void {1492) void {
1506 const ev: *Evented = @ptrCast(@alignCast(userdata));1493 const ev: *Evented = @ptrCast(@alignCast(userdata));
1507 const fiber = Thread.current().currentFiber();1494 const awaiting: *Fiber = @ptrCast(@alignCast(future));
1508 const future_fiber: *Fiber = @ptrCast(@alignCast(future));1495 if (@atomicLoad(?*Fiber, &awaiting.link.awaiter, .acquire) != Fiber.finished)
1509 if (@atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, fiber, .acq_rel)) |awaiter| {1496 ev.yield(null, .{ .await = awaiting });
1510 assert(awaiter == Fiber.finished);1497 @memcpy(result, awaiting.resultBytes(result_alignment));
1511 } else while (true) {1498 awaiting.destroy();
1512 ev.yield(null, .{ .await = 1 });
1513 const awaiter = @atomicLoad(?*Fiber, &future_fiber.link.awaiter, .acquire);
1514 if (awaiter == Fiber.finished) break;
1515 assert(awaiter == fiber); // spurious wakeup
1516 }
1517 @memcpy(result, future_fiber.resultBytes(result_alignment));
1518 future_fiber.destroy();
1519}1499}
15201500
1521fn cancel(1501fn cancel(
...@@ -1732,7 +1712,7 @@ const Group = struct {...@@ -1732,7 +1712,7 @@ const Group = struct {
1732 }1712 }
17331713
1734 const AsyncClosure = struct {1714 const AsyncClosure = struct {
1735 ev: *Evented,1715 evented: *Evented,
1736 group: Group,1716 group: Group,
1737 fiber: *Fiber,1717 fiber: *Fiber,
1738 start: *const fn (context: *const anyopaque) Io.Cancelable!void,1718 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
...@@ -1771,19 +1751,16 @@ const Group = struct {...@@ -1771,19 +1751,16 @@ const Group = struct {
1771 closure: *Group.AsyncClosure,1751 closure: *Group.AsyncClosure,
1772 message: *const SwitchMessage,1752 message: *const SwitchMessage,
1773 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {1753 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
1774 message.handle(closure.ev);1754 const ev = closure.evented;
1775 assert(closure.fiber.status.queue_next == null);
1776 const result = closure.start(closure.contextPointer());
1777 const ev = closure.ev;
1778 const group = closure.group;
1779 const fiber = closure.fiber;1755 const fiber = closure.fiber;
1780 const cancel_acknowledged = fiber.cancel_protection.acknowledged;1756 message.handle(ev);
1781 if (result) {1757 assert(fiber.status.queue_next == null);
1782 assert(!cancel_acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`1758 if (closure.start(closure.contextPointer())) {
1759 assert(!fiber.cancel_protection.acknowledged); // group task acknowledged cancelation but did not return `error.Canceled`
1783 } else |err| switch (err) {1760 } else |err| switch (err) {
1784 error.Canceled => assert(cancel_acknowledged), // group task returned `error.Canceled` but was never canceled1761 error.Canceled => assert(fiber.cancel_protection.acknowledged), // group task returned `error.Canceled` but was never canceled
1785 }1762 }
1786 ev.yield(group.removeFiber(ev, fiber), .destroy);1763 ev.yield(closure.group.removeFiber(ev, fiber), .destroy);
1787 unreachable; // switched to dead fiber1764 unreachable; // switched to dead fiber
1788 }1765 }
1789 };1766 };
...@@ -1851,7 +1828,6 @@ fn groupConcurrent(...@@ -1851,7 +1828,6 @@ fn groupConcurrent(
1851 },1828 },
1852 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1829 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1853 },1830 },
1854 .await_count = 0,
1855 .link = .{ .group = .{ .prev = null, .next = null } },1831 .link = .{ .group = .{ .prev = null, .next = null } },
1856 .status = .{ .queue_next = null },1832 .status = .{ .queue_next = null },
1857 .cancel_status = .unrequested,1833 .cancel_status = .unrequested,
...@@ -1869,7 +1845,7 @@ fn groupConcurrent(...@@ -1869,7 +1845,7 @@ fn groupConcurrent(
1869 },1845 },
1870 };1846 };
1871 closure.* = .{1847 closure.* = .{
1872 .ev = ev,1848 .evented = ev,
1873 .group = group,1849 .group = group,
1874 .fiber = fiber,1850 .fiber = fiber,
1875 .start = start,1851 .start = start,
...@@ -1928,57 +1904,6 @@ fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {...@@ -1928,57 +1904,6 @@ fn checkCancel(userdata: ?*anyopaque) Io.Cancelable!void {
1928 }1904 }
1929}1905}
19301906
1931fn select(userdata: ?*anyopaque, futures: []const *Io.AnyFuture) Io.Cancelable!usize {
1932 const ev: *Evented = @ptrCast(@alignCast(userdata));
1933 var cancel_region: CancelRegion = .init();
1934 defer cancel_region.deinit();
1935 var await_count: u31, var result = for (futures, 0..) |future, future_index| {
1936 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1937 if (@atomicRmw(
1938 ?*Fiber,
1939 &future_fiber.link.awaiter,
1940 .Xchg,
1941 cancel_region.fiber,
1942 .acq_rel,
1943 )) |awaiter| {
1944 assert(awaiter == Fiber.finished);
1945 break .{ @intCast(future_index), future_index };
1946 }
1947 } else result: {
1948 const await_count: u31 = @intCast(futures.len);
1949 cancel_region.await(.select) catch |err| switch (err) {
1950 error.Canceled => |e| break :result .{ await_count + 1, e },
1951 };
1952 ev.yield(null, .{ .await = 1 });
1953 cancel_region.await(.nothing) catch |err| switch (err) {
1954 error.Canceled => |e| break :result .{ await_count, e },
1955 };
1956 break :result .{ await_count - 1, futures.len };
1957 };
1958 for (futures[0 .. result catch futures.len], 0..) |future, future_index| {
1959 const future_fiber: *Fiber = @ptrCast(@alignCast(future));
1960 const awaiter = @atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, null, .monotonic);
1961 if (awaiter == Fiber.finished) {
1962 @atomicStore(?*Fiber, &future_fiber.link.awaiter, Fiber.finished, .monotonic);
1963 result = if (result) |finished_index| @min(future_index, finished_index) else |e| e;
1964 } else {
1965 assert(awaiter == cancel_region.fiber);
1966 await_count -= 1;
1967 }
1968 }
1969 // Equivalent to `ev.yield(null, .{ .await = await_count });`,
1970 // but avoiding a context switch in the common case.
1971 switch (std.math.order(
1972 @atomicRmw(i32, &cancel_region.fiber.await_count, .Sub, await_count, .monotonic),
1973 await_count,
1974 )) {
1975 .lt => ev.yield(null, .{ .await = 0 }),
1976 .eq => {},
1977 .gt => unreachable,
1978 }
1979 return result;
1980}
1981
1982fn futexWait(1907fn futexWait(
1983 userdata: ?*anyopaque,1908 userdata: ?*anyopaque,
1984 ptr: *const u32,1909 ptr: *const u32,
...@@ -2230,7 +2155,7 @@ fn deviceIoControl(...@@ -2230,7 +2155,7 @@ fn deviceIoControl(
2230 const rc = linux.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));2155 const rc = linux.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg));
2231 switch (linux.errno(rc)) {2156 switch (linux.errno(rc)) {
2232 .SUCCESS => return @bitCast(@as(u32, @truncate(rc))),2157 .SUCCESS => return @bitCast(@as(u32, @truncate(rc))),
2233 .INTR => continue,2158 .INTR => {},
2234 else => |err| return -@as(i32, @intFromEnum(err)),2159 else => |err| return -@as(i32, @intFromEnum(err)),
2235 }2160 }
2236 }2161 }
...@@ -2628,7 +2553,7 @@ fn dirCreateDir(...@@ -2628,7 +2553,7 @@ fn dirCreateDir(
2628 ev.yield(null, .nothing);2553 ev.yield(null, .nothing);
2629 switch (cancel_region.errno()) {2554 switch (cancel_region.errno()) {
2630 .SUCCESS => return,2555 .SUCCESS => return,
2631 .INTR, .CANCELED => continue,2556 .INTR, .CANCELED => {},
2632 .ACCES => return error.AccessDenied,2557 .ACCES => return error.AccessDenied,
2633 .BADF => |err| return errnoBug(err), // File descriptor used after closed.2558 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2634 .PERM => return error.PermissionDenied,2559 .PERM => return error.PermissionDenied,
...@@ -2711,7 +2636,7 @@ fn filePathKind(ev: *Evented, dir: Dir, sub_path: []const u8) !File.Kind {...@@ -2711,7 +2636,7 @@ fn filePathKind(ev: *Evented, dir: Dir, sub_path: []const u8) !File.Kind {
2711 if (!statx_buf.mask.TYPE) return error.Unexpected;2636 if (!statx_buf.mask.TYPE) return error.Unexpected;
2712 return statxKind(statx_buf.mode);2637 return statxKind(statx_buf.mode);
2713 },2638 },
2714 .INTR, .CANCELED => continue,2639 .INTR, .CANCELED => {},
2715 .ACCES => |err| return errnoBug(err),2640 .ACCES => |err| return errnoBug(err),
2716 .BADF => |err| return errnoBug(err), // File descriptor used after closed.2641 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2717 .FAULT => |err| return errnoBug(err),2642 .FAULT => |err| return errnoBug(err),
...@@ -2824,7 +2749,7 @@ fn dirAccess(...@@ -2824,7 +2749,7 @@ fn dirAccess(
2824 try sync.cancel_region.await(.nothing);2749 try sync.cancel_region.await(.nothing);
2825 switch (linux.errno(linux.faccessat(dir.handle, sub_path_posix, mode, flags))) {2750 switch (linux.errno(linux.faccessat(dir.handle, sub_path_posix, mode, flags))) {
2826 .SUCCESS => return,2751 .SUCCESS => return,
2827 .INTR => continue,2752 .INTR => {},
2828 .ACCES => return error.AccessDenied,2753 .ACCES => return error.AccessDenied,
2829 .PERM => return error.PermissionDenied,2754 .PERM => return error.PermissionDenied,
2830 .ROFS => return error.ReadOnlyFileSystem,2755 .ROFS => return error.ReadOnlyFileSystem,
...@@ -2863,7 +2788,7 @@ fn dirCreateFile(...@@ -2863,7 +2788,7 @@ fn dirCreateFile(
2863 .EXCL = flags.exclusive,2788 .EXCL = flags.exclusive,
2864 .CLOEXEC = true,2789 .CLOEXEC = true,
2865 }, flags.permissions.toMode());2790 }, flags.permissions.toMode());
2866 errdefer ev.close(fd);2791 errdefer ev.closeAsync(fd);
28672792
2868 switch (flags.lock) {2793 switch (flags.lock) {
2869 .none => {},2794 .none => {},
...@@ -3051,7 +2976,7 @@ fn dirOpenFile(...@@ -3051,7 +2976,7 @@ fn dirOpenFile(
3051 .CLOEXEC = true,2976 .CLOEXEC = true,
3052 .PATH = flags.path_only,2977 .PATH = flags.path_only,
3053 }, 0);2978 }, 0);
3054 errdefer ev.close(fd);2979 errdefer ev.closeAsync(fd);
30552980
3056 if (!flags.allow_directory) {2981 if (!flags.allow_directory) {
3057 const is_dir = is_dir: {2982 const is_dir = is_dir: {
...@@ -3105,7 +3030,7 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read...@@ -3105,7 +3030,7 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read
3105 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);3030 const rc = linux.getdents64(dr.dir.handle, dr.buffer.ptr, dr.buffer.len);
3106 switch (linux.errno(rc)) {3031 switch (linux.errno(rc)) {
3107 .SUCCESS => break rc,3032 .SUCCESS => break rc,
3108 .INTR => continue,3033 .INTR => {},
3109 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.3034 .BADF => |err| return errnoBug(err), // Dir is invalid or was opened without iteration ability.
3110 .FAULT => |err| return errnoBug(err),3035 .FAULT => |err| return errnoBug(err),
3111 .NOTDIR => |err| return errnoBug(err),3036 .NOTDIR => |err| return errnoBug(err),
...@@ -3198,7 +3123,7 @@ fn dirRealPathFile(...@@ -3198,7 +3123,7 @@ fn dirRealPathFile(
3198 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.3123 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.
3199 else => |e| return e,3124 else => |e| return e,
3200 };3125 };
3201 defer ev.close(fd);3126 defer ev.closeAsync(fd);
3202 return ev.realPath(try maybe_sync.enterSync(ev), fd, out_buffer);3127 return ev.realPath(try maybe_sync.enterSync(ev), fd, out_buffer);
3203}3128}
32043129
...@@ -3231,7 +3156,7 @@ fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Dele...@@ -3231,7 +3156,7 @@ fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Dele
3231 ev.yield(null, .nothing);3156 ev.yield(null, .nothing);
3232 switch (cancel_region.errno()) {3157 switch (cancel_region.errno()) {
3233 .SUCCESS => return,3158 .SUCCESS => return,
3234 .INTR, .CANCELED => continue,3159 .INTR, .CANCELED => {},
3235 .PERM => return error.PermissionDenied,3160 .PERM => return error.PermissionDenied,
3236 .ACCES => return error.AccessDenied,3161 .ACCES => return error.AccessDenied,
3237 .BUSY => return error.FileBusy,3162 .BUSY => return error.FileBusy,
...@@ -3283,7 +3208,7 @@ fn dirDeleteDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Delet...@@ -3283,7 +3208,7 @@ fn dirDeleteDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Delet
3283 ev.yield(null, .nothing);3208 ev.yield(null, .nothing);
3284 switch (cancel_region.errno()) {3209 switch (cancel_region.errno()) {
3285 .SUCCESS => return,3210 .SUCCESS => return,
3286 .INTR, .CANCELED => continue,3211 .INTR, .CANCELED => {},
3287 .ACCES => return error.AccessDenied,3212 .ACCES => return error.AccessDenied,
3288 .PERM => return error.PermissionDenied,3213 .PERM => return error.PermissionDenied,
3289 .BUSY => return error.FileBusy,3214 .BUSY => return error.FileBusy,
...@@ -3399,7 +3324,7 @@ fn dirSymLink(...@@ -3399,7 +3324,7 @@ fn dirSymLink(
3399 ev.yield(null, .nothing);3324 ev.yield(null, .nothing);
3400 switch (cancel_region.errno()) {3325 switch (cancel_region.errno()) {
3401 .SUCCESS => return,3326 .SUCCESS => return,
3402 .INTR, .CANCELED => continue,3327 .INTR, .CANCELED => {},
3403 .FAULT => |err| return errnoBug(err),3328 .FAULT => |err| return errnoBug(err),
3404 .INVAL => |err| return errnoBug(err),3329 .INVAL => |err| return errnoBug(err),
3405 .ACCES => return error.AccessDenied,3330 .ACCES => return error.AccessDenied,
...@@ -3438,7 +3363,7 @@ fn dirReadLink(...@@ -3438,7 +3363,7 @@ fn dirReadLink(
3438 const rc = linux.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);3363 const rc = linux.readlinkat(dir.handle, sub_path_posix, buffer.ptr, buffer.len);
3439 switch (linux.errno(rc)) {3364 switch (linux.errno(rc)) {
3440 .SUCCESS => return @bitCast(rc),3365 .SUCCESS => return @bitCast(rc),
3441 .INTR => continue,3366 .INTR => {},
3442 .ACCES => return error.AccessDenied,3367 .ACCES => return error.AccessDenied,
3443 .FAULT => |err| return errnoBug(err),3368 .FAULT => |err| return errnoBug(err),
3444 .INVAL => return error.NotLink,3369 .INVAL => return error.NotLink,
...@@ -3628,7 +3553,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {...@@ -3628,7 +3553,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
3628 if (!statx_buf.mask.SIZE) return error.Unexpected;3553 if (!statx_buf.mask.SIZE) return error.Unexpected;
3629 return statx_buf.size;3554 return statx_buf.size;
3630 },3555 },
3631 .INTR, .CANCELED => continue,3556 .INTR, .CANCELED => {},
3632 .ACCES => |err| return errnoBug(err),3557 .ACCES => |err| return errnoBug(err),
3633 .BADF => |err| return errnoBug(err), // File descriptor used after closed.3558 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3634 .FAULT => |err| return errnoBug(err),3559 .FAULT => |err| return errnoBug(err),
...@@ -3811,7 +3736,7 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {...@@ -3811,7 +3736,7 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
3811 ev.yield(null, .nothing);3736 ev.yield(null, .nothing);
3812 switch (cancel_region.errno()) {3737 switch (cancel_region.errno()) {
3813 .SUCCESS => return,3738 .SUCCESS => return,
3814 .INTR, .CANCELED => continue,3739 .INTR, .CANCELED => {},
3815 .BADF => |err| return errnoBug(err),3740 .BADF => |err| return errnoBug(err),
3816 .INVAL => |err| return errnoBug(err),3741 .INVAL => |err| return errnoBug(err),
3817 .ROFS => |err| return errnoBug(err),3742 .ROFS => |err| return errnoBug(err),
...@@ -3833,7 +3758,7 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {...@@ -3833,7 +3758,7 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
3833 const rc = linux.ioctl(file.handle, linux.T.IOCGWINSZ, @intFromPtr(&wsz));3758 const rc = linux.ioctl(file.handle, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
3834 switch (linux.errno(rc)) {3759 switch (linux.errno(rc)) {
3835 .SUCCESS => return true,3760 .SUCCESS => return true,
3836 .INTR => continue,3761 .INTR => {},
3837 else => return false,3762 else => return false,
3838 }3763 }
3839 }3764 }
...@@ -3869,7 +3794,7 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE...@@ -3869,7 +3794,7 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
3869 ev.yield(null, .nothing);3794 ev.yield(null, .nothing);
3870 switch (cancel_region.errno()) {3795 switch (cancel_region.errno()) {
3871 .SUCCESS => return,3796 .SUCCESS => return,
3872 .INTR, .CANCELED => continue,3797 .INTR, .CANCELED => {},
3873 .FBIG => return error.FileTooBig,3798 .FBIG => return error.FileTooBig,
3874 .IO => return error.InputOutput,3799 .IO => return error.InputOutput,
3875 .PERM => return error.PermissionDenied,3800 .PERM => return error.PermissionDenied,
...@@ -4051,7 +3976,7 @@ fn fileMemoryMapCreate(...@@ -4051,7 +3976,7 @@ fn fileMemoryMapCreate(
4051 const rc = linux.mmap(null, options.len, prot, flags, file.handle, casted_offset);3976 const rc = linux.mmap(null, options.len, prot, flags, file.handle, casted_offset);
4052 switch (linux.errno(rc)) {3977 switch (linux.errno(rc)) {
4053 .SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..options.len],3978 .SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..options.len],
4054 .INTR => continue,3979 .INTR => {},
4055 .ACCES => return error.AccessDenied,3980 .ACCES => return error.AccessDenied,
4056 .AGAIN => return error.LockedMemoryLimitExceeded,3981 .AGAIN => return error.LockedMemoryLimitExceeded,
4057 .MFILE => return error.ProcessFdQuotaExceeded,3982 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -4111,7 +4036,7 @@ fn fileMemoryMapSetLength(...@@ -4111,7 +4036,7 @@ fn fileMemoryMapSetLength(
4111 const rc = linux.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);4036 const rc = linux.mremap(old_memory.ptr, old_memory.len, new_len, flags, addr_hint);
4112 switch (linux.errno(rc)) {4037 switch (linux.errno(rc)) {
4113 .SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len],4038 .SUCCESS => break @as([*]align(page_align) u8, @ptrFromInt(rc))[0..new_len],
4114 .INTR => continue,4039 .INTR => {},
4115 .AGAIN => return error.LockedMemoryLimitExceeded,4040 .AGAIN => return error.LockedMemoryLimitExceeded,
4116 .NOMEM => return error.OutOfMemory,4041 .NOMEM => return error.OutOfMemory,
4117 .INVAL => |err| return errnoBug(err),4042 .INVAL => |err| return errnoBug(err),
...@@ -4220,7 +4145,7 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr...@@ -4220,7 +4145,7 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
4220 try sync.cancel_region.await(.nothing);4145 try sync.cancel_region.await(.nothing);
4221 switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {4146 switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {
4222 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,4147 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
4223 .INTR => continue,4148 .INTR => {},
4224 .NOENT => return error.CurrentDirUnlinked,4149 .NOENT => return error.CurrentDirUnlinked,
4225 .RANGE => return error.NameTooLong,4150 .RANGE => return error.NameTooLong,
4226 .FAULT => |err| return errnoBug(err),4151 .FAULT => |err| return errnoBug(err),
...@@ -4235,7 +4160,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr...@@ -4235,7 +4160,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
4235 if (dir.handle == linux.AT.FDCWD) return;4160 if (dir.handle == linux.AT.FDCWD) return;
4236 var sync: CancelRegion.Sync = try .init(ev);4161 var sync: CancelRegion.Sync = try .init(ev);
4237 defer sync.deinit(ev);4162 defer sync.deinit(ev);
4238 return ev.fchdir(&sync, dir.handle);4163 return fchdir(&sync, dir.handle);
4239}4164}
42404165
4241fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {4166fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {
...@@ -4272,7 +4197,7 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -4272,7 +4197,7 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42724197
4273 var sync: CancelRegion.Sync = try .init(ev);4198 var sync: CancelRegion.Sync = try .init(ev);
4274 defer sync.deinit(ev);4199 defer sync.deinit(ev);
4275 return ev.execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);4200 return execv(&sync, options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH);
4276}4201}
42774202
4278fn processReplacePath(4203fn processReplacePath(
...@@ -4292,7 +4217,7 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp...@@ -4292,7 +4217,7 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
4292 const spawned = try ev.spawn(options);4217 const spawned = try ev.spawn(options);
4293 var cancel_region: CancelRegion = .initBlocked();4218 var cancel_region: CancelRegion = .initBlocked();
4294 defer cancel_region.deinit();4219 defer cancel_region.deinit();
4295 defer ev.close(spawned.err_fd);4220 defer ev.closeAsync(spawned.err_fd);
42964221
4297 // Wait for the child to report any errors in or before `execvpe`.4222 // Wait for the child to report any errors in or before `execvpe`.
4298 var child_err: ForkBailError = undefined;4223 var child_err: ForkBailError = undefined;
...@@ -4434,8 +4359,9 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4434,8 +4359,9 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
44344359
4435 if (pid_result == 0) {4360 if (pid_result == 0) {
4436 defer comptime unreachable; // We are the child.4361 defer comptime unreachable; // We are the child.
4362 // Note that the parent uring is no longer accessible, so we must no longer reference `ev`.
4437 var sync: CancelRegion.Sync = .{ .cancel_region = .initBlocked() };4363 var sync: CancelRegion.Sync = .{ .cancel_region = .initBlocked() };
4438 const err = ev.setUpChild(&sync, .{4364 const err = setUpChild(&sync, .{
4439 .stdin_pipe = stdin_pipe[0],4365 .stdin_pipe = stdin_pipe[0],
4440 .stdout_pipe = stdout_pipe[1],4366 .stdout_pipe = stdout_pipe[1],
4441 .stderr_pipe = stderr_pipe[1],4367 .stderr_pipe = stderr_pipe[1],
...@@ -4446,7 +4372,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4446,7 +4372,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
4446 .PATH = PATH,4372 .PATH = PATH,
4447 .spawn = options,4373 .spawn = options,
4448 });4374 });
4449 ev.writeAll(&sync.cancel_region, err_pipe[1], @ptrCast(&err)) catch {};4375 writeAllSync(&sync, err_pipe[1], @ptrCast(&err)) catch {};
4450 const exit = if (builtin.single_threaded) linux.exit else linux.exit_group;4376 const exit = if (builtin.single_threaded) linux.exit else linux.exit_group;
4451 exit(1);4377 exit(1);
4452 }4378 }
...@@ -4454,13 +4380,13 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4454,13 +4380,13 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
4454 const pid: pid_t = @intCast(pid_result); // We are the parent.4380 const pid: pid_t = @intCast(pid_result); // We are the parent.
4455 errdefer comptime unreachable; // The child is forked; we must not error from now on4381 errdefer comptime unreachable; // The child is forked; we must not error from now on
44564382
4457 ev.close(err_pipe[1]); // make sure only the child holds the write end open4383 ev.closeAsync(err_pipe[1]); // make sure only the child holds the write end open
44584384
4459 if (options.stdin == .pipe) ev.close(stdin_pipe[0]);4385 if (options.stdin == .pipe) ev.closeAsync(stdin_pipe[0]);
4460 if (options.stdout == .pipe) ev.close(stdout_pipe[1]);4386 if (options.stdout == .pipe) ev.closeAsync(stdout_pipe[1]);
4461 if (options.stderr == .pipe) ev.close(stderr_pipe[1]);4387 if (options.stderr == .pipe) ev.closeAsync(stderr_pipe[1]);
44624388
4463 if (prog_pipe[1] != -1) ev.close(prog_pipe[1]);4389 if (prog_pipe[1] != -1) ev.closeAsync(prog_pipe[1]);
44644390
4465 options.progress_node.setIpcFile(ev, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });4391 options.progress_node.setIpcFile(ev, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } });
44664392
...@@ -4497,14 +4423,14 @@ pub fn pipe2(flags: linux.O) PipeError![2]fd_t {...@@ -4497,14 +4423,14 @@ pub fn pipe2(flags: linux.O) PipeError![2]fd_t {
4497 }4423 }
4498}4424}
4499fn destroyPipe(ev: *Evented, pipe: [2]fd_t) void {4425fn destroyPipe(ev: *Evented, pipe: [2]fd_t) void {
4500 if (pipe[0] != -1) ev.close(pipe[0]);4426 if (pipe[0] != -1) ev.closeAsync(pipe[0]);
4501 if (pipe[0] != pipe[1]) ev.close(pipe[1]);4427 if (pipe[0] != pipe[1]) ev.closeAsync(pipe[1]);
4502}4428}
45034429
4504/// Errors that can occur between fork() and execv()4430/// Errors that can occur between fork() and execv()
4505const ForkBailError = process.SetCurrentDirError || ChdirError ||4431const ForkBailError = process.SetCurrentDirError || ChdirError ||
4506 process.SpawnError || process.ReplaceError;4432 process.SpawnError || process.ReplaceError;
4507fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {4433fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4508 stdin_pipe: fd_t,4434 stdin_pipe: fd_t,
4509 stdout_pipe: fd_t,4435 stdout_pipe: fd_t,
4510 stderr_pipe: fd_t,4436 stderr_pipe: fd_t,
...@@ -4515,21 +4441,21 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4515,21 +4441,21 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
4515 PATH: []const u8,4441 PATH: []const u8,
4516 spawn: process.SpawnOptions,4442 spawn: process.SpawnOptions,
4517}) ForkBailError {4443}) ForkBailError {
4518 try ev.setUpChildIo(4444 try setUpChildIo(
4519 sync,4445 sync,
4520 options.spawn.stdin,4446 options.spawn.stdin,
4521 options.stdin_pipe,4447 options.stdin_pipe,
4522 linux.STDIN_FILENO,4448 linux.STDIN_FILENO,
4523 options.dev_null_fd,4449 options.dev_null_fd,
4524 );4450 );
4525 try ev.setUpChildIo(4451 try setUpChildIo(
4526 sync,4452 sync,
4527 options.spawn.stdout,4453 options.spawn.stdout,
4528 options.stdout_pipe,4454 options.stdout_pipe,
4529 linux.STDOUT_FILENO,4455 linux.STDOUT_FILENO,
4530 options.dev_null_fd,4456 options.dev_null_fd,
4531 );4457 );
4532 try ev.setUpChildIo(4458 try setUpChildIo(
4533 sync,4459 sync,
4534 options.spawn.stderr,4460 options.spawn.stderr,
4535 options.stderr_pipe,4461 options.stderr_pipe,
...@@ -4539,17 +4465,17 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4539,17 +4465,17 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
45394465
4540 switch (options.spawn.cwd) {4466 switch (options.spawn.cwd) {
4541 .inherit => {},4467 .inherit => {},
4542 .dir => |cwd_dir| try ev.fchdir(sync, cwd_dir.handle),4468 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),
4543 .path => |cwd_path| {4469 .path => |cwd_path| {
4544 var cwd_path_buffer: [PATH_MAX]u8 = undefined;4470 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4545 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);4471 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4546 try ev.chdir(sync, cwd_path_posix);4472 try chdir(sync, cwd_path_posix);
4547 },4473 },
4548 }4474 }
45494475
4550 // Must happen after fchdir above, the cwd file descriptor might be4476 // Must happen after fchdir above, the cwd file descriptor might be
4551 // equal to prog_fileno and be clobbered by this dup2 call.4477 // equal to prog_fileno and be clobbered by this dup2 call.
4552 if (options.prog_pipe != -1) try ev.dup2(sync, options.prog_pipe, prog_fileno);4478 if (options.prog_pipe != -1) try dup2(sync, options.prog_pipe, prog_fileno);
45534479
4554 if (options.spawn.gid) |gid| {4480 if (options.spawn.gid) |gid| {
4555 switch (linux.errno(linux.setregid(gid, gid))) {4481 switch (linux.errno(linux.setregid(gid, gid))) {
...@@ -4589,7 +4515,7 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4589,7 +4515,7 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
4589 }4515 }
4590 }4516 }
45914517
4592 return ev.execv(4518 return execv(
4593 sync,4519 sync,
4594 options.spawn.expand_arg0,4520 options.spawn.expand_arg0,
4595 options.argv_buf.ptr[0].?,4521 options.argv_buf.ptr[0].?,
...@@ -4600,7 +4526,6 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4600,7 +4526,6 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
4600}4526}
46014527
4602fn setUpChildIo(4528fn setUpChildIo(
4603 ev: *Evented,
4604 sync: *CancelRegion.Sync,4529 sync: *CancelRegion.Sync,
4605 stdio: process.SpawnOptions.StdIo,4530 stdio: process.SpawnOptions.StdIo,
4606 pipe_fd: fd_t,4531 pipe_fd: fd_t,
...@@ -4608,13 +4533,13 @@ fn setUpChildIo(...@@ -4608,13 +4533,13 @@ fn setUpChildIo(
4608 dev_null_fd: fd_t,4533 dev_null_fd: fd_t,
4609) !void {4534) !void {
4610 switch (stdio) {4535 switch (stdio) {
4611 .pipe => try ev.dup2(sync, pipe_fd, std_fileno),4536 .pipe => try dup2(sync, pipe_fd, std_fileno),
4612 .close => _ = linux.close(std_fileno),4537 .close => _ = linux.close(std_fileno),
4613 .inherit => {},4538 .inherit => {},
4614 .ignore => try ev.dup2(sync, dev_null_fd, std_fileno),4539 .ignore => try dup2(sync, dev_null_fd, std_fileno),
4615 .file => |file| {4540 .file => |file| {
4616 if (file.flags.nonblocking) @panic("TODO implement setUpChildIo when nonblocking file is used");4541 if (file.flags.nonblocking) @panic("TODO implement setUpChildIo when nonblocking file is used");
4617 try ev.dup2(sync, file.handle, std_fileno);4542 try dup2(sync, file.handle, std_fileno);
4618 },4543 },
4619 }4544 }
4620}4545}
...@@ -4623,13 +4548,12 @@ pub const DupError = error{...@@ -4623,13 +4548,12 @@ pub const DupError = error{
4623 ProcessFdQuotaExceeded,4548 ProcessFdQuotaExceeded,
4624 SystemResources,4549 SystemResources,
4625} || Io.UnexpectedError || Io.Cancelable;4550} || Io.UnexpectedError || Io.Cancelable;
4626pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void {4551pub fn dup2(sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t) DupError!void {
4627 _ = ev;
4628 while (true) {4552 while (true) {
4629 try sync.cancel_region.await(.nothing);4553 try sync.cancel_region.await(.nothing);
4630 switch (linux.errno(linux.dup2(old_fd, new_fd))) {4554 switch (linux.errno(linux.dup2(old_fd, new_fd))) {
4631 .SUCCESS => {},4555 .SUCCESS => {},
4632 .BUSY, .INTR => continue,4556 .BUSY, .INTR => {},
4633 .INVAL => |err| return errnoBug(err), // invalid parameters4557 .INVAL => |err| return errnoBug(err), // invalid parameters
4634 .BADF => |err| return errnoBug(err), // use after free4558 .BADF => |err| return errnoBug(err), // use after free
4635 .MFILE => return error.ProcessFdQuotaExceeded,4559 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -4640,7 +4564,6 @@ pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t)...@@ -4640,7 +4564,6 @@ pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t)
4640}4564}
46414565
4642fn execv(4566fn execv(
4643 ev: *Evented,
4644 sync: *CancelRegion.Sync,4567 sync: *CancelRegion.Sync,
4645 arg0_expand: process.ArgExpansion,4568 arg0_expand: process.ArgExpansion,
4646 file: [*:0]const u8,4569 file: [*:0]const u8,
...@@ -4649,7 +4572,8 @@ fn execv(...@@ -4649,7 +4572,8 @@ fn execv(
4649 PATH: []const u8,4572 PATH: []const u8,
4650) process.ReplaceError {4573) process.ReplaceError {
4651 const file_slice = std.mem.sliceTo(file, 0);4574 const file_slice = std.mem.sliceTo(file, 0);
4652 if (std.mem.findScalar(u8, file_slice, '/') != null) return ev.execvPath(sync, file, child_argv, env_block);4575 if (std.mem.findScalar(u8, file_slice, '/') != null)
4576 return execvPath(sync, file, child_argv, env_block);
46534577
4654 // Use of PATH_MAX here is valid as the path_buf will be passed4578 // Use of PATH_MAX here is valid as the path_buf will be passed
4655 // directly to the operating system in posixExecvPath.4579 // directly to the operating system in posixExecvPath.
...@@ -4677,7 +4601,7 @@ fn execv(...@@ -4677,7 +4601,7 @@ fn execv(
4677 .expand => child_argv[0] = full_path,4601 .expand => child_argv[0] = full_path,
4678 .no_expand => {},4602 .no_expand => {},
4679 }4603 }
4680 err = ev.execvPath(sync, full_path, child_argv, env_block);4604 err = execvPath(sync, full_path, child_argv, env_block);
4681 switch (err) {4605 switch (err) {
4682 error.AccessDenied => seen_eacces = true,4606 error.AccessDenied => seen_eacces = true,
4683 error.FileNotFound, error.NotDir => {},4607 error.FileNotFound, error.NotDir => {},
...@@ -4689,13 +4613,11 @@ fn execv(...@@ -4689,13 +4613,11 @@ fn execv(
4689}4613}
4690/// This function ignores PATH environment variable.4614/// This function ignores PATH environment variable.
4691pub fn execvPath(4615pub fn execvPath(
4692 ev: *Evented,
4693 sync: *CancelRegion.Sync,4616 sync: *CancelRegion.Sync,
4694 path: [*:0]const u8,4617 path: [*:0]const u8,
4695 child_argv: [*:null]const ?[*:0]const u8,4618 child_argv: [*:null]const ?[*:0]const u8,
4696 env_block: process.Environ.PosixBlock,4619 env_block: process.Environ.PosixBlock,
4697) process.ReplaceError {4620) process.ReplaceError {
4698 _ = ev;
4699 try sync.cancel_region.await(.nothing);4621 try sync.cancel_region.await(.nothing);
4700 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {4622 switch (linux.errno(linux.execve(path, child_argv, env_block.slice.ptr))) {
4701 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.4623 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
...@@ -4766,7 +4688,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr...@@ -4766,7 +4688,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
4766 child.resource_usage_statistics.rusage = rusage;4688 child.resource_usage_statistics.rusage = rusage;
4767 break;4689 break;
4768 },4690 },
4769 .INTR, .CANCELED => continue,4691 .INTR, .CANCELED => {},
4770 .CHILD => |err| return errnoBug(err), // Double-free.4692 .CHILD => |err| return errnoBug(err), // Double-free.
4771 else => |err| return unexpectedErrno(err),4693 else => |err| return unexpectedErrno(err),
4772 }4694 }
...@@ -4781,7 +4703,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr...@@ -4781,7 +4703,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
4781 _, .CONTINUED => .{ .unknown = status },4703 _, .CONTINUED => .{ .unknown = status },
4782 };4704 };
4783 },4705 },
4784 .INTR, .CANCELED => continue,4706 .INTR, .CANCELED => {},
4785 .CHILD => |err| return errnoBug(err), // Double-free.4707 .CHILD => |err| return errnoBug(err), // Double-free.
4786 else => |err| return unexpectedErrno(err),4708 else => |err| return unexpectedErrno(err),
4787 }4709 }
...@@ -4798,7 +4720,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4798,7 +4720,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4798 const pid = child.id.?;4720 const pid = child.id.?;
4799 while (true) switch (linux.errno(linux.kill(pid, .TERM))) {4721 while (true) switch (linux.errno(linux.kill(pid, .TERM))) {
4800 .SUCCESS => break,4722 .SUCCESS => break,
4801 .INTR => continue,4723 .INTR => {},
4802 .PERM => return,4724 .PERM => return,
4803 .INVAL => |err| return errnoBug(err) catch {},4725 .INVAL => |err| return errnoBug(err) catch {},
4804 .SRCH => |err| return errnoBug(err) catch {},4726 .SRCH => |err| return errnoBug(err) catch {},
...@@ -4830,7 +4752,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4830,7 +4752,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4830 ev.yield(null, .nothing);4752 ev.yield(null, .nothing);
4831 switch (maybe_sync.cancel_region.errno()) {4753 switch (maybe_sync.cancel_region.errno()) {
4832 .SUCCESS => return,4754 .SUCCESS => return,
4833 .INTR, .CANCELED => continue,4755 .INTR, .CANCELED => {},
4834 .CHILD => |err| return errnoBug(err) catch {}, // Double-free.4756 .CHILD => |err| return errnoBug(err) catch {}, // Double-free.
4835 else => |err| return unexpectedErrno(err) catch {},4757 else => |err| return unexpectedErrno(err) catch {},
4836 }4758 }
...@@ -4839,15 +4761,15 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4839,15 +4761,15 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
48394761
4840fn childCleanup(ev: *Evented, child: *process.Child) void {4762fn childCleanup(ev: *Evented, child: *process.Child) void {
4841 if (child.stdin) |*stdin| {4763 if (child.stdin) |*stdin| {
4842 ev.close(stdin.handle);4764 ev.closeAsync(stdin.handle);
4843 child.stdin = null;4765 child.stdin = null;
4844 }4766 }
4845 if (child.stdout) |*stdout| {4767 if (child.stdout) |*stdout| {
4846 ev.close(stdout.handle);4768 ev.closeAsync(stdout.handle);
4847 child.stdout = null;4769 child.stdout = null;
4848 }4770 }
4849 if (child.stderr) |*stderr| {4771 if (child.stderr) |*stderr| {
4850 ev.close(stderr.handle);4772 ev.closeAsync(stderr.handle);
4851 child.stderr = null;4773 child.stderr = null;
4852 }4774 }
4853 child.id = null;4775 child.id = null;
...@@ -4953,14 +4875,10 @@ fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {...@@ -4953,14 +4875,10 @@ fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {
4953 .resv = 0,4875 .resv = 0,
4954 };4876 };
4955 ev.yield(null, .nothing);4877 ev.yield(null, .nothing);
4956 switch (cancel_region.errno()) {4878 // Handles SUCCESS as well as clock not available and unexpected
4957 // Handles SUCCESS as well as clock not available and unexpected4879 // errors. The user had a chance to check clock resolution before
4958 // errors. The user had a chance to check clock resolution before4880 // getting here, which would have reported 0, making this a legal
4959 // getting here, which would have reported 0, making this a legal4881 // amount of time to sleep.
4960 // amount of time to sleep.
4961 else => return,
4962 .INTR, .CANCELED => return error.Canceled,
4963 }
4964}4882}
49654883
4966fn random(userdata: ?*anyopaque, buffer: []u8) void {4884fn random(userdata: ?*anyopaque, buffer: []u8) void {
...@@ -5037,7 +4955,7 @@ fn netBindIp(...@@ -5037,7 +4955,7 @@ fn netBindIp(
5037 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };4955 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
5038 defer maybe_sync.deinit(ev);4956 defer maybe_sync.deinit(ev);
5039 const socket_fd = try ev.socket(&maybe_sync.cancel_region, family, options);4957 const socket_fd = try ev.socket(&maybe_sync.cancel_region, family, options);
5040 errdefer ev.close(socket_fd);4958 errdefer ev.closeAsync(socket_fd);
5041 var storage: PosixAddress = undefined;4959 var storage: PosixAddress = undefined;
5042 var addr_len = addressToPosix(address, &storage);4960 var addr_len = addressToPosix(address, &storage);
5043 try ev.bind(&maybe_sync.cancel_region, socket_fd, &storage.any, addr_len);4961 try ev.bind(&maybe_sync.cancel_region, socket_fd, &storage.any, addr_len);
...@@ -5204,23 +5122,19 @@ fn netReceive(...@@ -5204,23 +5122,19 @@ fn netReceive(
5204 .data = data,5122 .data = data,
5205 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control,5123 .control = if (msg.control) |ptr| @as([*]u8, @ptrCast(ptr))[0..msg.controllen] else message.control,
5206 .flags = .{5124 .flags = .{
5207 .eor = (msg.flags & linux.MSG.EOR) != 0,5125 .eor = msg.flags & linux.MSG.EOR != 0,
5208 .trunc = (msg.flags & linux.MSG.TRUNC) != 0,5126 .trunc = msg.flags & linux.MSG.TRUNC != 0,
5209 .ctrunc = (msg.flags & linux.MSG.CTRUNC) != 0,5127 .ctrunc = msg.flags & linux.MSG.CTRUNC != 0,
5210 .oob = (msg.flags & linux.MSG.OOB) != 0,5128 .oob = msg.flags & linux.MSG.OOB != 0,
5211 .errqueue = if (@hasDecl(linux.MSG, "ERRQUEUE")) (msg.flags & linux.MSG.ERRQUEUE) != 0 else false,5129 .errqueue = msg.flags & linux.MSG.ERRQUEUE != 0,
5212 },5130 },
5213 };5131 };
5214 message_i += 1;5132 message_i += 1;
5215 continue;5133 continue;
5216 },5134 },
5217 .AGAIN => unreachable,5135 .AGAIN => unreachable,
5218 .INTR, .CANCELED => {5136 .INTR, .CANCELED => if (deadline) |d| if (now(ev, d.clock).nanoseconds >= d.raw.nanoseconds)
5219 if (deadline) |d| {5137 return .{ error.Timeout, message_i },
5220 if (now(ev, d.clock).nanoseconds >= d.raw.nanoseconds) return .{ error.Timeout, message_i };
5221 }
5222 continue;
5223 },
52245138
5225 .BADF => |err| return .{ errnoBug(err), message_i },5139 .BADF => |err| return .{ errnoBug(err), message_i },
5226 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },5140 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },
...@@ -5323,7 +5237,7 @@ fn netShutdown(...@@ -5323,7 +5237,7 @@ fn netShutdown(
5323 ev.yield(null, .nothing);5237 ev.yield(null, .nothing);
5324 switch (cancel_region.errno()) {5238 switch (cancel_region.errno()) {
5325 .SUCCESS => return,5239 .SUCCESS => return,
5326 .INTR, .CANCELED => continue,5240 .INTR, .CANCELED => {},
5327 .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),5241 .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),
5328 .NOTCONN => return error.SocketUnconnected,5242 .NOTCONN => return error.SocketUnconnected,
5329 .NOBUFS => return error.SystemResources,5243 .NOBUFS => return error.SystemResources,
...@@ -5393,7 +5307,7 @@ fn bind(...@@ -5393,7 +5307,7 @@ fn bind(
5393 ev.yield(null, .nothing);5307 ev.yield(null, .nothing);
5394 switch (cancel_region.errno()) {5308 switch (cancel_region.errno()) {
5395 .SUCCESS => return,5309 .SUCCESS => return,
5396 .INTR, .CANCELED => continue,5310 .INTR, .CANCELED => {},
5397 .ADDRINUSE => return error.AddressInUse,5311 .ADDRINUSE => return error.AddressInUse,
5398 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5312 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5399 .INVAL => |err| return errnoBug(err), // invalid parameters5313 .INVAL => |err| return errnoBug(err), // invalid parameters
...@@ -5407,13 +5321,12 @@ fn bind(...@@ -5407,13 +5321,12 @@ fn bind(
5407 }5321 }
5408}5322}
54095323
5410fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {5324fn chdir(sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {
5411 _ = ev;
5412 while (true) {5325 while (true) {
5413 try sync.cancel_region.await(.nothing);5326 try sync.cancel_region.await(.nothing);
5414 switch (linux.errno(linux.chdir(path))) {5327 switch (linux.errno(linux.chdir(path))) {
5415 .SUCCESS => return,5328 .SUCCESS => return,
5416 .INTR => continue,5329 .INTR => {},
5417 .ACCES => return error.AccessDenied,5330 .ACCES => return error.AccessDenied,
5418 .IO => return error.FileSystem,5331 .IO => return error.FileSystem,
5419 .LOOP => return error.SymLinkLoop,5332 .LOOP => return error.SymLinkLoop,
...@@ -5429,6 +5342,36 @@ fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError...@@ -5429,6 +5342,36 @@ fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError
5429}5342}
54305343
5431fn close(ev: *Evented, fd: fd_t) void {5344fn close(ev: *Evented, fd: fd_t) void {
5345 var cancel_region: CancelRegion = .initBlocked();
5346 defer cancel_region.deinit();
5347 const thread = cancel_region.awaitIoUring() catch |err| switch (err) {
5348 error.Canceled => unreachable, // blocked
5349 };
5350 thread.enqueue().* = .{
5351 .opcode = .CLOSE,
5352 .flags = 0,
5353 .ioprio = 0,
5354 .fd = fd,
5355 .off = 0,
5356 .addr = 0,
5357 .len = 0,
5358 .rw_flags = 0,
5359 .user_data = @intFromPtr(cancel_region.fiber),
5360 .buf_index = 0,
5361 .personality = 0,
5362 .splice_fd_in = 0,
5363 .addr3 = 0,
5364 .resv = 0,
5365 };
5366 ev.yield(null, .nothing);
5367 switch (cancel_region.errno()) {
5368 .BADF => recoverableOsBugDetected(), // Always a race condition.
5369 .INTR => {}, // This is still a success. See https://github.com/ziglang/zig/issues/2425
5370 else => {},
5371 }
5372}
5373
5374fn closeAsync(ev: *Evented, fd: fd_t) void {
5432 _ = ev;5375 _ = ev;
5433 const thread: *Thread = .current();5376 const thread: *Thread = .current();
5434 thread.enqueue().* = .{5377 thread.enqueue().* = .{
...@@ -5449,14 +5392,13 @@ fn close(ev: *Evented, fd: fd_t) void {...@@ -5449,14 +5392,13 @@ fn close(ev: *Evented, fd: fd_t) void {
5449 };5392 };
5450}5393}
54515394
5452fn fchdir(ev: *Evented, sync: *CancelRegion.Sync, dir: fd_t) process.SetCurrentDirError!void {5395fn fchdir(sync: *CancelRegion.Sync, dir: fd_t) process.SetCurrentDirError!void {
5453 _ = ev;
5454 if (dir == linux.AT.FDCWD) return;5396 if (dir == linux.AT.FDCWD) return;
5455 while (true) {5397 while (true) {
5456 try sync.cancel_region.await(.nothing);5398 try sync.cancel_region.await(.nothing);
5457 switch (linux.errno(linux.fchdir(dir))) {5399 switch (linux.errno(linux.fchdir(dir))) {
5458 .SUCCESS => return,5400 .SUCCESS => return,
5459 .INTR => continue,5401 .INTR => {},
5460 .ACCES => return error.AccessDenied,5402 .ACCES => return error.AccessDenied,
5461 .NOTDIR => return error.NotDir,5403 .NOTDIR => return error.NotDir,
5462 .IO => return error.FileSystem,5404 .IO => return error.FileSystem,
...@@ -5479,7 +5421,7 @@ fn fchmodat(...@@ -5479,7 +5421,7 @@ fn fchmodat(
5479 try sync.cancel_region.await(.nothing);5421 try sync.cancel_region.await(.nothing);
5480 switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {5422 switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {
5481 .SUCCESS => return,5423 .SUCCESS => return,
5482 .INTR => continue,5424 .INTR => {},
5483 .BADF => |err| return errnoBug(err),5425 .BADF => |err| return errnoBug(err),
5484 .FAULT => |err| return errnoBug(err),5426 .FAULT => |err| return errnoBug(err),
5485 .INVAL => |err| return errnoBug(err),5427 .INVAL => |err| return errnoBug(err),
...@@ -5511,7 +5453,7 @@ fn fchownat(...@@ -5511,7 +5453,7 @@ fn fchownat(
5511 try sync.cancel_region.await(.nothing);5453 try sync.cancel_region.await(.nothing);
5512 switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {5454 switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {
5513 .SUCCESS => return,5455 .SUCCESS => return,
5514 .INTR => continue,5456 .INTR => {},
5515 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate`5457 .BADF => |err| return errnoBug(err), // likely fd refers to directory opened without `Dir.OpenOptions.iterate`
5516 .FAULT => |err| return errnoBug(err),5458 .FAULT => |err| return errnoBug(err),
5517 .INVAL => |err| return errnoBug(err),5459 .INVAL => |err| return errnoBug(err),
...@@ -5543,7 +5485,7 @@ fn flock(...@@ -5543,7 +5485,7 @@ fn flock(
5543 .exclusive => LOCK.EX,5485 .exclusive => LOCK.EX,
5544 })))) {5486 })))) {
5545 .SUCCESS => return,5487 .SUCCESS => return,
5546 .INTR => continue,5488 .INTR => {},
5547 .BADF => |err| return errnoBug(err),5489 .BADF => |err| return errnoBug(err),
5548 .INVAL => |err| return errnoBug(err), // invalid parameters5490 .INVAL => |err| return errnoBug(err), // invalid parameters
5549 .NOLCK => return error.SystemResources,5491 .NOLCK => return error.SystemResources,
...@@ -5593,7 +5535,7 @@ fn getsockname(...@@ -5593,7 +5535,7 @@ fn getsockname(
5593 try sync.cancel_region.await(.nothing);5535 try sync.cancel_region.await(.nothing);
5594 switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {5536 switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {
5595 .SUCCESS => return,5537 .SUCCESS => return,
5596 .INTR => continue,5538 .INTR => {},
5597 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5539 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5598 .FAULT => |err| return errnoBug(err),5540 .FAULT => |err| return errnoBug(err),
5599 .INVAL => |err| return errnoBug(err), // invalid parameters5541 .INVAL => |err| return errnoBug(err), // invalid parameters
...@@ -5634,7 +5576,7 @@ fn linkat(...@@ -5634,7 +5576,7 @@ fn linkat(
5634 ev.yield(null, .nothing);5576 ev.yield(null, .nothing);
5635 switch (cancel_region.errno()) {5577 switch (cancel_region.errno()) {
5636 .SUCCESS => return,5578 .SUCCESS => return,
5637 .INTR, .CANCELED => continue,5579 .INTR, .CANCELED => {},
5638 .ACCES => return error.AccessDenied,5580 .ACCES => return error.AccessDenied,
5639 .DQUOT => return error.DiskQuota,5581 .DQUOT => return error.DiskQuota,
5640 .EXIST => return error.PathAlreadyExists,5582 .EXIST => return error.PathAlreadyExists,
...@@ -5674,7 +5616,7 @@ fn lseek(...@@ -5674,7 +5616,7 @@ fn lseek(
5674 8 => linux.lseek(fd, @bitCast(offset), whence),5616 8 => linux.lseek(fd, @bitCast(offset), whence),
5675 })) {5617 })) {
5676 .SUCCESS => return,5618 .SUCCESS => return,
5677 .INTR => continue,5619 .INTR => {},
5678 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5620 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5679 .INVAL => return error.Unseekable,5621 .INVAL => return error.Unseekable,
5680 .OVERFLOW => return error.Unseekable,5622 .OVERFLOW => return error.Unseekable,
...@@ -5717,7 +5659,7 @@ fn openat(...@@ -5717,7 +5659,7 @@ fn openat(
5717 const completion = cancel_region.completion();5659 const completion = cancel_region.completion();
5718 switch (completion.errno()) {5660 switch (completion.errno()) {
5719 .SUCCESS => return completion.result,5661 .SUCCESS => return completion.result,
5720 .INTR, .CANCELED => continue,5662 .INTR, .CANCELED => {},
5721 .FAULT => |err| return errnoBug(err),5663 .FAULT => |err| return errnoBug(err),
5722 .INVAL => return error.BadPathName,5664 .INVAL => return error.BadPathName,
5723 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5665 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
...@@ -5779,7 +5721,7 @@ fn preadv(...@@ -5779,7 +5721,7 @@ fn preadv(
5779 const completion = cancel_region.completion();5721 const completion = cancel_region.completion();
5780 switch (completion.errno()) {5722 switch (completion.errno()) {
5781 .SUCCESS => return @as(u32, @bitCast(completion.result)),5723 .SUCCESS => return @as(u32, @bitCast(completion.result)),
5782 .INTR, .CANCELED => continue,5724 .INTR, .CANCELED => {},
5783 .INVAL => |err| return errnoBug(err),5725 .INVAL => |err| return errnoBug(err),
5784 .FAULT => |err| return errnoBug(err),5726 .FAULT => |err| return errnoBug(err),
5785 .AGAIN => return error.WouldBlock,5727 .AGAIN => return error.WouldBlock,
...@@ -5826,7 +5768,7 @@ fn pwritev(...@@ -5826,7 +5768,7 @@ fn pwritev(
5826 const completion = cancel_region.completion();5768 const completion = cancel_region.completion();
5827 switch (completion.errno()) {5769 switch (completion.errno()) {
5828 .SUCCESS => return @as(u32, @bitCast(completion.result)),5770 .SUCCESS => return @as(u32, @bitCast(completion.result)),
5829 .INTR, .CANCELED => continue,5771 .INTR, .CANCELED => {},
5830 .INVAL => |err| return errnoBug(err),5772 .INVAL => |err| return errnoBug(err),
5831 .FAULT => |err| return errnoBug(err),5773 .FAULT => |err| return errnoBug(err),
5832 .AGAIN => return error.WouldBlock,5774 .AGAIN => return error.WouldBlock,
...@@ -5876,7 +5818,7 @@ fn realPath(...@@ -5876,7 +5818,7 @@ fn realPath(
5876 const rc = linux.readlink(proc_path, out_buffer.ptr, out_buffer.len);5818 const rc = linux.readlink(proc_path, out_buffer.ptr, out_buffer.len);
5877 switch (linux.errno(rc)) {5819 switch (linux.errno(rc)) {
5878 .SUCCESS => return rc,5820 .SUCCESS => return rc,
5879 .INTR => continue,5821 .INTR => {},
5880 .ACCES => return error.AccessDenied,5822 .ACCES => return error.AccessDenied,
5881 .FAULT => |err| return errnoBug(err),5823 .FAULT => |err| return errnoBug(err),
5882 .IO => return error.FileSystem,5824 .IO => return error.FileSystem,
...@@ -5921,7 +5863,7 @@ fn renameat(...@@ -5921,7 +5863,7 @@ fn renameat(
5921 ev.yield(null, .nothing);5863 ev.yield(null, .nothing);
5922 switch (cancel_region.errno()) {5864 switch (cancel_region.errno()) {
5923 .SUCCESS => return,5865 .SUCCESS => return,
5924 .INTR, .CANCELED => continue,5866 .INTR, .CANCELED => {},
5925 .ACCES => return error.AccessDenied,5867 .ACCES => return error.AccessDenied,
5926 .PERM => return error.PermissionDenied,5868 .PERM => return error.PermissionDenied,
5927 .BUSY => return error.FileBusy,5869 .BUSY => return error.FileBusy,
...@@ -5988,7 +5930,7 @@ fn setsockopt(...@@ -5988,7 +5930,7 @@ fn setsockopt(
5988 ev.yield(null, .nothing);5930 ev.yield(null, .nothing);
5989 switch (cancel_region.errno()) {5931 switch (cancel_region.errno()) {
5990 .SUCCESS => return,5932 .SUCCESS => return,
5991 .INTR, .CANCELED => continue,5933 .INTR, .CANCELED => {},
5992 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5934 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5993 .NOTSOCK => |err| return errnoBug(err),5935 .NOTSOCK => |err| return errnoBug(err),
5994 .INVAL => |err| return errnoBug(err),5936 .INVAL => |err| return errnoBug(err),
...@@ -6039,7 +5981,7 @@ fn socket(...@@ -6039,7 +5981,7 @@ fn socket(
6039 const completion = cancel_region.completion();5981 const completion = cancel_region.completion();
6040 switch (completion.errno()) {5982 switch (completion.errno()) {
6041 .SUCCESS => break completion.result,5983 .SUCCESS => break completion.result,
6042 .INTR, .CANCELED => continue,5984 .INTR, .CANCELED => {},
6043 .AFNOSUPPORT => return error.AddressFamilyUnsupported,5985 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
6044 .INVAL => return error.ProtocolUnsupportedBySystem,5986 .INVAL => return error.ProtocolUnsupportedBySystem,
6045 .MFILE => return error.ProcessFdQuotaExceeded,5987 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -6051,7 +5993,7 @@ fn socket(...@@ -6051,7 +5993,7 @@ fn socket(
6051 else => |err| return unexpectedErrno(err),5993 else => |err| return unexpectedErrno(err),
6052 }5994 }
6053 };5995 };
6054 errdefer ev.close(socket_fd);5996 errdefer ev.closeAsync(socket_fd);
60555997
6056 if (options.ip6_only) {5998 if (options.ip6_only) {
6057 if (linux.IPV6 == void) return error.OptionUnsupported;5999 if (linux.IPV6 == void) return error.OptionUnsupported;
...@@ -6101,7 +6043,7 @@ fn statx(...@@ -6101,7 +6043,7 @@ fn statx(
6101 ev.yield(null, .nothing);6043 ev.yield(null, .nothing);
6102 switch (cancel_region.errno()) {6044 switch (cancel_region.errno()) {
6103 .SUCCESS => return statFromLinux(&statx_buf),6045 .SUCCESS => return statFromLinux(&statx_buf),
6104 .INTR, .CANCELED => continue,6046 .INTR, .CANCELED => {},
6105 .ACCES => return error.AccessDenied,6047 .ACCES => return error.AccessDenied,
6106 .BADF => |err| return errnoBug(err), // File descriptor used after closed.6048 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
6107 .FAULT => |err| return errnoBug(err),6049 .FAULT => |err| return errnoBug(err),
...@@ -6140,7 +6082,7 @@ fn utimensat(...@@ -6140,7 +6082,7 @@ fn utimensat(
6140 try sync.cancel_region.await(.nothing);6082 try sync.cancel_region.await(.nothing);
6141 switch (linux.errno(linux.utimensat(dir, path, times, flags))) {6083 switch (linux.errno(linux.utimensat(dir, path, times, flags))) {
6142 .SUCCESS => return,6084 .SUCCESS => return,
6143 .INTR => continue,6085 .INTR => {},
6144 .BADF => |err| return errnoBug(err), // always a race condition6086 .BADF => |err| return errnoBug(err), // always a race condition
6145 .FAULT => |err| return errnoBug(err),6087 .FAULT => |err| return errnoBug(err),
6146 .INVAL => |err| return errnoBug(err),6088 .INVAL => |err| return errnoBug(err),
...@@ -6152,19 +6094,33 @@ fn utimensat(...@@ -6152,19 +6094,33 @@ fn utimensat(
6152 }6094 }
6153}6095}
61546096
6155fn writeAll(6097fn writeAllSync(sync: *CancelRegion.Sync, fd: fd_t, buffer: []const u8) File.Writer.Error!void {
6156 ev: *Evented,
6157 cancel_region: *CancelRegion,
6158 fd: fd_t,
6159 buffer: []const u8,
6160) (File.Writer.Error || error{EndOfStream})!void {
6161 var index: usize = 0;6098 var index: usize = 0;
6162 while (buffer.len - index != 0) {6099 while (buffer.len - index != 0) index += try writeSync(sync, fd, buffer[index..]);
6163 const len = try ev.pwritev(cancel_region, fd, &.{6100}
6164 .{ .base = buffer[index..].ptr, .len = buffer.len - index },6101
6165 }, null);6102fn writeSync(sync: *CancelRegion.Sync, fd: fd_t, buffer: []const u8) File.Writer.Error!usize {
6166 if (len == 0) return error.EndOfStream;6103 while (true) {
6167 index += len;6104 try sync.cancel_region.await(.nothing);
6105 const rc = linux.write(fd, buffer.ptr, buffer.len);
6106 switch (linux.errno(rc)) {
6107 .SUCCESS => return @intCast(rc),
6108 .INTR => {},
6109 .INVAL => |err| return errnoBug(err),
6110 .FAULT => |err| return errnoBug(err),
6111 .AGAIN => return error.WouldBlock,
6112 .BADF => return error.NotOpenForWriting, // Can be a race condition.
6113 .DESTADDRREQ => |err| return errnoBug(err), // `connect` was never called.
6114 .DQUOT => return error.DiskQuota,
6115 .FBIG => return error.FileTooBig,
6116 .IO => return error.InputOutput,
6117 .NOSPC => return error.NoSpaceLeft,
6118 .PERM => return error.PermissionDenied,
6119 .PIPE => return error.BrokenPipe,
6120 .CONNRESET => |err| return errnoBug(err), // Not a socket handle.
6121 .BUSY => return error.DeviceBusy,
6122 else => |err| return unexpectedErrno(err),
6123 }
6168 }6124 }
6169}6125}
61706126
lib/std/Io/test.zig-34
...@@ -282,40 +282,6 @@ test "Group.concurrent" {...@@ -282,40 +282,6 @@ test "Group.concurrent" {
282 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);282 try testing.expectEqualSlices(usize, &.{ 45, 245 }, &results);
283}283}
284284
285test "select" {
286 const io = testing.io;
287
288 var queue: Io.Queue(u8) = .init(&.{});
289
290 var get_a = io.concurrent(Io.Queue(u8).getOne, .{ &queue, io }) catch |err| switch (err) {
291 error.ConcurrencyUnavailable => {
292 try testing.expect(builtin.single_threaded);
293 return;
294 },
295 };
296 defer _ = get_a.cancel(io) catch {};
297
298 var get_b = try io.concurrent(Io.Queue(u8).getOne, .{ &queue, io });
299 defer _ = get_b.cancel(io) catch {};
300
301 var timeout = io.async(Io.sleep, .{ io, .fromMilliseconds(1), .awake });
302 defer timeout.cancel(io) catch {};
303
304 switch (try io.select(.{
305 .get_a = &get_a,
306 .get_b = &get_b,
307 .timeout = &timeout,
308 })) {
309 .get_a => return error.TestFailure,
310 .get_b => return error.TestFailure,
311 .timeout => {
312 queue.close(io);
313 try testing.expectError(error.Closed, get_a.await(io));
314 try testing.expectError(error.Closed, get_b.await(io));
315 },
316 }
317}
318
319fn testQueue(comptime len: usize) !void {285fn testQueue(comptime len: usize) !void {
320 const io = testing.io;286 const io = testing.io;
321 var buf: [len]usize = undefined;287 var buf: [len]usize = undefined;
lib/std/os/linux/IoUring.zig+4
...@@ -201,6 +201,10 @@ pub fn enter(self: *IoUring, to_submit: u32, min_complete: u32, flags: u32) !u32...@@ -201,6 +201,10 @@ pub fn enter(self: *IoUring, to_submit: u32, min_complete: u32, flags: u32) !u32
201 // The kernel believes our `self.fd` does not refer to an io_uring instance,201 // The kernel believes our `self.fd` does not refer to an io_uring instance,
202 // or the opcode is valid but not supported by this kernel (more likely):202 // or the opcode is valid but not supported by this kernel (more likely):
203 .OPNOTSUPP => return error.OpcodeNotSupported,203 .OPNOTSUPP => return error.OpcodeNotSupported,
204 // The thread submitting the work is invalid. This may occur if IORING_ENTER_GETEVENTS
205 // and IORING_SETUP_DEFER_TASKRUN is set, but the submitting thread is not the thread
206 // that initially created or enabled the io_uring associated with fd.
207 .EXIST => return error.InvalidThread,
204 // The operation was interrupted by a delivery of a signal before it could complete.208 // The operation was interrupted by a delivery of a signal before it could complete.
205 // This can happen while waiting for events with IORING_ENTER_GETEVENTS:209 // This can happen while waiting for events with IORING_ENTER_GETEVENTS:
206 .INTR => return error.SignalInterrupt,210 .INTR => return error.SignalInterrupt,