authorgravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-02-15 05:51:42-05:00
committergravatar for jacobly@ziglang.orgJacob Young <jacobly@ziglang.org> 2026-02-15 05:52:07-05:00
log5763f7dbcc65288d7d13236f82eec9a23d97bee7
tree460566d2e6d92c5c1916493b87e48d13a1e5b1d2
parentd9fc7fa04db797d7b27dab2d9d6f56f63848da76

std.Io.Evented: clean up supporting code for `select`


3 files changed, 207 insertions(+), 210 deletions(-)

lib/std/Io/Dispatch.zig+33-53
...@@ -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
...@@ -215,7 +211,6 @@ const Fiber = struct {...@@ -215,7 +211,6 @@ const Fiber = struct {
215 }211 }
216212
217 fn destroy(fiber: *Fiber, ev: *Evented) void {213 fn destroy(fiber: *Fiber, ev: *Evented) void {
218 assert(fiber.status.queue_next == null);
219 ev.allocator().free(fiber.allocatedSlice());214 ev.allocator().free(fiber.allocatedSlice());
220 }215 }
221216
...@@ -271,8 +266,8 @@ const Fiber = struct {...@@ -271,8 +266,8 @@ const Fiber = struct {
271 .group => {266 .group => {
272 // The awaiter received a cancelation request while awaiting a group,267 // The awaiter received a cancelation request while awaiting a group,
273 // so propagate the cancelation to the group.268 // so propagate the cancelation to the group.
274 if (fiber.status.awaiting_group.cancel(ev, null)) {269 if (fiber.awaiting_group.cancel(ev, null)) {
275 fiber.status = .{ .queue_next = null };270 fiber.awaiting_group = undefined;
276 ev.queue.async(fiber, &Fiber.@"resume");271 ev.queue.async(fiber, &Fiber.@"resume");
277 }272 }
278 },273 },
...@@ -516,9 +511,8 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v...@@ -516,9 +511,8 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
516 .required_align = {},511 .required_align = {},
517 .evented = ev,512 .evented = ev,
518 .context = undefined,513 .context = undefined,
519 .await_count = 0,
520 .link = .{ .awaiter = null },514 .link = .{ .awaiter = null },
521 .status = .{ .queue_next = null },515 .awaiting_group = undefined,
522 .cancel_status = .unrequested,516 .cancel_status = .unrequested,
523 .cancel_protection = .unblocked,517 .cancel_protection = .unblocked,
524 },518 },
...@@ -636,7 +630,7 @@ const SwitchMessage = struct {...@@ -636,7 +630,7 @@ const SwitchMessage = struct {
636630
637 const PendingTask = union(enum) {631 const PendingTask = union(enum) {
638 nothing,632 nothing,
639 await: u31,633 await: *Fiber,
640 activate: c.dispatch.object_t,634 activate: c.dispatch.object_t,
641 @"resume": c.dispatch.object_t,635 @"resume": c.dispatch.object_t,
642 group_await: Group,636 group_await: Group,
...@@ -655,10 +649,10 @@ const SwitchMessage = struct {...@@ -655,10 +649,10 @@ const SwitchMessage = struct {
655 thread.current_context = message.contexts.new;649 thread.current_context = message.contexts.new;
656 switch (message.pending_task) {650 switch (message.pending_task) {
657 .nothing => {},651 .nothing => {},
658 .await => |count| {652 .await => |awaiting| {
659 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));653 const awaiter: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
660 if (@atomicRmw(i32, &fiber.await_count, .Sub, count, .monotonic) > 0)654 if (@atomicRmw(?*Fiber, &awaiting.link.awaiter, .Xchg, awaiter, .acq_rel) ==
661 ev.queue.async(fiber, &Fiber.@"resume");655 Fiber.finished) ev.queue.async(awaiter, &Fiber.@"resume");
662 },656 },
663 .activate => |object| object.activate(),657 .activate => |object| object.activate(),
664 .@"resume" => |object| object.@"resume"(),658 .@"resume" => |object| object.@"resume"(),
...@@ -992,7 +986,7 @@ fn crashHandler(userdata: ?*anyopaque) void {...@@ -992,7 +986,7 @@ fn crashHandler(userdata: ?*anyopaque) void {
992}986}
993987
994const AsyncClosure = struct {988const AsyncClosure = struct {
995 ev: *Evented,989 evented: *Evented,
996 fiber: *Fiber,990 fiber: *Fiber,
997 start: *const fn (context: *const anyopaque, result: *anyopaque) void,991 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
998 result_align: Alignment,992 result_align: Alignment,
...@@ -1029,13 +1023,13 @@ const AsyncClosure = struct {...@@ -1029,13 +1023,13 @@ const AsyncClosure = struct {
1029 closure: *AsyncClosure,1023 closure: *AsyncClosure,
1030 message: *const SwitchMessage,1024 message: *const SwitchMessage,
1031 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {1025 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
1032 message.handle(closure.ev);1026 const ev = closure.evented;
1033 const fiber = closure.fiber;1027 const fiber = closure.fiber;
1028 message.handle(ev);
1034 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));1029 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
1035 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|
1036 if (@atomicRmw(i32, &awaiter.await_count, .Add, 1, .monotonic) == -1)1031 ev.queue.async(awaiter, &Fiber.@"resume");
1037 closure.ev.queue.async(awaiter, &Fiber.@"resume");1032 ev.yield(.nothing);
1038 closure.ev.yield(.nothing);
1039 unreachable; // switched to dead fiber1033 unreachable; // switched to dead fiber
1040 }1034 }
1041};1035};
...@@ -1090,14 +1084,13 @@ fn concurrent(...@@ -1090,14 +1084,13 @@ fn concurrent(
1090 },1084 },
1091 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1085 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1092 },1086 },
1093 .await_count = 0,
1094 .link = .{ .awaiter = null },1087 .link = .{ .awaiter = null },
1095 .status = .{ .queue_next = null },1088 .awaiting_group = undefined,
1096 .cancel_status = .unrequested,1089 .cancel_status = .unrequested,
1097 .cancel_protection = .unblocked,1090 .cancel_protection = .unblocked,
1098 };1091 };
1099 closure.* = .{1092 closure.* = .{
1100 .ev = ev,1093 .evented = ev,
1101 .fiber = fiber,1094 .fiber = fiber,
1102 .start = start,1095 .start = start,
1103 .result_align = result_alignment,1096 .result_align = result_alignment,
...@@ -1115,18 +1108,11 @@ fn await(...@@ -1115,18 +1108,11 @@ fn await(
1115 result_alignment: Alignment,1108 result_alignment: Alignment,
1116) void {1109) void {
1117 const ev: *Evented = @ptrCast(@alignCast(userdata));1110 const ev: *Evented = @ptrCast(@alignCast(userdata));
1118 const fiber = Thread.current().currentFiber();1111 const awaiting: *Fiber = @ptrCast(@alignCast(future));
1119 const future_fiber: *Fiber = @ptrCast(@alignCast(future));1112 if (@atomicLoad(?*Fiber, &awaiting.link.awaiter, .acquire) != Fiber.finished)
1120 if (@atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, fiber, .acq_rel)) |awaiter| {1113 ev.yield(.{ .await = awaiting });
1121 assert(awaiter == Fiber.finished);1114 @memcpy(result, awaiting.resultBytes(result_alignment));
1122 } else while (true) {1115 awaiting.destroy(ev);
1123 ev.yield(.{ .await = 1 });
1124 const awaiter = @atomicLoad(?*Fiber, &future_fiber.link.awaiter, .acquire);
1125 if (awaiter == Fiber.finished) break;
1126 assert(awaiter == fiber); // spurious wakeup
1127 }
1128 @memcpy(result, future_fiber.resultBytes(result_alignment));
1129 future_fiber.destroy(ev);
1130}1116}
11311117
1132fn cancel(1118fn cancel(
...@@ -1261,8 +1247,8 @@ const Group = struct {...@@ -1261,8 +1247,8 @@ const Group = struct {
1261 .awaiter_delayed = false,1247 .awaiter_delayed = false,
1262 .fibers = .null,1248 .fibers = .null,
1263 }, .release);1249 }, .release);
1264 assert(awaiter.status.awaiting_group.ptr == group.ptr);1250 assert(awaiter.awaiting_group.ptr == group.ptr);
1265 awaiter.status = .{ .queue_next = null };1251 awaiter.awaiting_group = undefined;
1266 return awaiter;1252 return awaiter;
1267 }1253 }
1268 // Race with `Fiber.requestCancel`1254 // Race with `Fiber.requestCancel`
...@@ -1330,8 +1316,7 @@ const Group = struct {...@@ -1330,8 +1316,7 @@ const Group = struct {
13301316
1331 /// Assumes the mutex is held.1317 /// Assumes the mutex is held.
1332 fn registerAwaiter(group: Group, awaiter: *Fiber) bool {1318 fn registerAwaiter(group: Group, awaiter: *Fiber) bool {
1333 assert(awaiter.status.queue_next == null);1319 awaiter.awaiting_group = group;
1334 awaiter.status = .{ .awaiting_group = group };
1335 assert(@atomicRmw(1320 assert(@atomicRmw(
1336 Awaiter,1321 Awaiter,
1337 group.awaiterPtr(),1322 group.awaiterPtr(),
...@@ -1343,7 +1328,7 @@ const Group = struct {...@@ -1343,7 +1328,7 @@ const Group = struct {
1343 }1328 }
13441329
1345 const AsyncClosure = struct {1330 const AsyncClosure = struct {
1346 ev: *Evented,1331 evented: *Evented,
1347 group: Group,1332 group: Group,
1348 fiber: *Fiber,1333 fiber: *Fiber,
1349 start: *const fn (context: *const anyopaque) Io.Cancelable!void,1334 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
...@@ -1382,19 +1367,15 @@ const Group = struct {...@@ -1382,19 +1367,15 @@ const Group = struct {
1382 closure: *Group.AsyncClosure,1367 closure: *Group.AsyncClosure,
1383 message: *const SwitchMessage,1368 message: *const SwitchMessage,
1384 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {1369 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
1385 message.handle(closure.ev);1370 const ev = closure.evented;
1386 assert(closure.fiber.status.queue_next == null);
1387 const result = closure.start(closure.contextPointer());
1388 const ev = closure.ev;
1389 const group = closure.group;
1390 const fiber = closure.fiber;1371 const fiber = closure.fiber;
1391 const cancel_acknowledged = fiber.cancel_protection.acknowledged;1372 message.handle(ev);
1392 if (result) {1373 if (closure.start(closure.contextPointer())) {
1393 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`
1394 } else |err| switch (err) {1375 } else |err| switch (err) {
1395 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
1396 }1377 }
1397 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");
1398 ev.yield(.destroy);1379 ev.yield(.destroy);
1399 unreachable; // switched to dead fiber1380 unreachable; // switched to dead fiber
1400 }1381 }
...@@ -1464,14 +1445,13 @@ fn groupConcurrent(...@@ -1464,14 +1445,13 @@ fn groupConcurrent(
1464 },1445 },
1465 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1446 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1466 },1447 },
1467 .await_count = 0,
1468 .link = .{ .group = .{ .prev = null, .next = null } },1448 .link = .{ .group = .{ .prev = null, .next = null } },
1469 .status = .{ .queue_next = null },1449 .awaiting_group = undefined,
1470 .cancel_status = .unrequested,1450 .cancel_status = .unrequested,
1471 .cancel_protection = .unblocked,1451 .cancel_protection = .unblocked,
1472 };1452 };
1473 closure.* = .{1453 closure.* = .{
1474 .ev = ev,1454 .evented = ev,
1475 .group = group,1455 .group = group,
1476 .fiber = fiber,1456 .fiber = fiber,
1477 .start = start,1457 .start = start,
lib/std/Io/Uring.zig+170-157
...@@ -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 },
...@@ -856,7 +855,6 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v...@@ -856,7 +855,6 @@ pub fn init(ev: *Evented, backing_allocator: Allocator, options: InitOptions) !v
856 main_fiber.* = .{855 main_fiber.* = .{
857 .required_align = {},856 .required_align = {},
858 .context = undefined,857 .context = undefined,
859 .await_count = 0,
860 .link = .{ .awaiter = null },858 .link = .{ .awaiter = null },
861 .status = .{ .queue_next = null },859 .status = .{ .queue_next = null },
862 .cancel_status = .unrequested,860 .cancel_status = .unrequested,
...@@ -1260,7 +1258,7 @@ const SwitchMessage = struct {...@@ -1260,7 +1258,7 @@ const SwitchMessage = struct {
1260 const PendingTask = union(enum) {1258 const PendingTask = union(enum) {
1261 nothing,1259 nothing,
1262 reschedule,1260 reschedule,
1263 await: u31,1261 await: *Fiber,
1264 group_await: Group,1262 group_await: Group,
1265 group_cancel: Group,1263 group_cancel: Group,
1266 batch_await: *Io.Batch,1264 batch_await: *Io.Batch,
...@@ -1284,10 +1282,11 @@ const SwitchMessage = struct {...@@ -1284,10 +1282,11 @@ const SwitchMessage = struct {
1284 assert(fiber.status.queue_next == null);1282 assert(fiber.status.queue_next == null);
1285 _ = ev.schedule(thread, .{ .head = fiber, .tail = fiber });1283 _ = ev.schedule(thread, .{ .head = fiber, .tail = fiber });
1286 },1284 },
1287 .await => |count| {1285 .await => |awaiting| {
1288 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));1286 const awaiter: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
1289 if (@atomicRmw(i32, &fiber.await_count, .Sub, count, .monotonic) > 0)1287 assert(awaiter.status.queue_next == null);
1290 _ = 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 });
1291 },1290 },
1292 .group_await => |group| {1291 .group_await => |group| {
1293 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));1292 const fiber: *Fiber = @alignCast(@fieldParentPtr("context", message.contexts.old));
...@@ -1361,7 +1360,7 @@ fn crashHandler(userdata: ?*anyopaque) void {...@@ -1361,7 +1360,7 @@ fn crashHandler(userdata: ?*anyopaque) void {
1361}1360}
13621361
1363const AsyncClosure = struct {1362const AsyncClosure = struct {
1364 ev: *Evented,1363 evented: *Evented,
1365 fiber: *Fiber,1364 fiber: *Fiber,
1366 start: *const fn (context: *const anyopaque, result: *anyopaque) void,1365 start: *const fn (context: *const anyopaque, result: *anyopaque) void,
1367 result_align: Alignment,1366 result_align: Alignment,
...@@ -1398,16 +1397,11 @@ const AsyncClosure = struct {...@@ -1398,16 +1397,11 @@ const AsyncClosure = struct {
1398 closure: *AsyncClosure,1397 closure: *AsyncClosure,
1399 message: *const SwitchMessage,1398 message: *const SwitchMessage,
1400 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {1399 ) callconv(.withStackAlign(.c, @alignOf(AsyncClosure))) noreturn {
1401 message.handle(closure.ev);1400 const ev = closure.evented;
1402 const fiber = closure.fiber;1401 const fiber = closure.fiber;
1402 message.handle(ev);
1403 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));1403 closure.start(closure.contextPointer(), fiber.resultBytes(closure.result_align));
1404 closure.ev.yield(1404 ev.yield(@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel), .nothing);
1405 if (@atomicRmw(?*Fiber, &fiber.link.awaiter, .Xchg, Fiber.finished, .acq_rel)) |awaiter|
1406 if (@atomicRmw(i32, &awaiter.await_count, .Add, 1, .monotonic) == -1) awaiter else null
1407 else
1408 null,
1409 .nothing,
1410 );
1411 unreachable; // switched to dead fiber1405 unreachable; // switched to dead fiber
1412 }1406 }
1413};1407};
...@@ -1461,7 +1455,6 @@ fn concurrent(...@@ -1461,7 +1455,6 @@ fn concurrent(
1461 },1455 },
1462 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1456 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1463 },1457 },
1464 .await_count = 0,
1465 .link = .{ .awaiter = null },1458 .link = .{ .awaiter = null },
1466 .status = .{ .queue_next = null },1459 .status = .{ .queue_next = null },
1467 .cancel_status = .unrequested,1460 .cancel_status = .unrequested,
...@@ -1479,7 +1472,7 @@ fn concurrent(...@@ -1479,7 +1472,7 @@ fn concurrent(
1479 },1472 },
1480 };1473 };
1481 closure.* = .{1474 closure.* = .{
1482 .ev = ev,1475 .evented = ev,
1483 .fiber = fiber,1476 .fiber = fiber,
1484 .start = start,1477 .start = start,
1485 .result_align = result_alignment,1478 .result_align = result_alignment,
...@@ -1498,18 +1491,11 @@ fn await(...@@ -1498,18 +1491,11 @@ fn await(
1498 result_alignment: Alignment,1491 result_alignment: Alignment,
1499) void {1492) void {
1500 const ev: *Evented = @ptrCast(@alignCast(userdata));1493 const ev: *Evented = @ptrCast(@alignCast(userdata));
1501 const fiber = Thread.current().currentFiber();1494 const awaiting: *Fiber = @ptrCast(@alignCast(future));
1502 const future_fiber: *Fiber = @ptrCast(@alignCast(future));1495 if (@atomicLoad(?*Fiber, &awaiting.link.awaiter, .acquire) != Fiber.finished)
1503 if (@atomicRmw(?*Fiber, &future_fiber.link.awaiter, .Xchg, fiber, .acq_rel)) |awaiter| {1496 ev.yield(null, .{ .await = awaiting });
1504 assert(awaiter == Fiber.finished);1497 @memcpy(result, awaiting.resultBytes(result_alignment));
1505 } else while (true) {1498 awaiting.destroy();
1506 ev.yield(null, .{ .await = 1 });
1507 const awaiter = @atomicLoad(?*Fiber, &future_fiber.link.awaiter, .acquire);
1508 if (awaiter == Fiber.finished) break;
1509 assert(awaiter == fiber); // spurious wakeup
1510 }
1511 @memcpy(result, future_fiber.resultBytes(result_alignment));
1512 future_fiber.destroy();
1513}1499}
15141500
1515fn cancel(1501fn cancel(
...@@ -1726,7 +1712,7 @@ const Group = struct {...@@ -1726,7 +1712,7 @@ const Group = struct {
1726 }1712 }
17271713
1728 const AsyncClosure = struct {1714 const AsyncClosure = struct {
1729 ev: *Evented,1715 evented: *Evented,
1730 group: Group,1716 group: Group,
1731 fiber: *Fiber,1717 fiber: *Fiber,
1732 start: *const fn (context: *const anyopaque) Io.Cancelable!void,1718 start: *const fn (context: *const anyopaque) Io.Cancelable!void,
...@@ -1765,19 +1751,16 @@ const Group = struct {...@@ -1765,19 +1751,16 @@ const Group = struct {
1765 closure: *Group.AsyncClosure,1751 closure: *Group.AsyncClosure,
1766 message: *const SwitchMessage,1752 message: *const SwitchMessage,
1767 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {1753 ) callconv(.withStackAlign(.c, @alignOf(Group.AsyncClosure))) noreturn {
1768 message.handle(closure.ev);1754 const ev = closure.evented;
1769 assert(closure.fiber.status.queue_next == null);
1770 const result = closure.start(closure.contextPointer());
1771 const ev = closure.ev;
1772 const group = closure.group;
1773 const fiber = closure.fiber;1755 const fiber = closure.fiber;
1774 const cancel_acknowledged = fiber.cancel_protection.acknowledged;1756 message.handle(ev);
1775 if (result) {1757 assert(fiber.status.queue_next == null);
1776 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`
1777 } else |err| switch (err) {1760 } else |err| switch (err) {
1778 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
1779 }1762 }
1780 ev.yield(group.removeFiber(ev, fiber), .destroy);1763 ev.yield(closure.group.removeFiber(ev, fiber), .destroy);
1781 unreachable; // switched to dead fiber1764 unreachable; // switched to dead fiber
1782 }1765 }
1783 };1766 };
...@@ -1845,7 +1828,6 @@ fn groupConcurrent(...@@ -1845,7 +1828,6 @@ fn groupConcurrent(
1845 },1828 },
1846 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),1829 else => |arch| @compileError("unimplemented architecture: " ++ @tagName(arch)),
1847 },1830 },
1848 .await_count = 0,
1849 .link = .{ .group = .{ .prev = null, .next = null } },1831 .link = .{ .group = .{ .prev = null, .next = null } },
1850 .status = .{ .queue_next = null },1832 .status = .{ .queue_next = null },
1851 .cancel_status = .unrequested,1833 .cancel_status = .unrequested,
...@@ -1863,7 +1845,7 @@ fn groupConcurrent(...@@ -1863,7 +1845,7 @@ fn groupConcurrent(
1863 },1845 },
1864 };1846 };
1865 closure.* = .{1847 closure.* = .{
1866 .ev = ev,1848 .evented = ev,
1867 .group = group,1849 .group = group,
1868 .fiber = fiber,1850 .fiber = fiber,
1869 .start = start,1851 .start = start,
...@@ -2173,7 +2155,7 @@ fn deviceIoControl(...@@ -2173,7 +2155,7 @@ fn deviceIoControl(
2173 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));
2174 switch (linux.errno(rc)) {2156 switch (linux.errno(rc)) {
2175 .SUCCESS => return @bitCast(@as(u32, @truncate(rc))),2157 .SUCCESS => return @bitCast(@as(u32, @truncate(rc))),
2176 .INTR => continue,2158 .INTR => {},
2177 else => |err| return -@as(i32, @intFromEnum(err)),2159 else => |err| return -@as(i32, @intFromEnum(err)),
2178 }2160 }
2179 }2161 }
...@@ -2571,7 +2553,7 @@ fn dirCreateDir(...@@ -2571,7 +2553,7 @@ fn dirCreateDir(
2571 ev.yield(null, .nothing);2553 ev.yield(null, .nothing);
2572 switch (cancel_region.errno()) {2554 switch (cancel_region.errno()) {
2573 .SUCCESS => return,2555 .SUCCESS => return,
2574 .INTR, .CANCELED => continue,2556 .INTR, .CANCELED => {},
2575 .ACCES => return error.AccessDenied,2557 .ACCES => return error.AccessDenied,
2576 .BADF => |err| return errnoBug(err), // File descriptor used after closed.2558 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2577 .PERM => return error.PermissionDenied,2559 .PERM => return error.PermissionDenied,
...@@ -2654,7 +2636,7 @@ fn filePathKind(ev: *Evented, dir: Dir, sub_path: []const u8) !File.Kind {...@@ -2654,7 +2636,7 @@ fn filePathKind(ev: *Evented, dir: Dir, sub_path: []const u8) !File.Kind {
2654 if (!statx_buf.mask.TYPE) return error.Unexpected;2636 if (!statx_buf.mask.TYPE) return error.Unexpected;
2655 return statxKind(statx_buf.mode);2637 return statxKind(statx_buf.mode);
2656 },2638 },
2657 .INTR, .CANCELED => continue,2639 .INTR, .CANCELED => {},
2658 .ACCES => |err| return errnoBug(err),2640 .ACCES => |err| return errnoBug(err),
2659 .BADF => |err| return errnoBug(err), // File descriptor used after closed.2641 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
2660 .FAULT => |err| return errnoBug(err),2642 .FAULT => |err| return errnoBug(err),
...@@ -2767,7 +2749,7 @@ fn dirAccess(...@@ -2767,7 +2749,7 @@ fn dirAccess(
2767 try sync.cancel_region.await(.nothing);2749 try sync.cancel_region.await(.nothing);
2768 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))) {
2769 .SUCCESS => return,2751 .SUCCESS => return,
2770 .INTR => continue,2752 .INTR => {},
2771 .ACCES => return error.AccessDenied,2753 .ACCES => return error.AccessDenied,
2772 .PERM => return error.PermissionDenied,2754 .PERM => return error.PermissionDenied,
2773 .ROFS => return error.ReadOnlyFileSystem,2755 .ROFS => return error.ReadOnlyFileSystem,
...@@ -2806,7 +2788,7 @@ fn dirCreateFile(...@@ -2806,7 +2788,7 @@ fn dirCreateFile(
2806 .EXCL = flags.exclusive,2788 .EXCL = flags.exclusive,
2807 .CLOEXEC = true,2789 .CLOEXEC = true,
2808 }, flags.permissions.toMode());2790 }, flags.permissions.toMode());
2809 errdefer ev.close(fd);2791 errdefer ev.closeAsync(fd);
28102792
2811 switch (flags.lock) {2793 switch (flags.lock) {
2812 .none => {},2794 .none => {},
...@@ -2994,7 +2976,7 @@ fn dirOpenFile(...@@ -2994,7 +2976,7 @@ fn dirOpenFile(
2994 .CLOEXEC = true,2976 .CLOEXEC = true,
2995 .PATH = flags.path_only,2977 .PATH = flags.path_only,
2996 }, 0);2978 }, 0);
2997 errdefer ev.close(fd);2979 errdefer ev.closeAsync(fd);
29982980
2999 if (!flags.allow_directory) {2981 if (!flags.allow_directory) {
3000 const is_dir = is_dir: {2982 const is_dir = is_dir: {
...@@ -3048,7 +3030,7 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read...@@ -3048,7 +3030,7 @@ fn dirRead(userdata: ?*anyopaque, dr: *Dir.Reader, buffer: []Dir.Entry) Dir.Read
3048 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);
3049 switch (linux.errno(rc)) {3031 switch (linux.errno(rc)) {
3050 .SUCCESS => break rc,3032 .SUCCESS => break rc,
3051 .INTR => continue,3033 .INTR => {},
3052 .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.
3053 .FAULT => |err| return errnoBug(err),3035 .FAULT => |err| return errnoBug(err),
3054 .NOTDIR => |err| return errnoBug(err),3036 .NOTDIR => |err| return errnoBug(err),
...@@ -3141,7 +3123,7 @@ fn dirRealPathFile(...@@ -3141,7 +3123,7 @@ fn dirRealPathFile(
3141 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.3123 error.FileLocksUnsupported => return errnoBug(.OPNOTSUPP), // Not asking for locks.
3142 else => |e| return e,3124 else => |e| return e,
3143 };3125 };
3144 defer ev.close(fd);3126 defer ev.closeAsync(fd);
3145 return ev.realPath(try maybe_sync.enterSync(ev), fd, out_buffer);3127 return ev.realPath(try maybe_sync.enterSync(ev), fd, out_buffer);
3146}3128}
31473129
...@@ -3174,7 +3156,7 @@ fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Dele...@@ -3174,7 +3156,7 @@ fn dirDeleteFile(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Dele
3174 ev.yield(null, .nothing);3156 ev.yield(null, .nothing);
3175 switch (cancel_region.errno()) {3157 switch (cancel_region.errno()) {
3176 .SUCCESS => return,3158 .SUCCESS => return,
3177 .INTR, .CANCELED => continue,3159 .INTR, .CANCELED => {},
3178 .PERM => return error.PermissionDenied,3160 .PERM => return error.PermissionDenied,
3179 .ACCES => return error.AccessDenied,3161 .ACCES => return error.AccessDenied,
3180 .BUSY => return error.FileBusy,3162 .BUSY => return error.FileBusy,
...@@ -3226,7 +3208,7 @@ fn dirDeleteDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Delet...@@ -3226,7 +3208,7 @@ fn dirDeleteDir(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8) Dir.Delet
3226 ev.yield(null, .nothing);3208 ev.yield(null, .nothing);
3227 switch (cancel_region.errno()) {3209 switch (cancel_region.errno()) {
3228 .SUCCESS => return,3210 .SUCCESS => return,
3229 .INTR, .CANCELED => continue,3211 .INTR, .CANCELED => {},
3230 .ACCES => return error.AccessDenied,3212 .ACCES => return error.AccessDenied,
3231 .PERM => return error.PermissionDenied,3213 .PERM => return error.PermissionDenied,
3232 .BUSY => return error.FileBusy,3214 .BUSY => return error.FileBusy,
...@@ -3342,7 +3324,7 @@ fn dirSymLink(...@@ -3342,7 +3324,7 @@ fn dirSymLink(
3342 ev.yield(null, .nothing);3324 ev.yield(null, .nothing);
3343 switch (cancel_region.errno()) {3325 switch (cancel_region.errno()) {
3344 .SUCCESS => return,3326 .SUCCESS => return,
3345 .INTR, .CANCELED => continue,3327 .INTR, .CANCELED => {},
3346 .FAULT => |err| return errnoBug(err),3328 .FAULT => |err| return errnoBug(err),
3347 .INVAL => |err| return errnoBug(err),3329 .INVAL => |err| return errnoBug(err),
3348 .ACCES => return error.AccessDenied,3330 .ACCES => return error.AccessDenied,
...@@ -3381,7 +3363,7 @@ fn dirReadLink(...@@ -3381,7 +3363,7 @@ fn dirReadLink(
3381 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);
3382 switch (linux.errno(rc)) {3364 switch (linux.errno(rc)) {
3383 .SUCCESS => return @bitCast(rc),3365 .SUCCESS => return @bitCast(rc),
3384 .INTR => continue,3366 .INTR => {},
3385 .ACCES => return error.AccessDenied,3367 .ACCES => return error.AccessDenied,
3386 .FAULT => |err| return errnoBug(err),3368 .FAULT => |err| return errnoBug(err),
3387 .INVAL => return error.NotLink,3369 .INVAL => return error.NotLink,
...@@ -3571,7 +3553,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {...@@ -3571,7 +3553,7 @@ fn fileLength(userdata: ?*anyopaque, file: File) File.LengthError!u64 {
3571 if (!statx_buf.mask.SIZE) return error.Unexpected;3553 if (!statx_buf.mask.SIZE) return error.Unexpected;
3572 return statx_buf.size;3554 return statx_buf.size;
3573 },3555 },
3574 .INTR, .CANCELED => continue,3556 .INTR, .CANCELED => {},
3575 .ACCES => |err| return errnoBug(err),3557 .ACCES => |err| return errnoBug(err),
3576 .BADF => |err| return errnoBug(err), // File descriptor used after closed.3558 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
3577 .FAULT => |err| return errnoBug(err),3559 .FAULT => |err| return errnoBug(err),
...@@ -3754,7 +3736,7 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {...@@ -3754,7 +3736,7 @@ fn fileSync(userdata: ?*anyopaque, file: File) File.SyncError!void {
3754 ev.yield(null, .nothing);3736 ev.yield(null, .nothing);
3755 switch (cancel_region.errno()) {3737 switch (cancel_region.errno()) {
3756 .SUCCESS => return,3738 .SUCCESS => return,
3757 .INTR, .CANCELED => continue,3739 .INTR, .CANCELED => {},
3758 .BADF => |err| return errnoBug(err),3740 .BADF => |err| return errnoBug(err),
3759 .INVAL => |err| return errnoBug(err),3741 .INVAL => |err| return errnoBug(err),
3760 .ROFS => |err| return errnoBug(err),3742 .ROFS => |err| return errnoBug(err),
...@@ -3776,7 +3758,7 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {...@@ -3776,7 +3758,7 @@ fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool {
3776 const rc = linux.ioctl(file.handle, linux.T.IOCGWINSZ, @intFromPtr(&wsz));3758 const rc = linux.ioctl(file.handle, linux.T.IOCGWINSZ, @intFromPtr(&wsz));
3777 switch (linux.errno(rc)) {3759 switch (linux.errno(rc)) {
3778 .SUCCESS => return true,3760 .SUCCESS => return true,
3779 .INTR => continue,3761 .INTR => {},
3780 else => return false,3762 else => return false,
3781 }3763 }
3782 }3764 }
...@@ -3812,7 +3794,7 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE...@@ -3812,7 +3794,7 @@ fn fileSetLength(userdata: ?*anyopaque, file: File, length: u64) File.SetLengthE
3812 ev.yield(null, .nothing);3794 ev.yield(null, .nothing);
3813 switch (cancel_region.errno()) {3795 switch (cancel_region.errno()) {
3814 .SUCCESS => return,3796 .SUCCESS => return,
3815 .INTR, .CANCELED => continue,3797 .INTR, .CANCELED => {},
3816 .FBIG => return error.FileTooBig,3798 .FBIG => return error.FileTooBig,
3817 .IO => return error.InputOutput,3799 .IO => return error.InputOutput,
3818 .PERM => return error.PermissionDenied,3800 .PERM => return error.PermissionDenied,
...@@ -3994,7 +3976,7 @@ fn fileMemoryMapCreate(...@@ -3994,7 +3976,7 @@ fn fileMemoryMapCreate(
3994 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);
3995 switch (linux.errno(rc)) {3977 switch (linux.errno(rc)) {
3996 .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],
3997 .INTR => continue,3979 .INTR => {},
3998 .ACCES => return error.AccessDenied,3980 .ACCES => return error.AccessDenied,
3999 .AGAIN => return error.LockedMemoryLimitExceeded,3981 .AGAIN => return error.LockedMemoryLimitExceeded,
4000 .MFILE => return error.ProcessFdQuotaExceeded,3982 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -4054,7 +4036,7 @@ fn fileMemoryMapSetLength(...@@ -4054,7 +4036,7 @@ fn fileMemoryMapSetLength(
4054 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);
4055 switch (linux.errno(rc)) {4037 switch (linux.errno(rc)) {
4056 .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],
4057 .INTR => continue,4039 .INTR => {},
4058 .AGAIN => return error.LockedMemoryLimitExceeded,4040 .AGAIN => return error.LockedMemoryLimitExceeded,
4059 .NOMEM => return error.OutOfMemory,4041 .NOMEM => return error.OutOfMemory,
4060 .INVAL => |err| return errnoBug(err),4042 .INVAL => |err| return errnoBug(err),
...@@ -4163,7 +4145,7 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr...@@ -4163,7 +4145,7 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr
4163 try sync.cancel_region.await(.nothing);4145 try sync.cancel_region.await(.nothing);
4164 switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {4146 switch (linux.errno(linux.getcwd(buffer.ptr, buffer.len))) {
4165 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,4147 .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?,
4166 .INTR => continue,4148 .INTR => {},
4167 .NOENT => return error.CurrentDirUnlinked,4149 .NOENT => return error.CurrentDirUnlinked,
4168 .RANGE => return error.NameTooLong,4150 .RANGE => return error.NameTooLong,
4169 .FAULT => |err| return errnoBug(err),4151 .FAULT => |err| return errnoBug(err),
...@@ -4178,7 +4160,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr...@@ -4178,7 +4160,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
4178 if (dir.handle == linux.AT.FDCWD) return;4160 if (dir.handle == linux.AT.FDCWD) return;
4179 var sync: CancelRegion.Sync = try .init(ev);4161 var sync: CancelRegion.Sync = try .init(ev);
4180 defer sync.deinit(ev);4162 defer sync.deinit(ev);
4181 return ev.fchdir(&sync, dir.handle);4163 return fchdir(&sync, dir.handle);
4182}4164}
41834165
4184fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {4166fn processSetCurrentPath(userdata: ?*anyopaque, dir_path: []const u8) ChdirError!void {
...@@ -4215,7 +4197,7 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces...@@ -4215,7 +4197,7 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces
42154197
4216 var sync: CancelRegion.Sync = try .init(ev);4198 var sync: CancelRegion.Sync = try .init(ev);
4217 defer sync.deinit(ev);4199 defer sync.deinit(ev);
4218 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);
4219}4201}
42204202
4221fn processReplacePath(4203fn processReplacePath(
...@@ -4235,7 +4217,7 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp...@@ -4235,7 +4217,7 @@ fn processSpawn(userdata: ?*anyopaque, options: process.SpawnOptions) process.Sp
4235 const spawned = try ev.spawn(options);4217 const spawned = try ev.spawn(options);
4236 var cancel_region: CancelRegion = .initBlocked();4218 var cancel_region: CancelRegion = .initBlocked();
4237 defer cancel_region.deinit();4219 defer cancel_region.deinit();
4238 defer ev.close(spawned.err_fd);4220 defer ev.closeAsync(spawned.err_fd);
42394221
4240 // 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`.
4241 var child_err: ForkBailError = undefined;4223 var child_err: ForkBailError = undefined;
...@@ -4377,8 +4359,9 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4377,8 +4359,9 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
43774359
4378 if (pid_result == 0) {4360 if (pid_result == 0) {
4379 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`.
4380 var sync: CancelRegion.Sync = .{ .cancel_region = .initBlocked() };4363 var sync: CancelRegion.Sync = .{ .cancel_region = .initBlocked() };
4381 const err = ev.setUpChild(&sync, .{4364 const err = setUpChild(&sync, .{
4382 .stdin_pipe = stdin_pipe[0],4365 .stdin_pipe = stdin_pipe[0],
4383 .stdout_pipe = stdout_pipe[1],4366 .stdout_pipe = stdout_pipe[1],
4384 .stderr_pipe = stderr_pipe[1],4367 .stderr_pipe = stderr_pipe[1],
...@@ -4389,7 +4372,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4389,7 +4372,7 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
4389 .PATH = PATH,4372 .PATH = PATH,
4390 .spawn = options,4373 .spawn = options,
4391 });4374 });
4392 ev.writeAll(&sync.cancel_region, err_pipe[1], @ptrCast(&err)) catch {};4375 writeAllSync(&sync, err_pipe[1], @ptrCast(&err)) catch {};
4393 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;
4394 exit(1);4377 exit(1);
4395 }4378 }
...@@ -4397,13 +4380,13 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned...@@ -4397,13 +4380,13 @@ fn spawn(ev: *Evented, options: process.SpawnOptions) process.SpawnError!Spawned
4397 const pid: pid_t = @intCast(pid_result); // We are the parent.4380 const pid: pid_t = @intCast(pid_result); // We are the parent.
4398 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
43994382
4400 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
44014384
4402 if (options.stdin == .pipe) ev.close(stdin_pipe[0]);4385 if (options.stdin == .pipe) ev.closeAsync(stdin_pipe[0]);
4403 if (options.stdout == .pipe) ev.close(stdout_pipe[1]);4386 if (options.stdout == .pipe) ev.closeAsync(stdout_pipe[1]);
4404 if (options.stderr == .pipe) ev.close(stderr_pipe[1]);4387 if (options.stderr == .pipe) ev.closeAsync(stderr_pipe[1]);
44054388
4406 if (prog_pipe[1] != -1) ev.close(prog_pipe[1]);4389 if (prog_pipe[1] != -1) ev.closeAsync(prog_pipe[1]);
44074390
4408 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 } });
44094392
...@@ -4440,14 +4423,14 @@ pub fn pipe2(flags: linux.O) PipeError![2]fd_t {...@@ -4440,14 +4423,14 @@ pub fn pipe2(flags: linux.O) PipeError![2]fd_t {
4440 }4423 }
4441}4424}
4442fn destroyPipe(ev: *Evented, pipe: [2]fd_t) void {4425fn destroyPipe(ev: *Evented, pipe: [2]fd_t) void {
4443 if (pipe[0] != -1) ev.close(pipe[0]);4426 if (pipe[0] != -1) ev.closeAsync(pipe[0]);
4444 if (pipe[0] != pipe[1]) ev.close(pipe[1]);4427 if (pipe[0] != pipe[1]) ev.closeAsync(pipe[1]);
4445}4428}
44464429
4447/// Errors that can occur between fork() and execv()4430/// Errors that can occur between fork() and execv()
4448const ForkBailError = process.SetCurrentDirError || ChdirError ||4431const ForkBailError = process.SetCurrentDirError || ChdirError ||
4449 process.SpawnError || process.ReplaceError;4432 process.SpawnError || process.ReplaceError;
4450fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {4433fn setUpChild(sync: *CancelRegion.Sync, options: struct {
4451 stdin_pipe: fd_t,4434 stdin_pipe: fd_t,
4452 stdout_pipe: fd_t,4435 stdout_pipe: fd_t,
4453 stderr_pipe: fd_t,4436 stderr_pipe: fd_t,
...@@ -4458,21 +4441,21 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4458,21 +4441,21 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
4458 PATH: []const u8,4441 PATH: []const u8,
4459 spawn: process.SpawnOptions,4442 spawn: process.SpawnOptions,
4460}) ForkBailError {4443}) ForkBailError {
4461 try ev.setUpChildIo(4444 try setUpChildIo(
4462 sync,4445 sync,
4463 options.spawn.stdin,4446 options.spawn.stdin,
4464 options.stdin_pipe,4447 options.stdin_pipe,
4465 linux.STDIN_FILENO,4448 linux.STDIN_FILENO,
4466 options.dev_null_fd,4449 options.dev_null_fd,
4467 );4450 );
4468 try ev.setUpChildIo(4451 try setUpChildIo(
4469 sync,4452 sync,
4470 options.spawn.stdout,4453 options.spawn.stdout,
4471 options.stdout_pipe,4454 options.stdout_pipe,
4472 linux.STDOUT_FILENO,4455 linux.STDOUT_FILENO,
4473 options.dev_null_fd,4456 options.dev_null_fd,
4474 );4457 );
4475 try ev.setUpChildIo(4458 try setUpChildIo(
4476 sync,4459 sync,
4477 options.spawn.stderr,4460 options.spawn.stderr,
4478 options.stderr_pipe,4461 options.stderr_pipe,
...@@ -4482,17 +4465,17 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4482,17 +4465,17 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
44824465
4483 switch (options.spawn.cwd) {4466 switch (options.spawn.cwd) {
4484 .inherit => {},4467 .inherit => {},
4485 .dir => |cwd_dir| try ev.fchdir(sync, cwd_dir.handle),4468 .dir => |cwd_dir| try fchdir(sync, cwd_dir.handle),
4486 .path => |cwd_path| {4469 .path => |cwd_path| {
4487 var cwd_path_buffer: [PATH_MAX]u8 = undefined;4470 var cwd_path_buffer: [PATH_MAX]u8 = undefined;
4488 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);4471 const cwd_path_posix = try pathToPosix(cwd_path, &cwd_path_buffer);
4489 try ev.chdir(sync, cwd_path_posix);4472 try chdir(sync, cwd_path_posix);
4490 },4473 },
4491 }4474 }
44924475
4493 // Must happen after fchdir above, the cwd file descriptor might be4476 // Must happen after fchdir above, the cwd file descriptor might be
4494 // equal to prog_fileno and be clobbered by this dup2 call.4477 // equal to prog_fileno and be clobbered by this dup2 call.
4495 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);
44964479
4497 if (options.spawn.gid) |gid| {4480 if (options.spawn.gid) |gid| {
4498 switch (linux.errno(linux.setregid(gid, gid))) {4481 switch (linux.errno(linux.setregid(gid, gid))) {
...@@ -4532,7 +4515,7 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4532,7 +4515,7 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
4532 }4515 }
4533 }4516 }
45344517
4535 return ev.execv(4518 return execv(
4536 sync,4519 sync,
4537 options.spawn.expand_arg0,4520 options.spawn.expand_arg0,
4538 options.argv_buf.ptr[0].?,4521 options.argv_buf.ptr[0].?,
...@@ -4543,7 +4526,6 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {...@@ -4543,7 +4526,6 @@ fn setUpChild(ev: *Evented, sync: *CancelRegion.Sync, options: struct {
4543}4526}
45444527
4545fn setUpChildIo(4528fn setUpChildIo(
4546 ev: *Evented,
4547 sync: *CancelRegion.Sync,4529 sync: *CancelRegion.Sync,
4548 stdio: process.SpawnOptions.StdIo,4530 stdio: process.SpawnOptions.StdIo,
4549 pipe_fd: fd_t,4531 pipe_fd: fd_t,
...@@ -4551,13 +4533,13 @@ fn setUpChildIo(...@@ -4551,13 +4533,13 @@ fn setUpChildIo(
4551 dev_null_fd: fd_t,4533 dev_null_fd: fd_t,
4552) !void {4534) !void {
4553 switch (stdio) {4535 switch (stdio) {
4554 .pipe => try ev.dup2(sync, pipe_fd, std_fileno),4536 .pipe => try dup2(sync, pipe_fd, std_fileno),
4555 .close => _ = linux.close(std_fileno),4537 .close => _ = linux.close(std_fileno),
4556 .inherit => {},4538 .inherit => {},
4557 .ignore => try ev.dup2(sync, dev_null_fd, std_fileno),4539 .ignore => try dup2(sync, dev_null_fd, std_fileno),
4558 .file => |file| {4540 .file => |file| {
4559 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");
4560 try ev.dup2(sync, file.handle, std_fileno);4542 try dup2(sync, file.handle, std_fileno);
4561 },4543 },
4562 }4544 }
4563}4545}
...@@ -4566,13 +4548,12 @@ pub const DupError = error{...@@ -4566,13 +4548,12 @@ pub const DupError = error{
4566 ProcessFdQuotaExceeded,4548 ProcessFdQuotaExceeded,
4567 SystemResources,4549 SystemResources,
4568} || Io.UnexpectedError || Io.Cancelable;4550} || Io.UnexpectedError || Io.Cancelable;
4569pub 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 {
4570 _ = ev;
4571 while (true) {4552 while (true) {
4572 try sync.cancel_region.await(.nothing);4553 try sync.cancel_region.await(.nothing);
4573 switch (linux.errno(linux.dup2(old_fd, new_fd))) {4554 switch (linux.errno(linux.dup2(old_fd, new_fd))) {
4574 .SUCCESS => {},4555 .SUCCESS => {},
4575 .BUSY, .INTR => continue,4556 .BUSY, .INTR => {},
4576 .INVAL => |err| return errnoBug(err), // invalid parameters4557 .INVAL => |err| return errnoBug(err), // invalid parameters
4577 .BADF => |err| return errnoBug(err), // use after free4558 .BADF => |err| return errnoBug(err), // use after free
4578 .MFILE => return error.ProcessFdQuotaExceeded,4559 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -4583,7 +4564,6 @@ pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t)...@@ -4583,7 +4564,6 @@ pub fn dup2(ev: *Evented, sync: *CancelRegion.Sync, old_fd: fd_t, new_fd: fd_t)
4583}4564}
45844565
4585fn execv(4566fn execv(
4586 ev: *Evented,
4587 sync: *CancelRegion.Sync,4567 sync: *CancelRegion.Sync,
4588 arg0_expand: process.ArgExpansion,4568 arg0_expand: process.ArgExpansion,
4589 file: [*:0]const u8,4569 file: [*:0]const u8,
...@@ -4592,7 +4572,8 @@ fn execv(...@@ -4592,7 +4572,8 @@ fn execv(
4592 PATH: []const u8,4572 PATH: []const u8,
4593) process.ReplaceError {4573) process.ReplaceError {
4594 const file_slice = std.mem.sliceTo(file, 0);4574 const file_slice = std.mem.sliceTo(file, 0);
4595 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);
45964577
4597 // 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
4598 // directly to the operating system in posixExecvPath.4579 // directly to the operating system in posixExecvPath.
...@@ -4620,7 +4601,7 @@ fn execv(...@@ -4620,7 +4601,7 @@ fn execv(
4620 .expand => child_argv[0] = full_path,4601 .expand => child_argv[0] = full_path,
4621 .no_expand => {},4602 .no_expand => {},
4622 }4603 }
4623 err = ev.execvPath(sync, full_path, child_argv, env_block);4604 err = execvPath(sync, full_path, child_argv, env_block);
4624 switch (err) {4605 switch (err) {
4625 error.AccessDenied => seen_eacces = true,4606 error.AccessDenied => seen_eacces = true,
4626 error.FileNotFound, error.NotDir => {},4607 error.FileNotFound, error.NotDir => {},
...@@ -4632,13 +4613,11 @@ fn execv(...@@ -4632,13 +4613,11 @@ fn execv(
4632}4613}
4633/// This function ignores PATH environment variable.4614/// This function ignores PATH environment variable.
4634pub fn execvPath(4615pub fn execvPath(
4635 ev: *Evented,
4636 sync: *CancelRegion.Sync,4616 sync: *CancelRegion.Sync,
4637 path: [*:0]const u8,4617 path: [*:0]const u8,
4638 child_argv: [*:null]const ?[*:0]const u8,4618 child_argv: [*:null]const ?[*:0]const u8,
4639 env_block: process.Environ.PosixBlock,4619 env_block: process.Environ.PosixBlock,
4640) process.ReplaceError {4620) process.ReplaceError {
4641 _ = ev;
4642 try sync.cancel_region.await(.nothing);4621 try sync.cancel_region.await(.nothing);
4643 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))) {
4644 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.4623 .FAULT => |err| return errnoBug(err), // Bad pointer parameter.
...@@ -4709,7 +4688,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr...@@ -4709,7 +4688,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
4709 child.resource_usage_statistics.rusage = rusage;4688 child.resource_usage_statistics.rusage = rusage;
4710 break;4689 break;
4711 },4690 },
4712 .INTR, .CANCELED => continue,4691 .INTR, .CANCELED => {},
4713 .CHILD => |err| return errnoBug(err), // Double-free.4692 .CHILD => |err| return errnoBug(err), // Double-free.
4714 else => |err| return unexpectedErrno(err),4693 else => |err| return unexpectedErrno(err),
4715 }4694 }
...@@ -4724,7 +4703,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr...@@ -4724,7 +4703,7 @@ fn childWait(userdata: ?*anyopaque, child: *process.Child) process.Child.WaitErr
4724 _, .CONTINUED => .{ .unknown = status },4703 _, .CONTINUED => .{ .unknown = status },
4725 };4704 };
4726 },4705 },
4727 .INTR, .CANCELED => continue,4706 .INTR, .CANCELED => {},
4728 .CHILD => |err| return errnoBug(err), // Double-free.4707 .CHILD => |err| return errnoBug(err), // Double-free.
4729 else => |err| return unexpectedErrno(err),4708 else => |err| return unexpectedErrno(err),
4730 }4709 }
...@@ -4741,7 +4720,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4741,7 +4720,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4741 const pid = child.id.?;4720 const pid = child.id.?;
4742 while (true) switch (linux.errno(linux.kill(pid, .TERM))) {4721 while (true) switch (linux.errno(linux.kill(pid, .TERM))) {
4743 .SUCCESS => break,4722 .SUCCESS => break,
4744 .INTR => continue,4723 .INTR => {},
4745 .PERM => return,4724 .PERM => return,
4746 .INVAL => |err| return errnoBug(err) catch {},4725 .INVAL => |err| return errnoBug(err) catch {},
4747 .SRCH => |err| return errnoBug(err) catch {},4726 .SRCH => |err| return errnoBug(err) catch {},
...@@ -4773,7 +4752,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4773,7 +4752,7 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
4773 ev.yield(null, .nothing);4752 ev.yield(null, .nothing);
4774 switch (maybe_sync.cancel_region.errno()) {4753 switch (maybe_sync.cancel_region.errno()) {
4775 .SUCCESS => return,4754 .SUCCESS => return,
4776 .INTR, .CANCELED => continue,4755 .INTR, .CANCELED => {},
4777 .CHILD => |err| return errnoBug(err) catch {}, // Double-free.4756 .CHILD => |err| return errnoBug(err) catch {}, // Double-free.
4778 else => |err| return unexpectedErrno(err) catch {},4757 else => |err| return unexpectedErrno(err) catch {},
4779 }4758 }
...@@ -4782,15 +4761,15 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {...@@ -4782,15 +4761,15 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void {
47824761
4783fn childCleanup(ev: *Evented, child: *process.Child) void {4762fn childCleanup(ev: *Evented, child: *process.Child) void {
4784 if (child.stdin) |*stdin| {4763 if (child.stdin) |*stdin| {
4785 ev.close(stdin.handle);4764 ev.closeAsync(stdin.handle);
4786 child.stdin = null;4765 child.stdin = null;
4787 }4766 }
4788 if (child.stdout) |*stdout| {4767 if (child.stdout) |*stdout| {
4789 ev.close(stdout.handle);4768 ev.closeAsync(stdout.handle);
4790 child.stdout = null;4769 child.stdout = null;
4791 }4770 }
4792 if (child.stderr) |*stderr| {4771 if (child.stderr) |*stderr| {
4793 ev.close(stderr.handle);4772 ev.closeAsync(stderr.handle);
4794 child.stderr = null;4773 child.stderr = null;
4795 }4774 }
4796 child.id = null;4775 child.id = null;
...@@ -4896,14 +4875,10 @@ fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {...@@ -4896,14 +4875,10 @@ fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void {
4896 .resv = 0,4875 .resv = 0,
4897 };4876 };
4898 ev.yield(null, .nothing);4877 ev.yield(null, .nothing);
4899 switch (cancel_region.errno()) {4878 // Handles SUCCESS as well as clock not available and unexpected
4900 // Handles SUCCESS as well as clock not available and unexpected4879 // errors. The user had a chance to check clock resolution before
4901 // errors. The user had a chance to check clock resolution before4880 // getting here, which would have reported 0, making this a legal
4902 // getting here, which would have reported 0, making this a legal4881 // amount of time to sleep.
4903 // amount of time to sleep.
4904 else => return,
4905 .INTR, .CANCELED => return error.Canceled,
4906 }
4907}4882}
49084883
4909fn random(userdata: ?*anyopaque, buffer: []u8) void {4884fn random(userdata: ?*anyopaque, buffer: []u8) void {
...@@ -4980,7 +4955,7 @@ fn netBindIp(...@@ -4980,7 +4955,7 @@ fn netBindIp(
4980 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };4955 var maybe_sync: CancelRegion.Sync.Maybe = .{ .cancel_region = .init() };
4981 defer maybe_sync.deinit(ev);4956 defer maybe_sync.deinit(ev);
4982 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);
4983 errdefer ev.close(socket_fd);4958 errdefer ev.closeAsync(socket_fd);
4984 var storage: PosixAddress = undefined;4959 var storage: PosixAddress = undefined;
4985 var addr_len = addressToPosix(address, &storage);4960 var addr_len = addressToPosix(address, &storage);
4986 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);
...@@ -5147,23 +5122,19 @@ fn netReceive(...@@ -5147,23 +5122,19 @@ fn netReceive(
5147 .data = data,5122 .data = data,
5148 .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,
5149 .flags = .{5124 .flags = .{
5150 .eor = (msg.flags & linux.MSG.EOR) != 0,5125 .eor = msg.flags & linux.MSG.EOR != 0,
5151 .trunc = (msg.flags & linux.MSG.TRUNC) != 0,5126 .trunc = msg.flags & linux.MSG.TRUNC != 0,
5152 .ctrunc = (msg.flags & linux.MSG.CTRUNC) != 0,5127 .ctrunc = msg.flags & linux.MSG.CTRUNC != 0,
5153 .oob = (msg.flags & linux.MSG.OOB) != 0,5128 .oob = msg.flags & linux.MSG.OOB != 0,
5154 .errqueue = if (@hasDecl(linux.MSG, "ERRQUEUE")) (msg.flags & linux.MSG.ERRQUEUE) != 0 else false,5129 .errqueue = msg.flags & linux.MSG.ERRQUEUE != 0,
5155 },5130 },
5156 };5131 };
5157 message_i += 1;5132 message_i += 1;
5158 continue;5133 continue;
5159 },5134 },
5160 .AGAIN => unreachable,5135 .AGAIN => unreachable,
5161 .INTR, .CANCELED => {5136 .INTR, .CANCELED => if (deadline) |d| if (now(ev, d.clock).nanoseconds >= d.raw.nanoseconds)
5162 if (deadline) |d| {5137 return .{ error.Timeout, message_i },
5163 if (now(ev, d.clock).nanoseconds >= d.raw.nanoseconds) return .{ error.Timeout, message_i };
5164 }
5165 continue;
5166 },
51675138
5168 .BADF => |err| return .{ errnoBug(err), message_i },5139 .BADF => |err| return .{ errnoBug(err), message_i },
5169 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },5140 .NFILE => return .{ error.SystemFdQuotaExceeded, message_i },
...@@ -5266,7 +5237,7 @@ fn netShutdown(...@@ -5266,7 +5237,7 @@ fn netShutdown(
5266 ev.yield(null, .nothing);5237 ev.yield(null, .nothing);
5267 switch (cancel_region.errno()) {5238 switch (cancel_region.errno()) {
5268 .SUCCESS => return,5239 .SUCCESS => return,
5269 .INTR, .CANCELED => continue,5240 .INTR, .CANCELED => {},
5270 .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),5241 .BADF, .NOTSOCK, .INVAL => |err| return errnoBug(err),
5271 .NOTCONN => return error.SocketUnconnected,5242 .NOTCONN => return error.SocketUnconnected,
5272 .NOBUFS => return error.SystemResources,5243 .NOBUFS => return error.SystemResources,
...@@ -5336,7 +5307,7 @@ fn bind(...@@ -5336,7 +5307,7 @@ fn bind(
5336 ev.yield(null, .nothing);5307 ev.yield(null, .nothing);
5337 switch (cancel_region.errno()) {5308 switch (cancel_region.errno()) {
5338 .SUCCESS => return,5309 .SUCCESS => return,
5339 .INTR, .CANCELED => continue,5310 .INTR, .CANCELED => {},
5340 .ADDRINUSE => return error.AddressInUse,5311 .ADDRINUSE => return error.AddressInUse,
5341 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5312 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5342 .INVAL => |err| return errnoBug(err), // invalid parameters5313 .INVAL => |err| return errnoBug(err), // invalid parameters
...@@ -5350,13 +5321,12 @@ fn bind(...@@ -5350,13 +5321,12 @@ fn bind(
5350 }5321 }
5351}5322}
53525323
5353fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {5324fn chdir(sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError!void {
5354 _ = ev;
5355 while (true) {5325 while (true) {
5356 try sync.cancel_region.await(.nothing);5326 try sync.cancel_region.await(.nothing);
5357 switch (linux.errno(linux.chdir(path))) {5327 switch (linux.errno(linux.chdir(path))) {
5358 .SUCCESS => return,5328 .SUCCESS => return,
5359 .INTR => continue,5329 .INTR => {},
5360 .ACCES => return error.AccessDenied,5330 .ACCES => return error.AccessDenied,
5361 .IO => return error.FileSystem,5331 .IO => return error.FileSystem,
5362 .LOOP => return error.SymLinkLoop,5332 .LOOP => return error.SymLinkLoop,
...@@ -5372,6 +5342,36 @@ fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError...@@ -5372,6 +5342,36 @@ fn chdir(ev: *Evented, sync: *CancelRegion.Sync, path: [*:0]const u8) ChdirError
5372}5342}
53735343
5374fn 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 {
5375 _ = ev;5375 _ = ev;
5376 const thread: *Thread = .current();5376 const thread: *Thread = .current();
5377 thread.enqueue().* = .{5377 thread.enqueue().* = .{
...@@ -5392,14 +5392,13 @@ fn close(ev: *Evented, fd: fd_t) void {...@@ -5392,14 +5392,13 @@ fn close(ev: *Evented, fd: fd_t) void {
5392 };5392 };
5393}5393}
53945394
5395fn fchdir(ev: *Evented, sync: *CancelRegion.Sync, dir: fd_t) process.SetCurrentDirError!void {5395fn fchdir(sync: *CancelRegion.Sync, dir: fd_t) process.SetCurrentDirError!void {
5396 _ = ev;
5397 if (dir == linux.AT.FDCWD) return;5396 if (dir == linux.AT.FDCWD) return;
5398 while (true) {5397 while (true) {
5399 try sync.cancel_region.await(.nothing);5398 try sync.cancel_region.await(.nothing);
5400 switch (linux.errno(linux.fchdir(dir))) {5399 switch (linux.errno(linux.fchdir(dir))) {
5401 .SUCCESS => return,5400 .SUCCESS => return,
5402 .INTR => continue,5401 .INTR => {},
5403 .ACCES => return error.AccessDenied,5402 .ACCES => return error.AccessDenied,
5404 .NOTDIR => return error.NotDir,5403 .NOTDIR => return error.NotDir,
5405 .IO => return error.FileSystem,5404 .IO => return error.FileSystem,
...@@ -5422,7 +5421,7 @@ fn fchmodat(...@@ -5422,7 +5421,7 @@ fn fchmodat(
5422 try sync.cancel_region.await(.nothing);5421 try sync.cancel_region.await(.nothing);
5423 switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {5422 switch (linux.errno(linux.fchmodat2(dir, path, mode, flags))) {
5424 .SUCCESS => return,5423 .SUCCESS => return,
5425 .INTR => continue,5424 .INTR => {},
5426 .BADF => |err| return errnoBug(err),5425 .BADF => |err| return errnoBug(err),
5427 .FAULT => |err| return errnoBug(err),5426 .FAULT => |err| return errnoBug(err),
5428 .INVAL => |err| return errnoBug(err),5427 .INVAL => |err| return errnoBug(err),
...@@ -5454,7 +5453,7 @@ fn fchownat(...@@ -5454,7 +5453,7 @@ fn fchownat(
5454 try sync.cancel_region.await(.nothing);5453 try sync.cancel_region.await(.nothing);
5455 switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {5454 switch (linux.errno(linux.fchownat(dir, path, owner, group, flags))) {
5456 .SUCCESS => return,5455 .SUCCESS => return,
5457 .INTR => continue,5456 .INTR => {},
5458 .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`
5459 .FAULT => |err| return errnoBug(err),5458 .FAULT => |err| return errnoBug(err),
5460 .INVAL => |err| return errnoBug(err),5459 .INVAL => |err| return errnoBug(err),
...@@ -5486,7 +5485,7 @@ fn flock(...@@ -5486,7 +5485,7 @@ fn flock(
5486 .exclusive => LOCK.EX,5485 .exclusive => LOCK.EX,
5487 })))) {5486 })))) {
5488 .SUCCESS => return,5487 .SUCCESS => return,
5489 .INTR => continue,5488 .INTR => {},
5490 .BADF => |err| return errnoBug(err),5489 .BADF => |err| return errnoBug(err),
5491 .INVAL => |err| return errnoBug(err), // invalid parameters5490 .INVAL => |err| return errnoBug(err), // invalid parameters
5492 .NOLCK => return error.SystemResources,5491 .NOLCK => return error.SystemResources,
...@@ -5536,7 +5535,7 @@ fn getsockname(...@@ -5536,7 +5535,7 @@ fn getsockname(
5536 try sync.cancel_region.await(.nothing);5535 try sync.cancel_region.await(.nothing);
5537 switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {5536 switch (linux.errno(linux.getsockname(socket_fd, addr, addr_len))) {
5538 .SUCCESS => return,5537 .SUCCESS => return,
5539 .INTR => continue,5538 .INTR => {},
5540 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5539 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5541 .FAULT => |err| return errnoBug(err),5540 .FAULT => |err| return errnoBug(err),
5542 .INVAL => |err| return errnoBug(err), // invalid parameters5541 .INVAL => |err| return errnoBug(err), // invalid parameters
...@@ -5577,7 +5576,7 @@ fn linkat(...@@ -5577,7 +5576,7 @@ fn linkat(
5577 ev.yield(null, .nothing);5576 ev.yield(null, .nothing);
5578 switch (cancel_region.errno()) {5577 switch (cancel_region.errno()) {
5579 .SUCCESS => return,5578 .SUCCESS => return,
5580 .INTR, .CANCELED => continue,5579 .INTR, .CANCELED => {},
5581 .ACCES => return error.AccessDenied,5580 .ACCES => return error.AccessDenied,
5582 .DQUOT => return error.DiskQuota,5581 .DQUOT => return error.DiskQuota,
5583 .EXIST => return error.PathAlreadyExists,5582 .EXIST => return error.PathAlreadyExists,
...@@ -5617,7 +5616,7 @@ fn lseek(...@@ -5617,7 +5616,7 @@ fn lseek(
5617 8 => linux.lseek(fd, @bitCast(offset), whence),5616 8 => linux.lseek(fd, @bitCast(offset), whence),
5618 })) {5617 })) {
5619 .SUCCESS => return,5618 .SUCCESS => return,
5620 .INTR => continue,5619 .INTR => {},
5621 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5620 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5622 .INVAL => return error.Unseekable,5621 .INVAL => return error.Unseekable,
5623 .OVERFLOW => return error.Unseekable,5622 .OVERFLOW => return error.Unseekable,
...@@ -5660,7 +5659,7 @@ fn openat(...@@ -5660,7 +5659,7 @@ fn openat(
5660 const completion = cancel_region.completion();5659 const completion = cancel_region.completion();
5661 switch (completion.errno()) {5660 switch (completion.errno()) {
5662 .SUCCESS => return completion.result,5661 .SUCCESS => return completion.result,
5663 .INTR, .CANCELED => continue,5662 .INTR, .CANCELED => {},
5664 .FAULT => |err| return errnoBug(err),5663 .FAULT => |err| return errnoBug(err),
5665 .INVAL => return error.BadPathName,5664 .INVAL => return error.BadPathName,
5666 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5665 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
...@@ -5722,7 +5721,7 @@ fn preadv(...@@ -5722,7 +5721,7 @@ fn preadv(
5722 const completion = cancel_region.completion();5721 const completion = cancel_region.completion();
5723 switch (completion.errno()) {5722 switch (completion.errno()) {
5724 .SUCCESS => return @as(u32, @bitCast(completion.result)),5723 .SUCCESS => return @as(u32, @bitCast(completion.result)),
5725 .INTR, .CANCELED => continue,5724 .INTR, .CANCELED => {},
5726 .INVAL => |err| return errnoBug(err),5725 .INVAL => |err| return errnoBug(err),
5727 .FAULT => |err| return errnoBug(err),5726 .FAULT => |err| return errnoBug(err),
5728 .AGAIN => return error.WouldBlock,5727 .AGAIN => return error.WouldBlock,
...@@ -5769,7 +5768,7 @@ fn pwritev(...@@ -5769,7 +5768,7 @@ fn pwritev(
5769 const completion = cancel_region.completion();5768 const completion = cancel_region.completion();
5770 switch (completion.errno()) {5769 switch (completion.errno()) {
5771 .SUCCESS => return @as(u32, @bitCast(completion.result)),5770 .SUCCESS => return @as(u32, @bitCast(completion.result)),
5772 .INTR, .CANCELED => continue,5771 .INTR, .CANCELED => {},
5773 .INVAL => |err| return errnoBug(err),5772 .INVAL => |err| return errnoBug(err),
5774 .FAULT => |err| return errnoBug(err),5773 .FAULT => |err| return errnoBug(err),
5775 .AGAIN => return error.WouldBlock,5774 .AGAIN => return error.WouldBlock,
...@@ -5819,7 +5818,7 @@ fn realPath(...@@ -5819,7 +5818,7 @@ fn realPath(
5819 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);
5820 switch (linux.errno(rc)) {5819 switch (linux.errno(rc)) {
5821 .SUCCESS => return rc,5820 .SUCCESS => return rc,
5822 .INTR => continue,5821 .INTR => {},
5823 .ACCES => return error.AccessDenied,5822 .ACCES => return error.AccessDenied,
5824 .FAULT => |err| return errnoBug(err),5823 .FAULT => |err| return errnoBug(err),
5825 .IO => return error.FileSystem,5824 .IO => return error.FileSystem,
...@@ -5864,7 +5863,7 @@ fn renameat(...@@ -5864,7 +5863,7 @@ fn renameat(
5864 ev.yield(null, .nothing);5863 ev.yield(null, .nothing);
5865 switch (cancel_region.errno()) {5864 switch (cancel_region.errno()) {
5866 .SUCCESS => return,5865 .SUCCESS => return,
5867 .INTR, .CANCELED => continue,5866 .INTR, .CANCELED => {},
5868 .ACCES => return error.AccessDenied,5867 .ACCES => return error.AccessDenied,
5869 .PERM => return error.PermissionDenied,5868 .PERM => return error.PermissionDenied,
5870 .BUSY => return error.FileBusy,5869 .BUSY => return error.FileBusy,
...@@ -5931,7 +5930,7 @@ fn setsockopt(...@@ -5931,7 +5930,7 @@ fn setsockopt(
5931 ev.yield(null, .nothing);5930 ev.yield(null, .nothing);
5932 switch (cancel_region.errno()) {5931 switch (cancel_region.errno()) {
5933 .SUCCESS => return,5932 .SUCCESS => return,
5934 .INTR, .CANCELED => continue,5933 .INTR, .CANCELED => {},
5935 .BADF => |err| return errnoBug(err), // File descriptor used after closed.5934 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
5936 .NOTSOCK => |err| return errnoBug(err),5935 .NOTSOCK => |err| return errnoBug(err),
5937 .INVAL => |err| return errnoBug(err),5936 .INVAL => |err| return errnoBug(err),
...@@ -5982,7 +5981,7 @@ fn socket(...@@ -5982,7 +5981,7 @@ fn socket(
5982 const completion = cancel_region.completion();5981 const completion = cancel_region.completion();
5983 switch (completion.errno()) {5982 switch (completion.errno()) {
5984 .SUCCESS => break completion.result,5983 .SUCCESS => break completion.result,
5985 .INTR, .CANCELED => continue,5984 .INTR, .CANCELED => {},
5986 .AFNOSUPPORT => return error.AddressFamilyUnsupported,5985 .AFNOSUPPORT => return error.AddressFamilyUnsupported,
5987 .INVAL => return error.ProtocolUnsupportedBySystem,5986 .INVAL => return error.ProtocolUnsupportedBySystem,
5988 .MFILE => return error.ProcessFdQuotaExceeded,5987 .MFILE => return error.ProcessFdQuotaExceeded,
...@@ -5994,7 +5993,7 @@ fn socket(...@@ -5994,7 +5993,7 @@ fn socket(
5994 else => |err| return unexpectedErrno(err),5993 else => |err| return unexpectedErrno(err),
5995 }5994 }
5996 };5995 };
5997 errdefer ev.close(socket_fd);5996 errdefer ev.closeAsync(socket_fd);
59985997
5999 if (options.ip6_only) {5998 if (options.ip6_only) {
6000 if (linux.IPV6 == void) return error.OptionUnsupported;5999 if (linux.IPV6 == void) return error.OptionUnsupported;
...@@ -6044,7 +6043,7 @@ fn statx(...@@ -6044,7 +6043,7 @@ fn statx(
6044 ev.yield(null, .nothing);6043 ev.yield(null, .nothing);
6045 switch (cancel_region.errno()) {6044 switch (cancel_region.errno()) {
6046 .SUCCESS => return statFromLinux(&statx_buf),6045 .SUCCESS => return statFromLinux(&statx_buf),
6047 .INTR, .CANCELED => continue,6046 .INTR, .CANCELED => {},
6048 .ACCES => return error.AccessDenied,6047 .ACCES => return error.AccessDenied,
6049 .BADF => |err| return errnoBug(err), // File descriptor used after closed.6048 .BADF => |err| return errnoBug(err), // File descriptor used after closed.
6050 .FAULT => |err| return errnoBug(err),6049 .FAULT => |err| return errnoBug(err),
...@@ -6083,7 +6082,7 @@ fn utimensat(...@@ -6083,7 +6082,7 @@ fn utimensat(
6083 try sync.cancel_region.await(.nothing);6082 try sync.cancel_region.await(.nothing);
6084 switch (linux.errno(linux.utimensat(dir, path, times, flags))) {6083 switch (linux.errno(linux.utimensat(dir, path, times, flags))) {
6085 .SUCCESS => return,6084 .SUCCESS => return,
6086 .INTR => continue,6085 .INTR => {},
6087 .BADF => |err| return errnoBug(err), // always a race condition6086 .BADF => |err| return errnoBug(err), // always a race condition
6088 .FAULT => |err| return errnoBug(err),6087 .FAULT => |err| return errnoBug(err),
6089 .INVAL => |err| return errnoBug(err),6088 .INVAL => |err| return errnoBug(err),
...@@ -6095,19 +6094,33 @@ fn utimensat(...@@ -6095,19 +6094,33 @@ fn utimensat(
6095 }6094 }
6096}6095}
60976096
6098fn writeAll(6097fn writeAllSync(sync: *CancelRegion.Sync, fd: fd_t, buffer: []const u8) File.Writer.Error!void {
6099 ev: *Evented,
6100 cancel_region: *CancelRegion,
6101 fd: fd_t,
6102 buffer: []const u8,
6103) (File.Writer.Error || error{EndOfStream})!void {
6104 var index: usize = 0;6098 var index: usize = 0;
6105 while (buffer.len - index != 0) {6099 while (buffer.len - index != 0) index += try writeSync(sync, fd, buffer[index..]);
6106 const len = try ev.pwritev(cancel_region, fd, &.{6100}
6107 .{ .base = buffer[index..].ptr, .len = buffer.len - index },6101
6108 }, null);6102fn writeSync(sync: *CancelRegion.Sync, fd: fd_t, buffer: []const u8) File.Writer.Error!usize {
6109 if (len == 0) return error.EndOfStream;6103 while (true) {
6110 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 }
6111 }6124 }
6112}6125}
61136126
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,