authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-31 12:07:31+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-31 12:07:31+01:00
log2b19134c86223236b3fffc1360577a31d0251604
tree6e98fbfeea5309faef0105c4f29b5c1d0235681a
parent5ccc2ea85d5d4c23daae8a3afe6b7784071597ac
parent9646801bed8f0f36b59deecff32ef02868ed72f2

Merge pull request 'std.Io: introduce batching and operations API, satisfying the "poll" use case' (#30743) from poll into master

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

28 files changed, 1608 insertions(+), 902 deletions(-)

build.zig+3-39
...@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) !void {...@@ -29,7 +29,7 @@ pub fn build(b: *std.Build) !void {
29 const use_zig_libcxx = b.option(bool, "use-zig-libcxx", "If libc++ is needed, use zig's bundled version, don't try to integrate with the system") orelse false;29 const use_zig_libcxx = b.option(bool, "use-zig-libcxx", "If libc++ is needed, use zig's bundled version, don't try to integrate with the system") orelse false;
3030
31 const test_step = b.step("test", "Run all the tests");31 const test_step = b.step("test", "Run all the tests");
32 const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse false;32 const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse only_c;
33 const skip_install_langref = b.option(bool, "no-langref", "skip copying of langref to the installation prefix") orelse skip_install_lib_files;33 const skip_install_langref = b.option(bool, "no-langref", "skip copying of langref to the installation prefix") orelse skip_install_lib_files;
34 const std_docs = b.option(bool, "std-docs", "include standard library autodocs") orelse false;34 const std_docs = b.option(bool, "std-docs", "include standard library autodocs") orelse false;
35 const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false;35 const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false;
...@@ -472,27 +472,7 @@ pub fn build(b: *std.Build) !void {...@@ -472,27 +472,7 @@ pub fn build(b: *std.Build) !void {
472 .skip_linux = skip_linux,472 .skip_linux = skip_linux,
473 .skip_llvm = skip_llvm,473 .skip_llvm = skip_llvm,
474 .skip_libc = skip_libc,474 .skip_libc = skip_libc,
475 .max_rss = switch (b.graph.host.result.os.tag) {475 .max_rss = 3_300_000_000,
476 .freebsd => 2_000_000_000,
477 .linux => switch (b.graph.host.result.cpu.arch) {
478 .aarch64 => 659_809_075,
479 .loongarch64 => 598_902_374,
480 .powerpc64le => 627_431_833,
481 .riscv64 => 827_043_430,
482 .s390x => 580_596_121,
483 .x86_64 => 3_290_894_745,
484 else => 3_300_000_000,
485 },
486 .macos => switch (b.graph.host.result.cpu.arch) {
487 .aarch64 => 767_736_217,
488 else => 800_000_000,
489 },
490 .windows => switch (b.graph.host.result.cpu.arch) {
491 .x86_64 => 603_070_054,
492 else => 700_000_000,
493 },
494 else => 3_300_000_000,
495 },
496 }));476 }));
497477
498 test_modules_step.dependOn(tests.addModuleTests(b, .{478 test_modules_step.dependOn(tests.addModuleTests(b, .{
...@@ -518,23 +498,7 @@ pub fn build(b: *std.Build) !void {...@@ -518,23 +498,7 @@ pub fn build(b: *std.Build) !void {
518 .skip_llvm = skip_llvm,498 .skip_llvm = skip_llvm,
519 .skip_libc = true,499 .skip_libc = true,
520 .no_builtin = true,500 .no_builtin = true,
521 .max_rss = switch (b.graph.host.result.os.tag) {501 .max_rss = 900_000_000,
522 .freebsd => 800_000_000,
523 .linux => switch (b.graph.host.result.cpu.arch) {
524 .aarch64 => 639_565_414,
525 .loongarch64 => 598_884_352,
526 .powerpc64le => 597_897_625,
527 .riscv64 => 636_429_516,
528 .s390x => 574_166_630,
529 .x86_64 => 978_463_129,
530 else => 900_000_000,
531 },
532 .macos => switch (b.graph.host.result.cpu.arch) {
533 .aarch64 => 701_413_785,
534 else => 800_000_000,
535 },
536 else => 900_000_000,
537 },
538 }));502 }));
539503
540 test_modules_step.dependOn(tests.addModuleTests(b, .{504 test_modules_step.dependOn(tests.addModuleTests(b, .{
lib/std/Build/Step.zig+38-17
...@@ -381,10 +381,17 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO...@@ -381,10 +381,17 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO
381381
382pub const ZigProcess = struct {382pub const ZigProcess = struct {
383 child: std.process.Child,383 child: std.process.Child,
384 poller: Io.Poller(StreamEnum),384 multi_reader_buffer: Io.File.MultiReader.Buffer(2),
385 multi_reader: Io.File.MultiReader,
385 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,386 progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void,
386387
387 pub const StreamEnum = enum { stdout, stderr };388 pub const StreamEnum = enum { stdout, stderr };
389
390 pub fn deinit(zp: *ZigProcess, io: Io) void {
391 zp.child.kill(io);
392 zp.multi_reader.deinit();
393 zp.* = undefined;
394 }
388};395};
389396
390/// Assumes that argv contains `--listen=-` and that the process being spawned397/// Assumes that argv contains `--listen=-` and that the process being spawned
...@@ -409,7 +416,8 @@ pub fn evalZigProcess(...@@ -409,7 +416,8 @@ pub fn evalZigProcess(
409 assert(watch);416 assert(watch);
410 if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);417 if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd);
411 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {418 const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) {
412 error.BrokenPipe => {419 error.BrokenPipe, error.EndOfStream => |reason| {
420 std.log.info("{s} restart required: {t}", .{ argv[0], reason });
413 // Process restart required.421 // Process restart required.
414 const term = zp.child.wait(io) catch |e| {422 const term = zp.child.wait(io) catch |e| {
415 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });423 return s.fail("unable to wait for {s}: {t}", .{ argv[0], e });
...@@ -455,18 +463,18 @@ pub fn evalZigProcess(...@@ -455,18 +463,18 @@ pub fn evalZigProcess(
455 .request_resource_usage_statistics = true,463 .request_resource_usage_statistics = true,
456 .progress_node = prog_node,464 .progress_node = prog_node,
457 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });465 }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err });
458 defer if (!watch) zp.child.kill(io);
459466
460 zp.* = .{467 zp.* = .{
461 .child = zp.child,468 .child = zp.child,
462 .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{469 .multi_reader_buffer = undefined,
463 .stdout = zp.child.stdout.?,470 .multi_reader = undefined,
464 .stderr = zp.child.stderr.?,
465 }),
466 .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},471 .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {},
467 };472 };
473 zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{
474 zp.child.stdout.?, zp.child.stderr.?,
475 });
468 if (watch) s.setZigProcess(zp);476 if (watch) s.setZigProcess(zp);
469 defer if (!watch) zp.poller.deinit();477 defer if (!watch) zp.deinit(io);
470478
471 const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);479 const result = try zigProcessUpdate(s, zp, watch, web_server, gpa);
472480
...@@ -532,15 +540,26 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build....@@ -532,15 +540,26 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
532 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);540 if (!watch) try sendMessage(io, zp.child.stdin.?, .exit);
533541
534 var result: ?Path = null;542 var result: ?Path = null;
543 var eos_err: error{EndOfStream}!void = {};
535544
536 const stdout = zp.poller.reader(.stdout);545 const stdout = zp.multi_reader.fileReader(0);
537546
538 poll: while (true) {547 while (true) {
539 const Header = std.zig.Server.Message.Header;548 const Header = std.zig.Server.Message.Header;
540 while (stdout.buffered().len < @sizeOf(Header)) if (!try zp.poller.poll()) break :poll;549 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
541 const header = stdout.takeStruct(Header, .little) catch unreachable;550 error.EndOfStream => break,
542 while (stdout.buffered().len < header.bytes_len) if (!try zp.poller.poll()) break :poll;551 error.ReadFailed => return stdout.err.?,
543 const body = stdout.take(header.bytes_len) catch unreachable;552 };
553 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
554 error.EndOfStream => |e| {
555 // Better to report the crash with stderr below, but we set
556 // this in case the child exits successfully while violating
557 // this protocol.
558 eos_err = e;
559 break;
560 },
561 error.ReadFailed => return stdout.err.?,
562 };
544 switch (header.tag) {563 switch (header.tag) {
545 .zig_version => {564 .zig_version => {
546 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {565 if (!std.mem.eql(u8, builtin.zig_version_string, body)) {
...@@ -553,11 +572,11 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build....@@ -553,11 +572,11 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
553 .error_bundle => {572 .error_bundle => {
554 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);573 s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
555 // This message indicates the end of the update.574 // This message indicates the end of the update.
556 if (watch) break :poll;575 if (watch) break;
557 },576 },
558 .emit_digest => {577 .emit_digest => {
559 const EmitDigest = std.zig.Server.Message.EmitDigest;578 const EmitDigest = std.zig.Server.Message.EmitDigest;
560 const emit_digest = @as(*align(1) const EmitDigest, @ptrCast(body));579 const emit_digest: *align(1) const EmitDigest = @ptrCast(body);
561 s.result_cached = emit_digest.flags.cache_hit;580 s.result_cached = emit_digest.flags.cache_hit;
562 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];581 const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len];
563 result = .{582 result = .{
...@@ -631,11 +650,13 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build....@@ -631,11 +650,13 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build.
631650
632 s.result_duration_ns = timer.read();651 s.result_duration_ns = timer.read();
633652
634 const stderr_contents = try zp.poller.toOwnedSlice(.stderr);653 const stderr_contents = zp.multi_reader.reader(1).buffered();
635 if (stderr_contents.len > 0) {654 if (stderr_contents.len > 0) {
636 try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents));655 try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents));
637 }656 }
638657
658 try eos_err;
659
639 return result;660 return result;
640}661}
641662
lib/std/Build/Step/Run.zig+105-85
...@@ -1385,14 +1385,12 @@ fn runCommand(...@@ -1385,14 +1385,12 @@ fn runCommand(
1385 break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| {1385 break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| {
1386 if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;1386 if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped;
1387 if (e == error.MakeFailed) return error.MakeFailed; // error already reported1387 if (e == error.MakeFailed) return error.MakeFailed; // error already reported
1388 return step.fail("unable to spawn interpreter {s}: {s}", .{1388 return step.fail("unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e });
1389 interp_argv.items[0], @errorName(e),
1390 });
1391 };1389 };
1392 }1390 }
1393 if (err == error.MakeFailed) return error.MakeFailed; // error already reported1391 if (err == error.MakeFailed) return error.MakeFailed; // error already reported
13941392
1395 return step.fail("failed to spawn and capture stdio from {s}: {s}", .{ argv[0], @errorName(err) });1393 return step.fail("failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err });
1396 };1394 };
13971395
1398 const generic_result = opt_generic_result orelse {1396 const generic_result = opt_generic_result orelse {
...@@ -1589,9 +1587,13 @@ fn spawnChildAndCollect(...@@ -1589,9 +1587,13 @@ fn spawnChildAndCollect(
1589 };1587 };
15901588
1591 if (run.stdio == .zig_test) {1589 if (run.stdio == .zig_test) {
1592 var timer = try std.time.Timer.start();1590 const started: Io.Clock.Timestamp = try .now(io, .awake);
1593 defer run.step.result_duration_ns = timer.read();1591 const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) {
1594 try evalZigTest(run, spawn_options, options, fuzz_context);1592 error.Canceled => |e| return e,
1593 else => |e| e,
1594 };
1595 run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds);
1596 try result;
1595 return null;1597 return null;
1596 } else {1598 } else {
1597 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;1599 const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit;
...@@ -1604,10 +1606,14 @@ fn spawnChildAndCollect(...@@ -1604,10 +1606,14 @@ fn spawnChildAndCollect(
1604 } else .no_color;1606 } else .no_color;
1605 defer if (inherit) io.unlockStderr();1607 defer if (inherit) io.unlockStderr();
1606 try setColorEnvironmentVariables(run, environ_map, terminal_mode);1608 try setColorEnvironmentVariables(run, environ_map, terminal_mode);
1607 var timer = try std.time.Timer.start();1609
1608 const res = try evalGeneric(run, spawn_options);1610 const started: Io.Clock.Timestamp = try .now(io, .awake);
1609 run.step.result_duration_ns = timer.read();1611 const result = evalGeneric(run, spawn_options) catch |err| switch (err) {
1610 return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr };1612 error.Canceled => |e| return e,
1613 else => |e| e,
1614 };
1615 run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds);
1616 return try result;
1611 }1617 }
1612}1618}
16131619
...@@ -1669,39 +1675,42 @@ fn evalZigTest(...@@ -1669,39 +1675,42 @@ fn evalZigTest(
16691675
1670 while (true) {1676 while (true) {
1671 var child = try process.spawn(io, spawn_options);1677 var child = try process.spawn(io, spawn_options);
1672 var poller = std.Io.poll(gpa, StdioPollEnum, .{1678 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
1673 .stdout = child.stdout.?,1679 var multi_reader: Io.File.MultiReader = undefined;
1674 .stderr = child.stderr.?,1680 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
1675 });
1676 var child_killed = false;1681 var child_killed = false;
1677 defer if (!child_killed) {1682 defer if (!child_killed) {
1678 child.kill(io);1683 child.kill(io);
1679 poller.deinit();1684 multi_reader.deinit();
1680 run.step.result_peak_rss = @max(1685 run.step.result_peak_rss = @max(
1681 run.step.result_peak_rss,1686 run.step.result_peak_rss,
1682 child.resource_usage_statistics.getMaxRss() orelse 0,1687 child.resource_usage_statistics.getMaxRss() orelse 0,
1683 );1688 );
1684 };1689 };
16851690
1686 switch (try pollZigTest(1691 switch (try waitZigTest(
1687 run,1692 run,
1688 &child,1693 &child,
1689 options,1694 options,
1690 fuzz_context,1695 fuzz_context,
1691 &poller,1696 &multi_reader,
1692 &test_metadata,1697 &test_metadata,
1693 &test_results,1698 &test_results,
1694 )) {1699 )) {
1695 .write_failed => |err| {1700 .write_failed => |err| {
1696 // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured1701 // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured
1697 // all available stderr to make our error output as useful as possible.1702 // all available stderr to make our error output as useful as possible.
1698 while (try poller.poll()) {}1703 const stderr_fr = multi_reader.fileReader(1);
1699 run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered());1704 while (stderr_fr.interface.fillMore()) |_| {} else |e| switch (e) {
1705 error.ReadFailed => return stderr_fr.err.?,
1706 error.EndOfStream => {},
1707 }
1708 run.step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered());
17001709
1701 // Clean up everything and wait for the child to exit.1710 // Clean up everything and wait for the child to exit.
1702 child.stdin.?.close(io);1711 child.stdin.?.close(io);
1703 child.stdin = null;1712 child.stdin = null;
1704 poller.deinit();1713 multi_reader.deinit();
1705 child_killed = true;1714 child_killed = true;
1706 const term = try child.wait(io);1715 const term = try child.wait(io);
1707 run.step.result_peak_rss = @max(1716 run.step.result_peak_rss = @max(
...@@ -1716,13 +1725,13 @@ fn evalZigTest(...@@ -1716,13 +1725,13 @@ fn evalZigTest(
1716 .no_poll => |no_poll| {1725 .no_poll => |no_poll| {
1717 // This might be a success (we requested exit and the child dutifully closed stdout) or1726 // This might be a success (we requested exit and the child dutifully closed stdout) or
1718 // a crash of some kind. Either way, the child will terminate by itself -- wait for it.1727 // a crash of some kind. Either way, the child will terminate by itself -- wait for it.
1719 const stderr_owned = try arena.dupe(u8, poller.reader(.stderr).buffered());1728 const stderr_reader = multi_reader.reader(1);
1720 poller.reader(.stderr).tossBuffered();1729 const stderr_owned = try arena.dupe(u8, stderr_reader.buffered());
17211730
1722 // Clean up everything and wait for the child to exit.1731 // Clean up everything and wait for the child to exit.
1723 child.stdin.?.close(io);1732 child.stdin.?.close(io);
1724 child.stdin = null;1733 child.stdin = null;
1725 poller.deinit();1734 multi_reader.deinit();
1726 child_killed = true;1735 child_killed = true;
1727 const term = try child.wait(io);1736 const term = try child.wait(io);
1728 run.step.result_peak_rss = @max(1737 run.step.result_peak_rss = @max(
...@@ -1770,8 +1779,9 @@ fn evalZigTest(...@@ -1770,8 +1779,9 @@ fn evalZigTest(
1770 return;1779 return;
1771 },1780 },
1772 .timeout => |timeout| {1781 .timeout => |timeout| {
1773 const stderr = poller.reader(.stderr).buffered();1782 const stderr_reader = multi_reader.reader(1);
1774 poller.reader(.stderr).tossBuffered();1783 const stderr = stderr_reader.buffered();
1784 stderr_reader.tossBuffered();
1775 if (timeout.active_test_index) |test_index| {1785 if (timeout.active_test_index) |test_index| {
1776 // A test was running. Report the timeout against that test, and continue on to1786 // A test was running. Report the timeout against that test, and continue on to
1777 // the next test.1787 // the next test.
...@@ -1796,16 +1806,16 @@ fn evalZigTest(...@@ -1796,16 +1806,16 @@ fn evalZigTest(
1796 }1806 }
1797}1807}
17981808
1799/// Polls stdout of a Zig test process until a termination condition is reached:1809/// Reads stdout of a Zig test process until a termination condition is reached:
1800/// * A write fails, indicating the child unexpectedly closed stdin1810/// * A write fails, indicating the child unexpectedly closed stdin
1801/// * A test (or a response from the test runner) times out1811/// * A test (or a response from the test runner) times out
1802/// * `poll` fails, indicating the child closed stdout and stderr1812/// * The wait fails, indicating the child closed stdout and stderr
1803fn pollZigTest(1813fn waitZigTest(
1804 run: *Run,1814 run: *Run,
1805 child: *process.Child,1815 child: *process.Child,
1806 options: Step.MakeOptions,1816 options: Step.MakeOptions,
1807 fuzz_context: ?FuzzContext,1817 fuzz_context: ?FuzzContext,
1808 poller: *std.Io.Poller(StdioPollEnum),1818 multi_reader: *Io.File.MultiReader,
1809 opt_metadata: *?TestMetadata,1819 opt_metadata: *?TestMetadata,
1810 results: *Step.TestResults,1820 results: *Step.TestResults,
1811) !union(enum) {1821) !union(enum) {
...@@ -1859,9 +1869,7 @@ fn pollZigTest(...@@ -1859,9 +1869,7 @@ fn pollZigTest(
18591869
1860 var active_test_index: ?u32 = null;1870 var active_test_index: ?u32 = null;
18611871
1862 // `null` means this host does not support `std.time.Timer`. This timer is `reset()` whenever we1872 var last_update: Io.Clock.Timestamp = try .now(io, .awake);
1863 // change `active_test_index`, i.e. whenever a test starts or finishes.
1864 var timer: ?std.time.Timer = std.time.Timer.start() catch null;
18651873
1866 var coverage_id: ?u64 = null;1874 var coverage_id: ?u64 = null;
18671875
...@@ -1869,16 +1877,26 @@ fn pollZigTest(...@@ -1869,16 +1877,26 @@ fn pollZigTest(
1869 // test. For instance, if the test runner leaves this much time between us requesting a test to1877 // test. For instance, if the test runner leaves this much time between us requesting a test to
1870 // start and it acknowledging the test starting, we terminate the child and raise an error. This1878 // start and it acknowledging the test starting, we terminate the child and raise an error. This
1871 // *should* never happen, but could in theory be caused by some very unlucky IB in a test.1879 // *should* never happen, but could in theory be caused by some very unlucky IB in a test.
1872 const response_timeout_ns: ?u64 = ns: {1880 const response_timeout: ?Io.Clock.Duration = t: {
1873 if (fuzz_context != null) break :ns null; // don't timeout fuzz tests1881 if (fuzz_context != null) break :t null; // don't timeout fuzz tests
1874 break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);1882 const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s);
1883 break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) };
1875 };1884 };
1885 const test_timeout: ?Io.Clock.Duration = if (options.unit_test_timeout_ns) |ns| .{
1886 .clock = .awake,
1887 .raw = .fromNanoseconds(ns),
1888 } else null;
18761889
1877 const stdout = poller.reader(.stdout);1890 const stdout = multi_reader.reader(0);
1878 const stderr = poller.reader(.stderr);1891 const stderr = multi_reader.reader(1);
1892 const Header = std.zig.Server.Message.Header;
18791893
1880 while (true) {1894 while (true) {
1881 const Header = std.zig.Server.Message.Header;1895 const timeout: Io.Timeout = t: {
1896 const opt_duration = if (active_test_index == null) response_timeout else test_timeout;
1897 const duration = opt_duration orelse break :t .none;
1898 break :t .{ .deadline = last_update.addDuration(duration) };
1899 };
18821900
1883 // This block is exited when `stdout` contains enough bytes for a `Header`.1901 // This block is exited when `stdout` contains enough bytes for a `Header`.
1884 header_ready: {1902 header_ready: {
...@@ -1887,47 +1905,37 @@ fn pollZigTest(...@@ -1887,47 +1905,37 @@ fn pollZigTest(
1887 break :header_ready;1905 break :header_ready;
1888 }1906 }
18891907
1890 // Always `null` if `timer` is `null`.1908 multi_reader.fill(64, timeout) catch |err| switch (err) {
1891 const opt_timeout_ns: ?u64 = ns: {1909 error.Timeout => return .{ .timeout = .{
1892 if (timer == null) break :ns null;
1893 if (active_test_index == null) break :ns response_timeout_ns;
1894 break :ns options.unit_test_timeout_ns;
1895 };
1896
1897 if (opt_timeout_ns) |timeout_ns| {
1898 const remaining_ns = timeout_ns -| timer.?.read();
1899 if (!try poller.pollTimeout(remaining_ns)) return .{ .no_poll = .{
1900 .active_test_index = active_test_index,1910 .active_test_index = active_test_index,
1901 .ns_elapsed = if (timer) |*t| t.read() else 0,1911 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1902 } };1912 } },
1903 } else {1913 error.EndOfStream => return .{ .no_poll = .{
1904 if (!try poller.poll()) return .{ .no_poll = .{
1905 .active_test_index = active_test_index,1914 .active_test_index = active_test_index,
1906 .ns_elapsed = if (timer) |*t| t.read() else 0,1915 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1907 } };1916 } },
1908 }1917 else => |e| return e,
19091918 };
1910 if (stdout.buffered().len >= @sizeOf(Header)) {
1911 // There wasn't a header before, but there is one after the `poll`.
1912 break :header_ready;
1913 }
19141919
1915 if (opt_timeout_ns) |timeout_ns| {
1916 const cur_ns = timer.?.read();
1917 if (cur_ns >= timeout_ns) return .{ .timeout = .{
1918 .active_test_index = active_test_index,
1919 .ns_elapsed = cur_ns,
1920 } };
1921 }
1922 continue;1920 continue;
1923 }1921 }
1924 // There is definitely a header available now -- read it.1922 // There is definitely a header available now -- read it.
1925 const header = stdout.takeStruct(Header, .little) catch unreachable;1923 const header = stdout.takeStruct(Header, .little) catch unreachable;
19261924
1927 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) return .{ .no_poll = .{1925 while (stdout.buffered().len < header.bytes_len) {
1928 .active_test_index = active_test_index,1926 multi_reader.fill(64, timeout) catch |err| switch (err) {
1929 .ns_elapsed = if (timer) |*t| t.read() else 0,1927 error.Timeout => return .{ .timeout = .{
1930 } };1928 .active_test_index = active_test_index,
1929 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1930 } },
1931 error.EndOfStream => return .{ .no_poll = .{
1932 .active_test_index = active_test_index,
1933 .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds),
1934 } },
1935 else => |e| return e,
1936 };
1937 }
1938
1931 const body = stdout.take(header.bytes_len) catch unreachable;1939 const body = stdout.take(header.bytes_len) catch unreachable;
1932 var body_r: std.Io.Reader = .fixed(body);1940 var body_r: std.Io.Reader = .fixed(body);
1933 switch (header.tag) {1941 switch (header.tag) {
...@@ -1968,13 +1976,13 @@ fn pollZigTest(...@@ -1968,13 +1976,13 @@ fn pollZigTest(
1968 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));1976 @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64));
19691977
1970 active_test_index = null;1978 active_test_index = null;
1971 if (timer) |*t| t.reset();1979 last_update = try .now(io, .awake);
19721980
1973 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };1981 requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err };
1974 },1982 },
1975 .test_started => {1983 .test_started => {
1976 active_test_index = opt_metadata.*.?.next_index - 1;1984 active_test_index = opt_metadata.*.?.next_index - 1;
1977 if (timer) |*t| t.reset();1985 last_update = try .now(io, .awake);
1978 },1986 },
1979 .test_results => {1987 .test_results => {
1980 assert(fuzz_context == null);1988 assert(fuzz_context == null);
...@@ -2017,7 +2025,10 @@ fn pollZigTest(...@@ -2017,7 +2025,10 @@ fn pollZigTest(
2017 }2025 }
20182026
2019 active_test_index = null;2027 active_test_index = null;
2020 if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap();2028
2029 const now: Io.Clock.Timestamp = try .now(io, .awake);
2030 md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds);
2031 last_update = now;
20212032
2022 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };2033 requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err };
2023 },2034 },
...@@ -2164,6 +2175,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul...@@ -2164,6 +2175,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul
2164 const b = run.step.owner;2175 const b = run.step.owner;
2165 const io = b.graph.io;2176 const io = b.graph.io;
2166 const arena = b.allocator;2177 const arena = b.allocator;
2178 const gpa = b.allocator;
21672179
2168 var child = try process.spawn(io, spawn_options);2180 var child = try process.spawn(io, spawn_options);
2169 defer child.kill(io);2181 defer child.kill(io);
...@@ -2211,23 +2223,31 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul...@@ -2211,23 +2223,31 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul
22112223
2212 if (child.stdout) |stdout| {2224 if (child.stdout) |stdout| {
2213 if (child.stderr) |stderr| {2225 if (child.stderr) |stderr| {
2214 var poller = std.Io.poll(arena, enum { stdout, stderr }, .{2226 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
2215 .stdout = stdout,2227 var multi_reader: Io.File.MultiReader = undefined;
2216 .stderr = stderr,2228 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr });
2217 });2229 defer multi_reader.deinit();
2218 defer poller.deinit();2230
2231 const stdout_reader = multi_reader.reader(0);
2232 const stderr_reader = multi_reader.reader(1);
22192233
2220 while (try poller.poll()) {2234 while (multi_reader.fill(64, .none)) |_| {
2221 if (run.stdio_limit.toInt()) |limit| {2235 if (run.stdio_limit.toInt()) |limit| {
2222 if (poller.reader(.stderr).buffered().len > limit)2236 if (stdout_reader.buffered().len > limit)
2223 return error.StdoutStreamTooLong;2237 return error.StdoutStreamTooLong;
2224 if (poller.reader(.stderr).buffered().len > limit)2238 if (stderr_reader.buffered().len > limit)
2225 return error.StderrStreamTooLong;2239 return error.StderrStreamTooLong;
2226 }2240 }
2241 } else |err| switch (err) {
2242 error.UnsupportedClock, error.Timeout => unreachable,
2243 error.EndOfStream => {},
2244 else => |e| return e,
2227 }2245 }
22282246
2229 stdout_bytes = try poller.toOwnedSlice(.stdout);2247 try multi_reader.checkAnyError();
2230 stderr_bytes = try poller.toOwnedSlice(.stderr);2248
2249 stdout_bytes = try multi_reader.toOwnedSlice(0);
2250 stderr_bytes = try multi_reader.toOwnedSlice(1);
2231 } else {2251 } else {
2232 var stdout_reader = stdout.readerStreaming(io, &.{});2252 var stdout_reader = stdout.readerStreaming(io, &.{});
2233 stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {2253 stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) {
lib/std/Build/WebServer.zig+23-13
...@@ -588,11 +588,12 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -588,11 +588,12 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
588 });588 });
589 defer child.kill(io);589 defer child.kill(io);
590590
591 var poller = Io.poll(gpa, enum { stdout, stderr }, .{591 var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited });
592 .stdout = child.stdout.?,592 defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {};
593 .stderr = child.stderr.?,593
594 });594 var stdout_buffer: [512]u8 = undefined;
595 defer poller.deinit();595 var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer);
596 const stdout = &stdout_reader.interface;
596597
597 try child.stdin.?.writeStreamingAll(io, @ptrCast(@as([]const std.zig.Client.Message.Header, &.{598 try child.stdin.?.writeStreamingAll(io, @ptrCast(@as([]const std.zig.Client.Message.Header, &.{
598 .{ .tag = .update, .bytes_len = 0 },599 .{ .tag = .update, .bytes_len = 0 },
...@@ -600,16 +601,17 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -600,16 +601,17 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
600 })));601 })));
601602
602 const Header = std.zig.Server.Message.Header;603 const Header = std.zig.Server.Message.Header;
604
603 var result: ?Cache.Path = null;605 var result: ?Cache.Path = null;
604 var result_error_bundle = std.zig.ErrorBundle.empty;606 var result_error_bundle = std.zig.ErrorBundle.empty;
607 var body_buffer: std.ArrayList(u8) = .empty;
608 defer body_buffer.deinit(gpa);
605609
606 const stdout = poller.reader(.stdout);610 while (true) {
607611 const header = try stdout.takeStruct(Header, .little);
608 poll: while (true) {612 body_buffer.clearRetainingCapacity();
609 while (stdout.buffered().len < @sizeOf(Header)) if (!(try poller.poll())) break :poll;613 try stdout.appendExact(gpa, &body_buffer, header.bytes_len);
610 const header = stdout.takeStruct(Header, .little) catch unreachable;614 const body = body_buffer.items;
611 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;
612 const body = stdout.take(header.bytes_len) catch unreachable;
613615
614 switch (header.tag) {616 switch (header.tag) {
615 .zig_version => {617 .zig_version => {
...@@ -636,7 +638,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -636,7 +638,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
636 }638 }
637 }639 }
638640
639 const stderr_contents = try poller.toOwnedSlice(.stderr);641 const stderr_contents = try stderr_task.await(io);
640 if (stderr_contents.len > 0) {642 if (stderr_contents.len > 0) {
641 std.debug.print("{s}", .{stderr_contents});643 std.debug.print("{s}", .{stderr_contents});
642 }644 }
...@@ -697,6 +699,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim...@@ -697,6 +699,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim
697 return base_path.join(arena, bin_name);699 return base_path.join(arena, bin_name);
698}700}
699701
702fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 {
703 var file_reader: Io.File.Reader = .initStreaming(file, io, &.{});
704 return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) {
705 error.ReadFailed => return file_reader.err.?,
706 else => |e| return e,
707 };
708}
709
700pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {710pub fn updateTimeReportCompile(ws: *WebServer, opts: struct {
701 compile: *Build.Step.Compile,711 compile: *Build.Step.Compile,
702712
lib/std/Io.zig+241-461
...@@ -15,463 +15,13 @@...@@ -15,463 +15,13 @@
15const Io = @This();15const Io = @This();
1616
17const builtin = @import("builtin");17const builtin = @import("builtin");
18const is_windows = builtin.os.tag == .windows;
1918
20const std = @import("std.zig");19const std = @import("std.zig");
21const windows = std.os.windows;
22const posix = std.posix;
23const math = std.math;20const math = std.math;
24const assert = std.debug.assert;21const assert = std.debug.assert;
25const Allocator = std.mem.Allocator;22const Allocator = std.mem.Allocator;
26const Alignment = std.mem.Alignment;23const Alignment = std.mem.Alignment;
2724
28pub fn poll(
29 gpa: Allocator,
30 comptime StreamEnum: type,
31 files: PollFiles(StreamEnum),
32) Poller(StreamEnum) {
33 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
34 var result: Poller(StreamEnum) = .{
35 .gpa = gpa,
36 .readers = @splat(.failing),
37 .poll_fds = undefined,
38 .windows = if (is_windows) .{
39 .first_read_done = false,
40 .overlapped = [1]windows.OVERLAPPED{
41 std.mem.zeroes(windows.OVERLAPPED),
42 } ** enum_fields.len,
43 .small_bufs = undefined,
44 .active = .{
45 .count = 0,
46 .handles_buf = undefined,
47 .stream_map = undefined,
48 },
49 } else {},
50 };
51
52 inline for (enum_fields, 0..) |field, i| {
53 if (is_windows) {
54 result.windows.active.handles_buf[i] = @field(files, field.name).handle;
55 } else {
56 result.poll_fds[i] = .{
57 .fd = @field(files, field.name).handle,
58 .events = posix.POLL.IN,
59 .revents = undefined,
60 };
61 }
62 }
63
64 return result;
65}
66
67pub fn Poller(comptime StreamEnum: type) type {
68 return struct {
69 const enum_fields = @typeInfo(StreamEnum).@"enum".fields;
70 const PollFd = if (is_windows) void else posix.pollfd;
71
72 gpa: Allocator,
73 readers: [enum_fields.len]Reader,
74 poll_fds: [enum_fields.len]PollFd,
75 windows: if (is_windows) struct {
76 first_read_done: bool,
77 overlapped: [enum_fields.len]windows.OVERLAPPED,
78 small_bufs: [enum_fields.len][128]u8,
79 active: struct {
80 count: math.IntFittingRange(0, enum_fields.len),
81 handles_buf: [enum_fields.len]windows.HANDLE,
82 stream_map: [enum_fields.len]StreamEnum,
83
84 pub fn removeAt(self: *@This(), index: u32) void {
85 assert(index < self.count);
86 for (index + 1..self.count) |i| {
87 self.handles_buf[i - 1] = self.handles_buf[i];
88 self.stream_map[i - 1] = self.stream_map[i];
89 }
90 self.count -= 1;
91 }
92 },
93 } else void,
94
95 const Self = @This();
96
97 pub fn deinit(self: *Self) void {
98 const gpa = self.gpa;
99 if (is_windows) {
100 // cancel any pending IO to prevent clobbering OVERLAPPED value
101 for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| {
102 _ = windows.kernel32.CancelIo(h);
103 }
104 }
105 inline for (&self.readers) |*r| gpa.free(r.buffer);
106 self.* = undefined;
107 }
108
109 pub fn poll(self: *Self) !bool {
110 if (is_windows) {
111 return pollWindows(self, null);
112 } else {
113 return pollPosix(self, null);
114 }
115 }
116
117 pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool {
118 if (is_windows) {
119 return pollWindows(self, nanoseconds);
120 } else {
121 return pollPosix(self, nanoseconds);
122 }
123 }
124
125 pub fn reader(self: *Self, which: StreamEnum) *Reader {
126 return &self.readers[@intFromEnum(which)];
127 }
128
129 pub fn toOwnedSlice(self: *Self, which: StreamEnum) error{OutOfMemory}![]u8 {
130 const gpa = self.gpa;
131 const r = reader(self, which);
132 if (r.seek == 0) {
133 const new = try gpa.realloc(r.buffer, r.end);
134 r.buffer = &.{};
135 r.end = 0;
136 return new;
137 }
138 const new = try gpa.dupe(u8, r.buffered());
139 gpa.free(r.buffer);
140 r.buffer = &.{};
141 r.seek = 0;
142 r.end = 0;
143 return new;
144 }
145
146 fn pollWindows(self: *Self, nanoseconds: ?u64) !bool {
147 const bump_amt = 512;
148 const gpa = self.gpa;
149
150 if (!self.windows.first_read_done) {
151 var already_read_data = false;
152 for (0..enum_fields.len) |i| {
153 const handle = self.windows.active.handles_buf[i];
154 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
155 gpa,
156 handle,
157 &self.windows.overlapped[i],
158 &self.readers[i],
159 &self.windows.small_bufs[i],
160 bump_amt,
161 )) {
162 .populated, .empty => |state| {
163 if (state == .populated) already_read_data = true;
164 self.windows.active.handles_buf[self.windows.active.count] = handle;
165 self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i));
166 self.windows.active.count += 1;
167 },
168 .closed => {}, // don't add to the wait_objects list
169 .closed_populated => {
170 // don't add to the wait_objects list, but we did already get data
171 already_read_data = true;
172 },
173 }
174 }
175 self.windows.first_read_done = true;
176 if (already_read_data) return true;
177 }
178
179 while (true) {
180 if (self.windows.active.count == 0) return false;
181
182 const status = windows.kernel32.WaitForMultipleObjects(
183 self.windows.active.count,
184 &self.windows.active.handles_buf,
185 0,
186 if (nanoseconds) |ns|
187 @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1)
188 else
189 windows.INFINITE,
190 );
191 if (status == windows.WAIT_FAILED)
192 return windows.unexpectedError(windows.GetLastError());
193 if (status == windows.WAIT_TIMEOUT)
194 return true;
195
196 if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1)
197 unreachable;
198
199 const active_idx = status - windows.WAIT_OBJECT_0;
200
201 const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]);
202 const handle = self.windows.active.handles_buf[active_idx];
203
204 const overlapped = &self.windows.overlapped[stream_idx];
205 const stream_reader = &self.readers[stream_idx];
206 const small_buf = &self.windows.small_bufs[stream_idx];
207
208 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
209 .success => |n| n,
210 .closed => {
211 self.windows.active.removeAt(active_idx);
212 continue;
213 },
214 .aborted => unreachable,
215 };
216 const buf = small_buf[0..num_bytes_read];
217 const dest = try writableSliceGreedyAlloc(stream_reader, gpa, buf.len);
218 @memcpy(dest[0..buf.len], buf);
219 advanceBufferEnd(stream_reader, buf.len);
220
221 switch (try windowsAsyncReadToFifoAndQueueSmallRead(
222 gpa,
223 handle,
224 overlapped,
225 stream_reader,
226 small_buf,
227 bump_amt,
228 )) {
229 .empty => {}, // irrelevant, we already got data from the small buffer
230 .populated => {},
231 .closed,
232 .closed_populated, // identical, since we already got data from the small buffer
233 => self.windows.active.removeAt(active_idx),
234 }
235 return true;
236 }
237 }
238
239 fn pollPosix(self: *Self, nanoseconds: ?u64) !bool {
240 const gpa = self.gpa;
241 // We ask for ensureUnusedCapacity with this much extra space. This
242 // has more of an effect on small reads because once the reads
243 // start to get larger the amount of space an ArrayList will
244 // allocate grows exponentially.
245 const bump_amt = 512;
246
247 const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP;
248
249 const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns|
250 std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32)
251 else
252 -1);
253 if (events_len == 0) {
254 for (self.poll_fds) |poll_fd| {
255 if (poll_fd.fd != -1) return true;
256 } else return false;
257 }
258
259 var keep_polling = false;
260 for (&self.poll_fds, &self.readers) |*poll_fd, *r| {
261 // Try reading whatever is available before checking the error
262 // conditions.
263 // It's still possible to read after a POLL.HUP is received,
264 // always check if there's some data waiting to be read first.
265 if (poll_fd.revents & posix.POLL.IN != 0) {
266 const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt);
267 const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) {
268 error.BrokenPipe => 0, // Handle the same as EOF.
269 else => |e| return e,
270 };
271 advanceBufferEnd(r, amt);
272 if (amt == 0) {
273 // Remove the fd when the EOF condition is met.
274 poll_fd.fd = -1;
275 } else {
276 keep_polling = true;
277 }
278 } else if (poll_fd.revents & err_mask != 0) {
279 // Exclude the fds that signaled an error.
280 poll_fd.fd = -1;
281 } else if (poll_fd.fd != -1) {
282 keep_polling = true;
283 }
284 }
285 return keep_polling;
286 }
287
288 /// Returns a slice into the unused capacity of `buffer` with at least
289 /// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary.
290 ///
291 /// After calling this function, typically the caller will follow up with a
292 /// call to `advanceBufferEnd` to report the actual number of bytes buffered.
293 fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 {
294 {
295 const unused = r.buffer[r.end..];
296 if (unused.len >= min_len) return unused;
297 }
298 if (r.seek > 0) {
299 const data = r.buffer[r.seek..r.end];
300 @memmove(r.buffer[0..data.len], data);
301 r.seek = 0;
302 r.end = data.len;
303 }
304 {
305 var list: std.ArrayList(u8) = .{
306 .items = r.buffer[0..r.end],
307 .capacity = r.buffer.len,
308 };
309 defer r.buffer = list.allocatedSlice();
310 try list.ensureUnusedCapacity(allocator, min_len);
311 }
312 const unused = r.buffer[r.end..];
313 assert(unused.len >= min_len);
314 return unused;
315 }
316
317 /// After writing directly into the unused capacity of `buffer`, this function
318 /// updates `end` so that users of `Reader` can receive the data.
319 fn advanceBufferEnd(r: *Reader, n: usize) void {
320 assert(n <= r.buffer.len - r.end);
321 r.end += n;
322 }
323
324 /// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful
325 /// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For
326 /// compatibility, we point it to this dummy variables, which we never otherwise access.
327 /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
328 var win_dummy_bytes_read: u32 = undefined;
329
330 /// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before
331 /// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data
332 /// is available. `handle` must have no pending asynchronous operation.
333 fn windowsAsyncReadToFifoAndQueueSmallRead(
334 gpa: Allocator,
335 handle: windows.HANDLE,
336 overlapped: *windows.OVERLAPPED,
337 r: *Reader,
338 small_buf: *[128]u8,
339 bump_amt: usize,
340 ) !enum { empty, populated, closed_populated, closed } {
341 var read_any_data = false;
342 while (true) {
343 const fifo_read_pending = while (true) {
344 const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt);
345 const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32);
346
347 if (0 == windows.kernel32.ReadFile(
348 handle,
349 buf.ptr,
350 buf_len,
351 &win_dummy_bytes_read,
352 overlapped,
353 )) switch (windows.GetLastError()) {
354 .IO_PENDING => break true,
355 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
356 else => |err| return windows.unexpectedError(err),
357 };
358
359 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
360 .success => |n| n,
361 .closed => return if (read_any_data) .closed_populated else .closed,
362 .aborted => unreachable,
363 };
364
365 read_any_data = true;
366 advanceBufferEnd(r, num_bytes_read);
367
368 if (num_bytes_read == buf_len) {
369 // We filled the buffer, so there's probably more data available.
370 continue;
371 } else {
372 // We didn't fill the buffer, so assume we're out of data.
373 // There is no pending read.
374 break false;
375 }
376 };
377
378 if (fifo_read_pending) cancel_read: {
379 // Cancel the pending read into the FIFO.
380 _ = windows.kernel32.CancelIo(handle);
381
382 // We have to wait for the handle to be signalled, i.e. for the cancelation to complete.
383 switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) {
384 windows.WAIT_OBJECT_0 => {},
385 windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()),
386 else => unreachable,
387 }
388
389 // If it completed before we canceled, make sure to tell the FIFO!
390 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) {
391 .success => |n| n,
392 .closed => return if (read_any_data) .closed_populated else .closed,
393 .aborted => break :cancel_read,
394 };
395 read_any_data = true;
396 advanceBufferEnd(r, num_bytes_read);
397 }
398
399 // Try to queue the 1-byte read.
400 if (0 == windows.kernel32.ReadFile(
401 handle,
402 small_buf,
403 small_buf.len,
404 &win_dummy_bytes_read,
405 overlapped,
406 )) switch (windows.GetLastError()) {
407 .IO_PENDING => {
408 // 1-byte read pending as intended
409 return if (read_any_data) .populated else .empty;
410 },
411 .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed,
412 else => |err| return windows.unexpectedError(err),
413 };
414
415 // We got data back this time. Write it to the FIFO and run the main loop again.
416 const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) {
417 .success => |n| n,
418 .closed => return if (read_any_data) .closed_populated else .closed,
419 .aborted => unreachable,
420 };
421 const buf = small_buf[0..num_bytes_read];
422 const dest = try writableSliceGreedyAlloc(r, gpa, buf.len);
423 @memcpy(dest[0..buf.len], buf);
424 advanceBufferEnd(r, buf.len);
425 read_any_data = true;
426 }
427 }
428
429 /// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation.
430 /// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected).
431 ///
432 /// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the
433 /// operation immediately returns data:
434 /// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially
435 /// erroneous results."
436 /// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...]
437 /// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to
438 /// get the actual number of bytes read."
439 /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile
440 fn windowsGetReadResult(
441 handle: windows.HANDLE,
442 overlapped: *windows.OVERLAPPED,
443 allow_aborted: bool,
444 ) !union(enum) {
445 success: u32,
446 closed,
447 aborted,
448 } {
449 var num_bytes_read: u32 = undefined;
450 if (0 == windows.kernel32.GetOverlappedResult(
451 handle,
452 overlapped,
453 &num_bytes_read,
454 0,
455 )) switch (windows.GetLastError()) {
456 .BROKEN_PIPE => return .closed,
457 .OPERATION_ABORTED => |err| if (allow_aborted) {
458 return .aborted;
459 } else {
460 return windows.unexpectedError(err);
461 },
462 else => |err| return windows.unexpectedError(err),
463 };
464 return .{ .success = num_bytes_read };
465 }
466 };
467}
468
469/// Given an enum, returns a struct with fields of that enum, each field
470/// representing an I/O stream for polling.
471pub fn PollFiles(comptime StreamEnum: type) type {
472 return @Struct(.auto, null, std.meta.fieldNames(StreamEnum), &@splat(Io.File), &@splat(.{}));
473}
474
475userdata: ?*anyopaque,25userdata: ?*anyopaque,
476vtable: *const VTable,26vtable: *const VTable,
47727
...@@ -599,6 +149,11 @@ pub const VTable = struct {...@@ -599,6 +149,11 @@ pub const VTable = struct {
599 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,149 futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void,
600 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,150 futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void,
601151
152 operate: *const fn (?*anyopaque, Operation) Cancelable!Operation.Result,
153 batchAwaitAsync: *const fn (?*anyopaque, *Batch) Cancelable!void,
154 batchAwaitConcurrent: *const fn (?*anyopaque, *Batch, Timeout) Batch.AwaitConcurrentError!void,
155 batchCancel: *const fn (?*anyopaque, *Batch) void,
156
602 dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void,157 dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void,
603 dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus,158 dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus,
604 dirCreateDirPathOpen: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir,159 dirCreateDirPathOpen: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir,
...@@ -633,9 +188,7 @@ pub const VTable = struct {...@@ -633,9 +188,7 @@ pub const VTable = struct {
633 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,188 fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize,
634 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,189 fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize,
635 fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize,190 fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize,
636 /// Returns 0 on end of stream.191 /// Returns 0 if reading at or past the end.
637 fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.Reader.Error!usize,
638 /// Returns 0 on end of stream.
639 fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize,192 fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize,
640 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,193 fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void,
641 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,194 fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void,
...@@ -702,20 +255,247 @@ pub const VTable = struct {...@@ -702,20 +255,247 @@ pub const VTable = struct {
702 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,255 netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void,
703};256};
704257
258pub const Operation = union(enum) {
259 file_read_streaming: FileReadStreaming,
260
261 pub const Tag = @typeInfo(Operation).@"union".tag_type.?;
262
263 /// May return 0 reads which is different than `error.EndOfStream`.
264 pub const FileReadStreaming = struct {
265 file: File,
266 data: []const []u8,
267
268 pub const Error = UnendingError || error{EndOfStream};
269 pub const UnendingError = error{
270 InputOutput,
271 SystemResources,
272 /// Trying to read a directory file descriptor as if it were a file.
273 IsDir,
274 ConnectionResetByPeer,
275 /// File was not opened with read capability.
276 NotOpenForReading,
277 SocketUnconnected,
278 /// Non-blocking has been enabled, and reading from the file descriptor
279 /// would block.
280 WouldBlock,
281 /// In WASI, this error occurs when the file descriptor does
282 /// not hold the required rights to read from it.
283 AccessDenied,
284 /// Unable to read file due to lock. Depending on the `Io` implementation,
285 /// reading from a locked file may return this error, or may ignore the
286 /// lock.
287 LockViolation,
288 } || Io.UnexpectedError;
289
290 pub const Result = usize;
291 };
292
293 pub const Result = Result: {
294 const operation_fields = @typeInfo(Operation).@"union".fields;
295 var field_names: [operation_fields.len][]const u8 = undefined;
296 var field_types: [operation_fields.len]type = undefined;
297 for (operation_fields, &field_names, &field_types) |field, *field_name, *field_type| {
298 field_name.* = field.name;
299 field_type.* = field.type.Error!field.type.Result;
300 }
301 break :Result @Union(.auto, Tag, &field_names, &field_types, &@splat(.{}));
302 };
303
304 pub const Storage = union {
305 unused: List.DoubleNode,
306 submission: Submission,
307 pending: Pending,
308 completion: Completion,
309
310 pub const Submission = struct {
311 node: List.SingleNode,
312 operation: Operation,
313 };
314
315 pub const Pending = struct {
316 node: List.DoubleNode,
317 tag: Tag,
318 context: [3]usize,
319 };
320
321 pub const Completion = struct {
322 node: List.SingleNode,
323 result: Result,
324 };
325 };
326
327 pub const OptionalIndex = enum(u32) {
328 none = std.math.maxInt(u32),
329 _,
330
331 pub fn fromIndex(i: usize) OptionalIndex {
332 const oi: OptionalIndex = @enumFromInt(i);
333 assert(oi != .none);
334 return oi;
335 }
336
337 pub fn toIndex(oi: OptionalIndex) u32 {
338 assert(oi != .none);
339 return @intFromEnum(oi);
340 }
341 };
342 pub const List = struct {
343 head: OptionalIndex,
344 tail: OptionalIndex,
345
346 pub const empty: List = .{ .head = .none, .tail = .none };
347
348 pub const SingleNode = struct { next: OptionalIndex };
349 pub const DoubleNode = struct { prev: OptionalIndex, next: OptionalIndex };
350 };
351};
352
353/// Performs one `Operation`.
354pub fn operate(io: Io, operation: Operation) Cancelable!Operation.Result {
355 return io.vtable.operate(io.userdata, operation);
356}
357
358/// Submits many operations together without waiting for all of them to
359/// complete.
360///
361/// This is a low-level abstraction based on `Operation`. For a higher
362/// level API that operates on `Future`, see `Select` and `Group`.
363pub const Batch = struct {
364 storage: []Operation.Storage,
365 unused: Operation.List,
366 submissions: Operation.List,
367 pending: Operation.List,
368 completions: Operation.List,
369 context: ?*anyopaque,
370
371 /// After calling this, it is safe to unconditionally defer a call to
372 /// `cancel`.
373 pub fn init(storage: []Operation.Storage) Batch {
374 var prev: Operation.OptionalIndex = .none;
375 for (storage, 0..) |*operation, index| {
376 operation.* = .{ .unused = .{ .prev = prev, .next = .fromIndex(index + 1) } };
377 prev = .fromIndex(index);
378 }
379 storage[storage.len - 1].unused.next = .none;
380 return .{
381 .storage = storage,
382 .unused = .{
383 .head = .fromIndex(0),
384 .tail = .fromIndex(storage.len - 1),
385 },
386 .submissions = .empty,
387 .pending = .empty,
388 .completions = .empty,
389 .context = null,
390 };
391 }
392
393 /// Adds an operation to be performed at the next await call.
394 /// Returns the index that will be returned by `next` after the operation completes.
395 /// Asserts that no more than `storage.len` operations are active at a time.
396 pub fn add(b: *Batch, operation: Operation) u32 {
397 const index = b.unused.next;
398 b.addAt(index.toIndex(), operation);
399 return index;
400 }
401
402 /// Adds an operation to be performed at the next await call.
403 /// After the operation completes, `next` will return `index`.
404 /// Asserts that the operation at `index` is not active.
405 pub fn addAt(b: *Batch, index: u32, operation: Operation) void {
406 const storage = &b.storage[index];
407 const unused = storage.unused;
408 switch (unused.prev) {
409 .none => b.unused.head = .none,
410 else => |prev_index| b.storage[prev_index.toIndex()].unused.next = unused.next,
411 }
412 switch (unused.next) {
413 .none => b.unused.tail = .none,
414 else => |next_index| b.storage[next_index.toIndex()].unused.prev = unused.prev,
415 }
416
417 switch (b.submissions.tail) {
418 .none => b.submissions.head = .fromIndex(index),
419 else => |tail_index| b.storage[tail_index.toIndex()].submission.node.next = .fromIndex(index),
420 }
421 storage.* = .{ .submission = .{ .node = .{ .next = .none }, .operation = operation } };
422 b.submissions.tail = .fromIndex(index);
423 }
424
425 /// After calling `awaitAsync`, `awaitConcurrent`, or `cancel`, this
426 /// function iterates over the completed operations.
427 ///
428 /// Each completion returned from this function dequeues from the `Batch`.
429 /// It is not required to dequeue all completions before awaiting again.
430 pub fn next(b: *Batch) ?struct { index: u32, result: Operation.Result } {
431 const index = b.completions.head;
432 if (index == .none) return null;
433 const storage = &b.storage[index.toIndex()];
434 const completion = storage.completion;
435 const next_index = completion.node.next;
436 b.completions.head = next_index;
437 if (next_index == .none) b.completions.tail = .none;
438
439 const tail_index = b.unused.tail;
440 switch (tail_index) {
441 .none => b.unused.head = index,
442 else => b.storage[tail_index.toIndex()].unused.next = index,
443 }
444 storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } };
445 b.unused.tail = index;
446 return .{ .index = index.toIndex(), .result = completion.result };
447 }
448
449 /// Waits for at least one of the submitted operations to complete. After
450 /// this function returns the completed operations can be iterated with
451 /// `next`.
452 ///
453 /// This function provides opportunity for the implementation to introduce
454 /// concurrency into the batched operations, but unlike `awaitConcurrent`,
455 /// does not require it, and therefore cannot fail with
456 /// `error.ConcurrencyUnavailable`.
457 pub fn awaitAsync(b: *Batch, io: Io) Cancelable!void {
458 return io.vtable.batchAwaitAsync(io.userdata, b);
459 }
460
461 pub const AwaitConcurrentError = ConcurrentError || Cancelable || Timeout.Error;
462
463 /// Waits for at least one of the submitted operations to complete. After
464 /// this function returns the completed operations can be iterated with
465 /// `next`.
466 ///
467 /// Unlike `awaitAsync`, this function requires the implementation to
468 /// perform the operations concurrently and therefore can fail with
469 /// `error.ConcurrencyUnavailable`.
470 pub fn awaitConcurrent(b: *Batch, io: Io, timeout: Timeout) AwaitConcurrentError!void {
471 return io.vtable.batchAwaitConcurrent(io.userdata, b, timeout);
472 }
473
474 /// Requests all pending operations to be interrupted, then waits for all
475 /// pending operations to complete. After this returns, the `Batch` is in a
476 /// well-defined state, ready to be iterated with `next`. Successfully
477 /// canceled operations will be absent from the iteration. Some operations
478 /// may have successfully completed regardless of the cancel request and
479 /// will appear in the iteration.
480 pub fn cancel(b: *Batch, io: Io) void {
481 return io.vtable.batchCancel(io.userdata, b);
482 }
483};
484
705pub const Limit = enum(usize) {485pub const Limit = enum(usize) {
706 nothing = 0,486 nothing = 0,
707 unlimited = std.math.maxInt(usize),487 unlimited = math.maxInt(usize),
708 _,488 _,
709489
710 /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`.490 /// `math.maxInt(usize)` is interpreted to mean `.unlimited`.
711 pub fn limited(n: usize) Limit {491 pub fn limited(n: usize) Limit {
712 return @enumFromInt(n);492 return @enumFromInt(n);
713 }493 }
714494
715 /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean495 /// Any value grater than `math.maxInt(usize)` is interpreted to mean
716 /// `.unlimited`.496 /// `.unlimited`.
717 pub fn limited64(n: u64) Limit {497 pub fn limited64(n: u64) Limit {
718 return @enumFromInt(@min(n, std.math.maxInt(usize)));498 return @enumFromInt(@min(n, math.maxInt(usize)));
719 }499 }
720500
721 pub fn countVec(data: []const []const u8) Limit {501 pub fn countVec(data: []const []const u8) Limit {
...@@ -929,9 +709,9 @@ pub const Clock = enum {...@@ -929,9 +709,9 @@ pub const Clock = enum {
929 };709 };
930 }710 }
931711
932 pub fn compare(lhs: Clock.Timestamp, op: std.math.CompareOperator, rhs: Clock.Timestamp) bool {712 pub fn compare(lhs: Clock.Timestamp, op: math.CompareOperator, rhs: Clock.Timestamp) bool {
933 assert(lhs.clock == rhs.clock);713 assert(lhs.clock == rhs.clock);
934 return std.math.compare(lhs.raw.nanoseconds, op, rhs.raw.nanoseconds);714 return math.compare(lhs.raw.nanoseconds, op, rhs.raw.nanoseconds);
935 }715 }
936 };716 };
937717
...@@ -996,7 +776,7 @@ pub const Duration = struct {...@@ -996,7 +776,7 @@ pub const Duration = struct {
996 nanoseconds: i96,776 nanoseconds: i96,
997777
998 pub const zero: Duration = .{ .nanoseconds = 0 };778 pub const zero: Duration = .{ .nanoseconds = 0 };
999 pub const max: Duration = .{ .nanoseconds = std.math.maxInt(i96) };779 pub const max: Duration = .{ .nanoseconds = math.maxInt(i96) };
1000780
1001 pub fn fromNanoseconds(x: i96) Duration {781 pub fn fromNanoseconds(x: i96) Duration {
1002 return .{ .nanoseconds = x };782 return .{ .nanoseconds = x };
...@@ -1652,7 +1432,7 @@ pub const Event = enum(u32) {...@@ -1652,7 +1432,7 @@ pub const Event = enum(u32) {
1652 pub fn set(e: *Event, io: Io) void {1432 pub fn set(e: *Event, io: Io) void {
1653 switch (@atomicRmw(Event, e, .Xchg, .is_set, .release)) {1433 switch (@atomicRmw(Event, e, .Xchg, .is_set, .release)) {
1654 .unset, .is_set => {},1434 .unset, .is_set => {},
1655 .waiting => io.futexWake(Event, e, std.math.maxInt(u32)),1435 .waiting => io.futexWake(Event, e, math.maxInt(u32)),
1656 }1436 }
1657 }1437 }
16581438
lib/std/Io/File.zig+29-3
...@@ -10,6 +10,18 @@ const assert = std.debug.assert;...@@ -10,6 +10,18 @@ const assert = std.debug.assert;
10const Dir = std.Io.Dir;10const Dir = std.Io.Dir;
1111
12handle: Handle,12handle: Handle,
13flags: Flags,
14
15pub const Flags = struct {
16 /// * true:
17 /// - windows: opened with MODE.IO.ASYNCHRONOUS
18 /// - POSIX: O_NONBLOCK is set
19 /// * false:
20 /// - windows: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or
21 /// not a file.
22 /// - POSIX: O_NONBLOCK is unset
23 nonblocking: bool,
24};
1325
14pub const Handle = std.posix.fd_t;26pub const Handle = std.posix.fd_t;
1527
...@@ -18,6 +30,9 @@ pub const Writer = @import("File/Writer.zig");...@@ -18,6 +30,9 @@ pub const Writer = @import("File/Writer.zig");
18pub const Atomic = @import("File/Atomic.zig");30pub const Atomic = @import("File/Atomic.zig");
19/// Memory intended to remain consistent with file contents.31/// Memory intended to remain consistent with file contents.
20pub const MemoryMap = @import("File/MemoryMap.zig");32pub const MemoryMap = @import("File/MemoryMap.zig");
33/// Concurrently read from multiple file streams, eliminating risk of
34/// deadlocking.
35pub const MultiReader = @import("File/MultiReader.zig");
2136
22pub const INode = std.posix.ino_t;37pub const INode = std.posix.ino_t;
23pub const NLink = std.posix.nlink_t;38pub const NLink = std.posix.nlink_t;
...@@ -77,9 +92,11 @@ pub fn stdout() File {...@@ -77,9 +92,11 @@ pub fn stdout() File {
77 return switch (native_os) {92 return switch (native_os) {
78 .windows => .{93 .windows => .{
79 .handle = std.os.windows.peb().ProcessParameters.hStdOutput,94 .handle = std.os.windows.peb().ProcessParameters.hStdOutput,
95 .flags = .{ .nonblocking = false },
80 },96 },
81 else => .{97 else => .{
82 .handle = std.posix.STDOUT_FILENO,98 .handle = std.posix.STDOUT_FILENO,
99 .flags = .{ .nonblocking = false },
83 },100 },
84 };101 };
85}102}
...@@ -88,9 +105,11 @@ pub fn stderr() File {...@@ -88,9 +105,11 @@ pub fn stderr() File {
88 return switch (native_os) {105 return switch (native_os) {
89 .windows => .{106 .windows => .{
90 .handle = std.os.windows.peb().ProcessParameters.hStdError,107 .handle = std.os.windows.peb().ProcessParameters.hStdError,
108 .flags = .{ .nonblocking = false },
91 },109 },
92 else => .{110 else => .{
93 .handle = std.posix.STDERR_FILENO,111 .handle = std.posix.STDERR_FILENO,
112 .flags = .{ .nonblocking = false },
94 },113 },
95 };114 };
96}115}
...@@ -99,9 +118,11 @@ pub fn stdin() File {...@@ -99,9 +118,11 @@ pub fn stdin() File {
99 return switch (native_os) {118 return switch (native_os) {
100 .windows => .{119 .windows => .{
101 .handle = std.os.windows.peb().ProcessParameters.hStdInput,120 .handle = std.os.windows.peb().ProcessParameters.hStdInput,
121 .flags = .{ .nonblocking = false },
102 },122 },
103 else => .{123 else => .{
104 .handle = std.posix.STDIN_FILENO,124 .handle = std.posix.STDIN_FILENO,
125 .flags = .{ .nonblocking = false },
105 },126 },
106 };127 };
107}128}
...@@ -549,12 +570,18 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {...@@ -549,12 +570,18 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void {
549 });570 });
550}571}
551572
573pub const ReadStreamingError = error{EndOfStream} || Reader.Error;
574
552/// Returns 0 on stream end or if `buffer` has no space available for data.575/// Returns 0 on stream end or if `buffer` has no space available for data.
553///576///
554/// See also:577/// See also:
555/// * `reader`578/// * `reader`
556pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize {579pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize {
557 return io.vtable.fileReadStreaming(io.userdata, file, buffer);580 const result = try io.operate(.{ .file_read_streaming = .{
581 .file = file,
582 .data = buffer,
583 } });
584 return result.file_read_streaming;
558}585}
559586
560pub const ReadPositionalError = error{587pub const ReadPositionalError = error{
...@@ -562,7 +589,6 @@ pub const ReadPositionalError = error{...@@ -562,7 +589,6 @@ pub const ReadPositionalError = error{
562 SystemResources,589 SystemResources,
563 /// Trying to read a directory file descriptor as if it were a file.590 /// Trying to read a directory file descriptor as if it were a file.
564 IsDir,591 IsDir,
565 BrokenPipe,
566 /// Non-blocking has been enabled, and reading from the file descriptor592 /// Non-blocking has been enabled, and reading from the file descriptor
567 /// would block.593 /// would block.
568 WouldBlock,594 WouldBlock,
lib/std/Io/File/MultiReader.zig created+269
...@@ -0,0 +1,269 @@
1const MultiReader = @This();
2
3const std = @import("../../std.zig");
4const Io = std.Io;
5const File = Io.File;
6const Allocator = std.mem.Allocator;
7const assert = std.debug.assert;
8
9gpa: Allocator,
10streams: *Streams,
11batch: Io.Batch,
12
13pub const Context = struct {
14 mr: *MultiReader,
15 fr: File.Reader,
16 vec: [1][]u8,
17 err: ?Error,
18};
19
20pub const Error = UnendingError || error{EndOfStream};
21pub const UnendingError = Allocator.Error || File.Reader.Error || Io.ConcurrentError;
22
23/// Trailing:
24/// * `contexts: [len]Context`
25/// * `storage: [len]Io.Operation.Storage`
26pub const Streams = extern struct {
27 len: u32,
28
29 pub fn contexts(s: *Streams) []Context {
30 const base: usize = @intFromPtr(s);
31 const ptr: [*]Context = @ptrFromInt(std.mem.alignForward(usize, base + @sizeOf(Streams), @alignOf(Context)));
32 return ptr[0..s.len];
33 }
34
35 pub fn storage(s: *Streams) []Io.Operation.Storage {
36 const prev = contexts(s);
37 const end = prev.ptr + prev.len;
38 const ptr: [*]Io.Operation.Storage = @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(end), @alignOf(Io.Operation.Storage)));
39 return ptr[0..s.len];
40 }
41};
42
43pub fn Buffer(comptime n: usize) type {
44 return extern struct {
45 len: u32,
46 contexts: [n][@sizeOf(Context)]u8 align(@alignOf(Context)),
47 storage: [n][@sizeOf(Io.Operation.Storage)]u8 align(@alignOf(Io.Operation.Storage)),
48
49 pub fn toStreams(b: *@This()) *Streams {
50 b.len = n;
51 return @ptrCast(b);
52 }
53 };
54}
55
56/// See `Streams.Buffer` for convenience API to obtain the `streams` parameter.
57pub fn init(mr: *MultiReader, gpa: Allocator, io: Io, streams: *Streams, files: []const File) void {
58 const contexts = streams.contexts();
59 for (contexts, files) |*context, file| context.* = .{
60 .mr = mr,
61 .fr = .{
62 .io = io,
63 .file = file,
64 .mode = .streaming,
65 .interface = .{
66 .vtable = &.{
67 .stream = stream,
68 .discard = discard,
69 .readVec = readVec,
70 .rebase = rebase,
71 },
72 .buffer = &.{},
73 .seek = 0,
74 .end = 0,
75 },
76 },
77 .vec = .{&.{}},
78 .err = null,
79 };
80 mr.* = .{
81 .gpa = gpa,
82 .streams = streams,
83 .batch = .init(streams.storage()),
84 };
85 for (contexts, 0..) |*context, i| {
86 const r = &context.fr.interface;
87 rebaseGrowing(mr, context, 1) catch |err| {
88 context.err = err;
89 continue;
90 };
91 context.vec[0] = r.buffer;
92 mr.batch.addAt(@intCast(i), .{ .file_read_streaming = .{
93 .file = context.fr.file,
94 .data = &context.vec,
95 } });
96 }
97}
98
99pub fn deinit(mr: *MultiReader) void {
100 const gpa = mr.gpa;
101 const contexts = mr.streams.contexts();
102 const io = contexts[0].fr.io;
103 mr.batch.cancel(io);
104 for (contexts) |*context| {
105 gpa.free(context.fr.interface.buffer);
106 }
107}
108
109pub fn fileReader(mr: *MultiReader, index: usize) *File.Reader {
110 return &mr.streams.contexts()[index].fr;
111}
112
113pub fn reader(mr: *MultiReader, index: usize) *Io.Reader {
114 return &mr.streams.contexts()[index].fr.interface;
115}
116
117/// Checks for errors in all streams, prioritizing `error.Canceled` if it
118/// occurred anywhere, and ignoring `error.EndOfStream`.
119pub fn checkAnyError(mr: *const MultiReader) UnendingError!void {
120 const contexts = mr.streams.contexts();
121 var other: UnendingError!void = {};
122 for (contexts) |*context| {
123 if (context.err) |err| switch (err) {
124 error.Canceled => |e| return e,
125 error.EndOfStream => continue,
126 else => |e| other = e,
127 };
128 }
129 return other;
130}
131
132pub fn toOwnedSlice(mr: *MultiReader, index: usize) Allocator.Error![]u8 {
133 const gpa = mr.gpa;
134 const r: *Io.Reader = reader(mr, index);
135 if (r.seek == 0) {
136 const new = try gpa.realloc(r.buffer, r.end);
137 r.buffer = &.{};
138 r.end = 0;
139 return new;
140 }
141 const new = try gpa.dupe(u8, r.buffered());
142 gpa.free(r.buffer);
143 r.buffer = &.{};
144 r.seek = 0;
145 r.end = 0;
146 return new;
147}
148
149fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize {
150 _ = limit;
151 _ = w;
152 const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r));
153 const context: *Context = @fieldParentPtr("fr", fr);
154 try fillUntimed(context, 1);
155 return 0;
156}
157
158fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
159 _ = limit;
160 const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r));
161 const context: *Context = @fieldParentPtr("fr", fr);
162 try fillUntimed(context, 1);
163 return 0;
164}
165
166fn readVec(r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize {
167 _ = data;
168 const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r));
169 const context: *Context = @fieldParentPtr("fr", fr);
170 try fillUntimed(context, 1);
171 return 0;
172}
173
174fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void {
175 const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r));
176 const context: *Context = @fieldParentPtr("fr", fr);
177 try fillUntimed(context, capacity);
178}
179
180fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void {
181 fill(context.mr, capacity, .none) catch |err| switch (err) {
182 error.Timeout, error.UnsupportedClock => unreachable,
183 error.Canceled, error.ConcurrencyUnavailable => |e| {
184 context.err = e;
185 return error.ReadFailed;
186 },
187 error.EndOfStream => |e| return e,
188 };
189 if (context.err) |err| switch (err) {
190 error.EndOfStream => |e| return e,
191 else => return error.ReadFailed,
192 };
193}
194
195pub const FillError = Io.Batch.AwaitConcurrentError || error{
196 /// `fill` was called when all streams already have failed or reached the
197 /// end.
198 EndOfStream,
199};
200
201/// Wait until at least one stream receives more data.
202pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillError!void {
203 const contexts = mr.streams.contexts();
204 const io = contexts[0].fr.io;
205 var any_completed = false;
206
207 try mr.batch.awaitConcurrent(io, timeout);
208
209 while (mr.batch.next()) |operation| {
210 any_completed = true;
211 const context = &contexts[operation.index];
212 const n = operation.result.file_read_streaming catch |err| {
213 context.err = err;
214 continue;
215 };
216 const r = &context.fr.interface;
217 r.end += n;
218 if (r.buffer.len - r.end < unused_capacity) {
219 rebaseGrowing(mr, context, r.bufferedLen() + unused_capacity) catch |err| {
220 context.err = err;
221 continue;
222 };
223 assert(r.seek == 0);
224 }
225 context.vec[0] = r.buffer[r.end..];
226 mr.batch.addAt(operation.index, .{ .file_read_streaming = .{
227 .file = context.fr.file,
228 .data = &context.vec,
229 } });
230 }
231
232 if (!any_completed) return error.EndOfStream;
233}
234
235/// Wait until all streams fail or reach the end.
236pub fn fillRemaining(mr: *MultiReader, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void {
237 while (fill(mr, 1, timeout)) |_| {} else |err| switch (err) {
238 error.EndOfStream => return,
239 else => |e| return e,
240 }
241}
242
243fn rebaseGrowing(mr: *MultiReader, context: *Context, capacity: usize) Allocator.Error!void {
244 const gpa = mr.gpa;
245 const r = &context.fr.interface;
246 if (r.buffer.len >= capacity) {
247 const data = r.buffer[r.seek..r.end];
248 @memmove(r.buffer[0..data.len], data);
249 r.seek = 0;
250 r.end = data.len;
251 } else {
252 const adjusted_capacity = std.ArrayList(u8).growCapacity(capacity);
253
254 if (r.seek == 0) {
255 if (gpa.remap(r.buffer, adjusted_capacity)) |new_memory| {
256 r.buffer = new_memory;
257 return;
258 }
259 }
260
261 const data = r.buffer[r.seek..r.end];
262 const new = try gpa.alloc(u8, adjusted_capacity);
263 @memcpy(new[0..data.len], data);
264 gpa.free(r.buffer);
265 r.buffer = new;
266 r.seek = 0;
267 r.end = data.len;
268 }
269}
lib/std/Io/File/Reader.zig+19-35
...@@ -26,27 +26,7 @@ size_err: ?SizeError = null,...@@ -26,27 +26,7 @@ size_err: ?SizeError = null,
26seek_err: ?SeekError = null,26seek_err: ?SeekError = null,
27interface: Io.Reader,27interface: Io.Reader,
2828
29pub const Error = error{29pub const Error = Io.Operation.FileReadStreaming.UnendingError || Io.Cancelable;
30 InputOutput,
31 SystemResources,
32 /// Trying to read a directory file descriptor as if it were a file.
33 IsDir,
34 BrokenPipe,
35 ConnectionResetByPeer,
36 /// File was not opened with read capability.
37 NotOpenForReading,
38 SocketUnconnected,
39 /// Non-blocking has been enabled, and reading from the file descriptor
40 /// would block.
41 WouldBlock,
42 /// In WASI, this error occurs when the file descriptor does
43 /// not hold the required rights to read from it.
44 AccessDenied,
45 /// Unable to read file due to lock. Depending on the `Io` implementation,
46 /// reading from a locked file may return this error, or may ignore the
47 /// lock.
48 LockViolation,
49} || Io.Cancelable || Io.UnexpectedError;
5030
51pub const SizeError = File.StatError || error{31pub const SizeError = File.StatError || error{
52 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.32 /// Occurs if, for example, the file handle is a network socket and therefore does not have a size.
...@@ -300,14 +280,16 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {...@@ -300,14 +280,16 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize {
300 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);280 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data);
301 const dest = iovecs_buffer[0..dest_n];281 const dest = iovecs_buffer[0..dest_n];
302 assert(dest[0].len > 0);282 assert(dest[0].len > 0);
303 const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| {283 const n = r.file.readStreaming(io, dest) catch |err| switch (err) {
304 r.err = err;284 error.EndOfStream => {
305 return error.ReadFailed;285 r.size = r.pos;
286 return error.EndOfStream;
287 },
288 else => |e| {
289 r.err = e;
290 return error.ReadFailed;
291 },
306 };292 };
307 if (n == 0) {
308 r.size = r.pos;
309 return error.EndOfStream;
310 }
311 r.pos += n;293 r.pos += n;
312 if (n > data_size) {294 if (n > data_size) {
313 r.interface.end += n - data_size;295 r.interface.end += n - data_size;
...@@ -355,14 +337,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {...@@ -355,14 +337,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize {
355 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);337 const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data);
356 const dest = iovecs_buffer[0..dest_n];338 const dest = iovecs_buffer[0..dest_n];
357 assert(dest[0].len > 0);339 assert(dest[0].len > 0);
358 const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| {340 const n = file.readStreaming(io, dest) catch |err| switch (err) {
359 r.err = err;341 error.EndOfStream => {
360 return error.ReadFailed;342 r.size = r.pos;
343 return error.EndOfStream;
344 },
345 else => |e| {
346 r.err = e;
347 return error.ReadFailed;
348 },
361 };349 };
362 if (n == 0) {
363 r.size = r.pos;
364 return error.EndOfStream;
365 }
366 r.pos += n;350 r.pos += n;
367 if (n > data_size) {351 if (n > data_size) {
368 r.interface.end += n - data_size;352 r.interface.end += n - data_size;
lib/std/Io/Reader.zig+24-5
...@@ -127,9 +127,7 @@ pub const ShortError = error{...@@ -127,9 +127,7 @@ pub const ShortError = error{
127 ReadFailed,127 ReadFailed,
128};128};
129129
130pub const RebaseError = error{130pub const RebaseError = Error;
131 EndOfStream,
132};
133131
134pub const failing: Reader = .{132pub const failing: Reader = .{
135 .vtable = &.{133 .vtable = &.{
...@@ -315,6 +313,27 @@ pub fn allocRemainingAlignedSentinel(...@@ -315,6 +313,27 @@ pub fn allocRemainingAlignedSentinel(
315 }313 }
316}314}
317315
316pub const AppendExactError = Allocator.Error || Error;
317
318/// Transfers exactly `n` bytes from the reader to the `ArrayList`.
319///
320/// See also:
321/// * `appendRemaining`
322pub fn appendExact(
323 r: *Reader,
324 gpa: Allocator,
325 list: *ArrayList(u8),
326 n: usize,
327) AppendExactError!void {
328 try list.ensureUnusedCapacity(gpa, n);
329 var a = std.Io.Writer.Allocating.fromArrayList(gpa, list);
330 defer list.* = a.toArrayList();
331 streamExact(r, &a.writer, n) catch |err| switch (err) {
332 error.ReadFailed, error.EndOfStream => |e| return e,
333 error.WriteFailed => unreachable,
334 };
335}
336
318/// Transfers all bytes from the current position to the end of the stream, up337/// Transfers all bytes from the current position to the end of the stream, up
319/// to `limit`, appending them to `list`.338/// to `limit`, appending them to `list`.
320///339///
...@@ -1381,7 +1400,7 @@ pub fn takeLeb128(r: *Reader, comptime T: type) TakeLeb128Error!T {...@@ -1381,7 +1400,7 @@ pub fn takeLeb128(r: *Reader, comptime T: type) TakeLeb128Error!T {
1381}1400}
13821401
1383/// Ensures `capacity` data can be buffered without rebasing.1402/// Ensures `capacity` data can be buffered without rebasing.
1384pub fn rebase(r: *Reader, capacity: usize) RebaseError!void {1403pub fn rebase(r: *Reader, capacity: usize) Error!void {
1385 if (r.buffer.len - r.seek >= capacity) {1404 if (r.buffer.len - r.seek >= capacity) {
1386 @branchHint(.likely);1405 @branchHint(.likely);
1387 return;1406 return;
...@@ -1389,7 +1408,7 @@ pub fn rebase(r: *Reader, capacity: usize) RebaseError!void {...@@ -1389,7 +1408,7 @@ pub fn rebase(r: *Reader, capacity: usize) RebaseError!void {
1389 return r.vtable.rebase(r, capacity);1408 return r.vtable.rebase(r, capacity);
1390}1409}
13911410
1392pub fn defaultRebase(r: *Reader, capacity: usize) RebaseError!void {1411pub fn defaultRebase(r: *Reader, capacity: usize) Error!void {
1393 assert(r.buffer.len - r.seek < capacity);1412 assert(r.buffer.len - r.seek < capacity);
1394 const data = r.buffer[r.seek..r.end];1413 const data = r.buffer[r.seek..r.end];
1395 @memmove(r.buffer[0..data.len], data);1414 @memmove(r.buffer[0..data.len], data);
lib/std/Io/Threaded.zig+702-93
...@@ -1255,6 +1255,32 @@ const AlertableSyscall = struct {...@@ -1255,6 +1255,32 @@ const AlertableSyscall = struct {
1255 assert(is_windows);1255 assert(is_windows);
1256 }1256 }
12571257
1258 fn start() Io.Cancelable!AlertableSyscall {
1259 const thread = Thread.current orelse return .{ .thread = null };
1260 switch (thread.cancel_protection) {
1261 .blocked => return .{ .thread = null },
1262 .unblocked => {},
1263 }
1264 const old_status = thread.status.fetchOr(.{
1265 .cancelation = @enumFromInt(0b010),
1266 .awaitable = .null,
1267 }, .monotonic);
1268 switch (old_status.cancelation) {
1269 .parked => unreachable,
1270 .blocked => unreachable,
1271 .blocked_alertable => unreachable,
1272 .blocked_canceling => unreachable,
1273 .blocked_alertable_canceling => unreachable,
1274 .none => return .{ .thread = thread }, // new status is `.blocked_alertable`
1275 .canceling => {
1276 // Status is unchanged (still `.canceling`)---change to `.canceled` before return.
1277 thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic);
1278 return error.Canceled;
1279 },
1280 .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged)
1281 }
1282 }
1283
1258 fn checkCancel(s: AlertableSyscall) Io.Cancelable!void {1284 fn checkCancel(s: AlertableSyscall) Io.Cancelable!void {
1259 comptime assert(is_windows);1285 comptime assert(is_windows);
1260 const thread = s.thread orelse return;1286 const thread = s.thread orelse return;
...@@ -1314,8 +1340,17 @@ const AlertableSyscall = struct {...@@ -1314,8 +1340,17 @@ const AlertableSyscall = struct {
1314 }1340 }
1315};1341};
13161342
1343fn waitForApcOrAlert() void {
1344 const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER);
1345 _ = windows.ntdll.NtDelayExecution(windows.TRUE, &infinite_timeout);
1346}
1347
1317const max_iovecs_len = 8;1348const max_iovecs_len = 8;
1318const splat_buffer_size = 64;1349const splat_buffer_size = 64;
1350/// Happens to be the same number that matches maximum number of handles that
1351/// NtWaitForMultipleObjects accepts. We use this value also for poll() on
1352/// posix systems.
1353const poll_buffer_len = 64;
1319const default_PATH = "/usr/local/bin:/bin/:/usr/bin";1354const default_PATH = "/usr/local/bin:/bin/:/usr/bin";
13201355
1321comptime {1356comptime {
...@@ -1579,6 +1614,11 @@ pub fn io(t: *Threaded) Io {...@@ -1579,6 +1614,11 @@ pub fn io(t: *Threaded) Io {
1579 .futexWaitUncancelable = futexWaitUncancelable,1614 .futexWaitUncancelable = futexWaitUncancelable,
1580 .futexWake = futexWake,1615 .futexWake = futexWake,
15811616
1617 .operate = operate,
1618 .batchAwaitAsync = batchAwaitAsync,
1619 .batchAwaitConcurrent = batchAwaitConcurrent,
1620 .batchCancel = batchCancel,
1621
1582 .dirCreateDir = dirCreateDir,1622 .dirCreateDir = dirCreateDir,
1583 .dirCreateDirPath = dirCreateDirPath,1623 .dirCreateDirPath = dirCreateDirPath,
1584 .dirCreateDirPathOpen = dirCreateDirPathOpen,1624 .dirCreateDirPathOpen = dirCreateDirPathOpen,
...@@ -1613,7 +1653,6 @@ pub fn io(t: *Threaded) Io {...@@ -1613,7 +1653,6 @@ pub fn io(t: *Threaded) Io {
1613 .fileWritePositional = fileWritePositional,1653 .fileWritePositional = fileWritePositional,
1614 .fileWriteFileStreaming = fileWriteFileStreaming,1654 .fileWriteFileStreaming = fileWriteFileStreaming,
1615 .fileWriteFilePositional = fileWriteFilePositional,1655 .fileWriteFilePositional = fileWriteFilePositional,
1616 .fileReadStreaming = fileReadStreaming,
1617 .fileReadPositional = fileReadPositional,1656 .fileReadPositional = fileReadPositional,
1618 .fileSeekBy = fileSeekBy,1657 .fileSeekBy = fileSeekBy,
1619 .fileSeekTo = fileSeekTo,1658 .fileSeekTo = fileSeekTo,
...@@ -1739,6 +1778,11 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1739,6 +1778,11 @@ pub fn ioBasic(t: *Threaded) Io {
1739 .futexWaitUncancelable = futexWaitUncancelable,1778 .futexWaitUncancelable = futexWaitUncancelable,
1740 .futexWake = futexWake,1779 .futexWake = futexWake,
17411780
1781 .operate = operate,
1782 .batchAwaitAsync = batchAwaitAsync,
1783 .batchAwaitConcurrent = batchAwaitConcurrent,
1784 .batchCancel = batchCancel,
1785
1742 .dirCreateDir = dirCreateDir,1786 .dirCreateDir = dirCreateDir,
1743 .dirCreateDirPath = dirCreateDirPath,1787 .dirCreateDirPath = dirCreateDirPath,
1744 .dirCreateDirPathOpen = dirCreateDirPathOpen,1788 .dirCreateDirPathOpen = dirCreateDirPathOpen,
...@@ -1773,7 +1817,6 @@ pub fn ioBasic(t: *Threaded) Io {...@@ -1773,7 +1817,6 @@ pub fn ioBasic(t: *Threaded) Io {
1773 .fileWritePositional = fileWritePositional,1817 .fileWritePositional = fileWritePositional,
1774 .fileWriteFileStreaming = fileWriteFileStreaming,1818 .fileWriteFileStreaming = fileWriteFileStreaming,
1775 .fileWriteFilePositional = fileWriteFilePositional,1819 .fileWriteFilePositional = fileWriteFilePositional,
1776 .fileReadStreaming = fileReadStreaming,
1777 .fileReadPositional = fileReadPositional,1820 .fileReadPositional = fileReadPositional,
1778 .fileSeekBy = fileSeekBy,1821 .fileSeekBy = fileSeekBy,
1779 .fileSeekTo = fileSeekTo,1822 .fileSeekTo = fileSeekTo,
...@@ -2440,6 +2483,485 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {...@@ -2440,6 +2483,485 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void {
2440 Thread.futexWake(ptr, max_waiters);2483 Thread.futexWake(ptr, max_waiters);
2441}2484}
24422485
2486fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result {
2487 const t: *Threaded = @ptrCast(@alignCast(userdata));
2488 switch (operation) {
2489 .file_read_streaming => |o| return .{
2490 .file_read_streaming = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) {
2491 error.Canceled => |e| return e,
2492 else => |e| e,
2493 },
2494 },
2495 }
2496}
2497
2498fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void {
2499 const t: *Threaded = @ptrCast(@alignCast(userdata));
2500 if (is_windows) {
2501 batchAwaitWindows(b, false) catch |err| switch (err) {
2502 error.ConcurrencyUnavailable => unreachable, // passed concurrency=false
2503 else => |e| return e,
2504 };
2505 const alertable_syscall = try AlertableSyscall.start();
2506 while (b.pending.head != .none and b.completions.head == .none) waitForApcOrAlert();
2507 alertable_syscall.finish();
2508 return;
2509 }
2510 if (native_os == .wasi and !builtin.link_libc) @panic("TODO");
2511 var poll_buffer: [poll_buffer_len]posix.pollfd = undefined;
2512 var poll_len: u32 = 0;
2513 {
2514 var index = b.submissions.head;
2515 while (index != .none and poll_len < poll_buffer_len) {
2516 const submission = &b.storage[index.toIndex()].submission;
2517 switch (submission.operation) {
2518 .file_read_streaming => |o| {
2519 poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 };
2520 poll_len += 1;
2521 },
2522 }
2523 index = submission.node.next;
2524 }
2525 }
2526 switch (poll_len) {
2527 0 => return,
2528 1 => {},
2529 else => while (true) {
2530 const timeout_ms: i32 = t: {
2531 if (b.completions.head != .none) {
2532 // It is legal to call batchWait with already completed
2533 // operations in the ring. In such case, we need to avoid
2534 // blocking in the poll syscall, but we can still take this
2535 // opportunity to find additional ready operations.
2536 break :t 0;
2537 }
2538 const max_poll_ms = std.math.maxInt(i32);
2539 break :t max_poll_ms;
2540 };
2541 const syscall = try Syscall.start();
2542 const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms);
2543 syscall.finish();
2544 switch (posix.errno(rc)) {
2545 .SUCCESS => {
2546 if (rc == 0) {
2547 if (b.completions.head != .none) {
2548 // Since there are already completions available in the
2549 // queue, this is neither a timeout nor a case for
2550 // retrying.
2551 return;
2552 }
2553 continue;
2554 }
2555 var prev_index: Io.Operation.OptionalIndex = .none;
2556 var index = b.submissions.head;
2557 for (poll_buffer[0..poll_len]) |poll_entry| {
2558 const storage = &b.storage[index.toIndex()];
2559 const submission = &storage.submission;
2560 const next_index = submission.node.next;
2561 if (poll_entry.revents != 0) {
2562 const result = try operate(t, submission.operation);
2563
2564 switch (prev_index) {
2565 .none => b.submissions.head = next_index,
2566 else => b.storage[prev_index.toIndex()].submission.node.next = next_index,
2567 }
2568 if (next_index == .none) b.submissions.tail = prev_index;
2569
2570 switch (b.completions.tail) {
2571 .none => b.completions.head = index,
2572 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2573 }
2574 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2575 b.completions.tail = index;
2576 } else prev_index = index;
2577 index = next_index;
2578 }
2579 assert(index == .none);
2580 return;
2581 },
2582 .INTR => continue,
2583 else => break,
2584 }
2585 },
2586 }
2587 {
2588 var tail_index = b.completions.tail;
2589 defer b.completions.tail = tail_index;
2590 var index = b.submissions.head;
2591 errdefer b.submissions.head = index;
2592 while (index != .none) {
2593 const storage = &b.storage[index.toIndex()];
2594 const submission = &storage.submission;
2595 const next_index = submission.node.next;
2596 const result = try operate(t, submission.operation);
2597
2598 switch (tail_index) {
2599 .none => b.completions.head = index,
2600 else => b.storage[tail_index.toIndex()].completion.node.next = index,
2601 }
2602 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2603 tail_index = index;
2604 index = next_index;
2605 }
2606 b.submissions = .{ .head = .none, .tail = .none };
2607 }
2608}
2609
2610fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void {
2611 const t: *Threaded = @ptrCast(@alignCast(userdata));
2612 if (is_windows) {
2613 const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) {
2614 error.Unexpected => deadline: {
2615 recoverableOsBugDetected();
2616 break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake };
2617 },
2618 error.UnsupportedClock => |e| return e,
2619 };
2620 try batchAwaitWindows(b, true);
2621 while (b.pending.head != .none and b.completions.head == .none) {
2622 var delay_interval: windows.LARGE_INTEGER = interval: {
2623 const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER);
2624 break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) {
2625 error.UnsupportedClock => |e| return e,
2626 error.Unexpected => {
2627 recoverableOsBugDetected();
2628 break :interval -1;
2629 },
2630 };
2631 };
2632 const alertable_syscall = try AlertableSyscall.start();
2633 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);
2634 alertable_syscall.finish();
2635 switch (delay_rc) {
2636 .SUCCESS, .TIMEOUT => {
2637 // The thread woke due to the timeout. Although spurious
2638 // timeouts are OK, when no deadline is passed we must not
2639 // return `error.Timeout`.
2640 if (timeout != .none and b.completions.head == .none) return error.Timeout;
2641 },
2642 else => {},
2643 }
2644 }
2645 return;
2646 }
2647 if (native_os == .wasi and !builtin.link_libc) @panic("TODO");
2648 var poll_buffer: [poll_buffer_len]posix.pollfd = undefined;
2649 var poll_storage: struct {
2650 gpa: std.mem.Allocator,
2651 b: *Io.Batch,
2652 slice: []posix.pollfd,
2653 len: u32,
2654
2655 fn add(storage: *@This(), file: Io.File, events: @FieldType(posix.pollfd, "events")) Io.ConcurrentError!void {
2656 const len = storage.len;
2657 if (len == poll_buffer_len) {
2658 const slice: []posix.pollfd = if (storage.b.context) |context|
2659 @as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..storage.b.storage.len]
2660 else allocation: {
2661 const allocation = storage.gpa.alloc(posix.pollfd, storage.b.storage.len) catch
2662 return error.ConcurrencyUnavailable;
2663 storage.b.context = allocation.ptr;
2664 break :allocation allocation;
2665 };
2666 @memcpy(slice[0..poll_buffer_len], storage.slice);
2667 }
2668 storage.slice[len] = .{
2669 .fd = file.handle,
2670 .events = events,
2671 .revents = 0,
2672 };
2673 storage.len = len + 1;
2674 }
2675 } = .{ .gpa = t.allocator, .b = b, .slice = &poll_buffer, .len = 0 };
2676 {
2677 var index = b.submissions.head;
2678 while (index != .none) {
2679 const submission = &b.storage[index.toIndex()].submission;
2680 switch (submission.operation) {
2681 .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN),
2682 }
2683 index = submission.node.next;
2684 }
2685 }
2686 switch (poll_storage.len) {
2687 0 => return,
2688 1 => if (timeout == .none) {
2689 const index = b.submissions.head;
2690 const storage = &b.storage[index.toIndex()];
2691 const result = try operate(t, storage.submission.operation);
2692
2693 b.submissions = .{ .head = .none, .tail = .none };
2694
2695 switch (b.completions.tail) {
2696 .none => b.completions.head = index,
2697 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2698 }
2699 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2700 b.completions.tail = index;
2701 return;
2702 },
2703 else => {},
2704 }
2705 const t_io = ioBasic(t);
2706 const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock;
2707 while (true) {
2708 const timeout_ms: i32 = t: {
2709 if (b.completions.head != .none) {
2710 // It is legal to call batchWait with already completed
2711 // operations in the ring. In such case, we need to avoid
2712 // blocking in the poll syscall, but we can still take this
2713 // opportunity to find additional ready operations.
2714 break :t 0;
2715 }
2716 const d = deadline orelse break :t -1;
2717 const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock;
2718 if (duration.raw.nanoseconds <= 0) return error.Timeout;
2719 const max_poll_ms = std.math.maxInt(i32);
2720 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
2721 };
2722 const syscall = try Syscall.start();
2723 const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms);
2724 syscall.finish();
2725 switch (posix.errno(rc)) {
2726 .SUCCESS => {
2727 if (rc == 0) {
2728 if (b.completions.head != .none) {
2729 // Since there are already completions available in the
2730 // queue, this is neither a timeout nor a case for
2731 // retrying.
2732 return;
2733 }
2734 // Although spurious timeouts are OK, when no deadline is
2735 // passed we must not return `error.Timeout`.
2736 if (deadline == null) continue;
2737 return error.Timeout;
2738 }
2739 var prev_index: Io.Operation.OptionalIndex = .none;
2740 var index = b.submissions.head;
2741 for (poll_storage.slice[0..poll_storage.len]) |poll_entry| {
2742 const submission = &b.storage[index.toIndex()].submission;
2743 const next_index = submission.node.next;
2744 if (poll_entry.revents != 0) {
2745 const result = try operate(t, submission.operation);
2746
2747 switch (prev_index) {
2748 .none => b.submissions.head = next_index,
2749 else => b.storage[prev_index.toIndex()].submission.node.next = next_index,
2750 }
2751 if (next_index == .none) b.submissions.tail = prev_index;
2752
2753 switch (b.completions.tail) {
2754 .none => b.completions.head = index,
2755 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index,
2756 }
2757 b.completions.tail = index;
2758 b.storage[index.toIndex()] = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2759 } else prev_index = index;
2760 index = next_index;
2761 }
2762 assert(index == .none);
2763 return;
2764 },
2765 .INTR => continue,
2766 else => return error.ConcurrencyUnavailable,
2767 }
2768 }
2769}
2770
2771const WindowsBatchPendingOperationContext = extern struct {
2772 file: windows.HANDLE,
2773 iosb: windows.IO_STATUS_BLOCK,
2774
2775 const Erased = [3]usize;
2776
2777 comptime {
2778 assert(@sizeOf(Erased) <= @sizeOf(WindowsBatchPendingOperationContext));
2779 }
2780
2781 fn toErased(context: *WindowsBatchPendingOperationContext) *Erased {
2782 return @ptrCast(context);
2783 }
2784
2785 fn fromErased(erased: *Erased) *WindowsBatchPendingOperationContext {
2786 return @ptrCast(erased);
2787 }
2788};
2789
2790fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void {
2791 const t: *Threaded = @ptrCast(@alignCast(userdata));
2792 {
2793 var tail_index = b.unused.tail;
2794 defer b.unused.tail = tail_index;
2795 var index = b.submissions.head;
2796 errdefer b.submissions.head = index;
2797 while (index != .none) {
2798 const next_index = b.storage[index.toIndex()].submission.node.next;
2799 switch (tail_index) {
2800 .none => b.unused.head = index,
2801 else => b.storage[tail_index.toIndex()].unused.next = index,
2802 }
2803 b.storage[index.toIndex()] = .{ .unused = .{ .prev = tail_index, .next = .none } };
2804 tail_index = index;
2805 index = next_index;
2806 }
2807 b.submissions = .{ .head = .none, .tail = .none };
2808 }
2809 if (is_windows) {
2810 var index = b.pending.head;
2811 while (index != .none) {
2812 const pending = &b.storage[index.toIndex()].pending;
2813 const context: *WindowsBatchPendingOperationContext = .fromErased(&pending.context);
2814 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
2815 _ = windows.ntdll.NtCancelIoFileEx(context.file, &context.iosb, &cancel_iosb);
2816 index = pending.node.next;
2817 }
2818 while (b.pending.head != .none) waitForApcOrAlert();
2819 } else if (b.context) |context| {
2820 t.allocator.free(@as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..b.storage.len]);
2821 b.context = null;
2822 }
2823 assert(b.pending.head == .none);
2824}
2825
2826fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void {
2827 const b: *Io.Batch = @ptrCast(@alignCast(apc_context));
2828 const context: *WindowsBatchPendingOperationContext = @fieldParentPtr("iosb", iosb);
2829 const erased_context = context.toErased();
2830 const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("context", erased_context);
2831 switch (pending.node.prev) {
2832 .none => b.pending.head = pending.node.next,
2833 else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next,
2834 }
2835 switch (pending.node.next) {
2836 .none => b.pending.tail = pending.node.prev,
2837 else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev,
2838 }
2839 const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending);
2840 const index = storage - b.storage.ptr;
2841 switch (iosb.u.Status) {
2842 .CANCELLED => {
2843 const tail_index = b.unused.tail;
2844 switch (tail_index) {
2845 .none => b.unused.head = .fromIndex(index),
2846 else => b.storage[tail_index.toIndex()].unused.next = .fromIndex(index),
2847 }
2848 storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } };
2849 b.unused.tail = .fromIndex(index);
2850 },
2851 else => {
2852 switch (b.completions.tail) {
2853 .none => b.completions.head = .fromIndex(index),
2854 else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = .fromIndex(index),
2855 }
2856 b.completions.tail = .fromIndex(index);
2857 const result: Io.Operation.Result = switch (pending.tag) {
2858 .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) },
2859 };
2860 storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } };
2861 },
2862 }
2863}
2864
2865/// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable.
2866fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, ConcurrencyUnavailable }!void {
2867 var index = b.submissions.head;
2868 errdefer b.submissions.head = index;
2869 while (index != .none) {
2870 const storage = &b.storage[index.toIndex()];
2871 const submission = storage.submission;
2872 storage.* = .{ .pending = .{
2873 .node = .{ .prev = b.pending.tail, .next = .none },
2874 .tag = submission.operation,
2875 .context = undefined,
2876 } };
2877 switch (b.pending.tail) {
2878 .none => b.pending.head = index,
2879 else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index,
2880 }
2881 b.pending.tail = index;
2882 const context: *WindowsBatchPendingOperationContext = .fromErased(&storage.pending.context);
2883 errdefer {
2884 context.iosb.u.Status = .CANCELLED;
2885 batchApc(b, &context.iosb, 0);
2886 }
2887 switch (submission.operation) {
2888 .file_read_streaming => |o| o: {
2889 var data_index: usize = 0;
2890 while (o.data.len - data_index != 0 and o.data[data_index].len == 0) data_index += 1;
2891 if (o.data.len - data_index == 0) {
2892 context.iosb = .{
2893 .u = .{ .Status = .SUCCESS },
2894 .Information = 0,
2895 };
2896 batchApc(b, &context.iosb, 0);
2897 break :o;
2898 }
2899 const buffer = o.data[data_index];
2900 const short_buffer_len = @min(std.math.maxInt(u32), buffer.len);
2901
2902 if (o.file.flags.nonblocking) {
2903 context.file = o.file.handle;
2904 switch (windows.ntdll.NtReadFile(
2905 o.file.handle,
2906 null, // event
2907 &batchApc,
2908 b,
2909 &context.iosb,
2910 buffer.ptr,
2911 short_buffer_len,
2912 null, // byte offset
2913 null, // key
2914 )) {
2915 .PENDING, .SUCCESS => {},
2916 .CANCELLED => unreachable,
2917 else => |status| {
2918 context.iosb.u.Status = status;
2919 batchApc(b, &context.iosb, 0);
2920 },
2921 }
2922 } else {
2923 if (concurrency) return error.ConcurrencyUnavailable;
2924
2925 const syscall: Syscall = try .start();
2926 while (true) switch (windows.ntdll.NtReadFile(
2927 o.file.handle,
2928 null, // event
2929 null, // APC routine
2930 null, // APC context
2931 &context.iosb,
2932 buffer.ptr,
2933 short_buffer_len,
2934 null, // byte offset
2935 null, // key
2936 )) {
2937 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
2938 .CANCELLED => {
2939 try syscall.checkCancel();
2940 continue;
2941 },
2942 else => |status| {
2943 syscall.finish();
2944
2945 context.iosb.u.Status = status;
2946 batchApc(b, &context.iosb, 0);
2947 break;
2948 },
2949 };
2950 }
2951 },
2952 }
2953 index = submission.node.next;
2954 }
2955 b.submissions = .{ .head = .none, .tail = .none };
2956}
2957
2958fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void {
2959 const ct = complete_tail.*;
2960 const len: u31 = @intCast(ring.len);
2961 ring[ct.index(len)] = op;
2962 complete_tail.* = ct.next(len);
2963}
2964
2443const dirCreateDir = switch (native_os) {2965const dirCreateDir = switch (native_os) {
2444 .windows => dirCreateDirWindows,2966 .windows => dirCreateDirWindows,
2445 .wasi => dirCreateDirWasi,2967 .wasi => dirCreateDirWasi,
...@@ -2759,8 +3281,10 @@ fn dirCreateDirPathOpenWasi(...@@ -2759,8 +3281,10 @@ fn dirCreateDirPathOpenWasi(
27593281
2760fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {3282fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat {
2761 const t: *Threaded = @ptrCast(@alignCast(userdata));3283 const t: *Threaded = @ptrCast(@alignCast(userdata));
2762 const file: File = .{ .handle = dir.handle };3284 return fileStat(t, .{
2763 return fileStat(t, file);3285 .handle = dir.handle,
3286 .flags = .{ .nonblocking = false },
3287 });
2764}3288}
27653289
2766const dirStatFile = switch (native_os) {3290const dirStatFile = switch (native_os) {
...@@ -3552,7 +4076,10 @@ fn dirCreateFilePosix(...@@ -3552,7 +4076,10 @@ fn dirCreateFilePosix(
3552 }4076 }
3553 }4077 }
35544078
3555 return .{ .handle = fd };4079 return .{
4080 .handle = fd,
4081 .flags = .{ .nonblocking = false },
4082 };
3556}4083}
35574084
3558fn dirCreateFileWindows(4085fn dirCreateFileWindows(
...@@ -3682,7 +4209,10 @@ fn dirCreateFileWindows(...@@ -3682,7 +4209,10 @@ fn dirCreateFileWindows(
3682 errdefer windows.CloseHandle(handle);4209 errdefer windows.CloseHandle(handle);
36834210
3684 const exclusive = switch (flags.lock) {4211 const exclusive = switch (flags.lock) {
3685 .none => return .{ .handle = handle },4212 .none => return .{
4213 .handle = handle,
4214 .flags = .{ .nonblocking = false },
4215 },
3686 .shared => false,4216 .shared => false,
3687 .exclusive => true,4217 .exclusive => true,
3688 };4218 };
...@@ -3702,7 +4232,10 @@ fn dirCreateFileWindows(...@@ -3702,7 +4232,10 @@ fn dirCreateFileWindows(
3702 )) {4232 )) {
3703 .SUCCESS => {4233 .SUCCESS => {
3704 syscall.finish();4234 syscall.finish();
3705 return .{ .handle = handle };4235 return .{
4236 .handle = handle,
4237 .flags = .{ .nonblocking = false },
4238 };
3706 },4239 },
3707 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),4240 .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources),
3708 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),4241 .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock),
...@@ -3751,7 +4284,10 @@ fn dirCreateFileWasi(...@@ -3751,7 +4284,10 @@ fn dirCreateFileWasi(
3751 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {4284 switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) {
3752 .SUCCESS => {4285 .SUCCESS => {
3753 syscall.finish();4286 syscall.finish();
3754 return .{ .handle = fd };4287 return .{
4288 .handle = fd,
4289 .flags = .{ .nonblocking = false },
4290 };
3755 },4291 },
3756 .INTR => {4292 .INTR => {
3757 try syscall.checkCancel();4293 try syscall.checkCancel();
...@@ -3846,7 +4382,10 @@ fn dirCreateFileAtomic(...@@ -3846,7 +4382,10 @@ fn dirCreateFileAtomic(
3846 .SUCCESS => {4382 .SUCCESS => {
3847 syscall.finish();4383 syscall.finish();
3848 return .{4384 return .{
3849 .file = .{ .handle = @intCast(rc) },4385 .file = .{
4386 .handle = @intCast(rc),
4387 .flags = .{ .nonblocking = false },
4388 },
3850 .file_basename_hex = 0,4389 .file_basename_hex = 0,
3851 .dest_sub_path = dest_path,4390 .dest_sub_path = dest_path,
3852 .file_open = true,4391 .file_open = true,
...@@ -4054,7 +4593,10 @@ fn dirOpenFilePosix(...@@ -4054,7 +4593,10 @@ fn dirOpenFilePosix(
40544593
4055 if (!flags.allow_directory) {4594 if (!flags.allow_directory) {
4056 const is_dir = is_dir: {4595 const is_dir = is_dir: {
4057 const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {4596 const stat = fileStat(t, .{
4597 .handle = fd,
4598 .flags = .{ .nonblocking = false },
4599 }) catch |err| switch (err) {
4058 // The directory-ness is either unknown or unknowable4600 // The directory-ness is either unknown or unknowable
4059 error.Streaming => break :is_dir false,4601 error.Streaming => break :is_dir false,
4060 else => |e| return e,4602 else => |e| return e,
...@@ -4140,7 +4682,10 @@ fn dirOpenFilePosix(...@@ -4140,7 +4682,10 @@ fn dirOpenFilePosix(
4140 }4682 }
4141 }4683 }
41424684
4143 return .{ .handle = fd };4685 return .{
4686 .handle = fd,
4687 .flags = .{ .nonblocking = false },
4688 };
4144}4689}
41454690
4146fn dirOpenFileWindows(4691fn dirOpenFileWindows(
...@@ -4273,7 +4818,10 @@ pub fn dirOpenFileWtf16(...@@ -4273,7 +4818,10 @@ pub fn dirOpenFileWtf16(
4273 errdefer w.CloseHandle(handle);4818 errdefer w.CloseHandle(handle);
42744819
4275 const exclusive = switch (flags.lock) {4820 const exclusive = switch (flags.lock) {
4276 .none => return .{ .handle = handle },4821 .none => return .{
4822 .handle = handle,
4823 .flags = .{ .nonblocking = false },
4824 },
4277 .shared => false,4825 .shared => false,
4278 .exclusive => true,4826 .exclusive => true,
4279 };4827 };
...@@ -4296,7 +4844,10 @@ pub fn dirOpenFileWtf16(...@@ -4296,7 +4844,10 @@ pub fn dirOpenFileWtf16(
4296 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer4844 .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer
4297 else => |status| return syscall.unexpectedNtstatus(status),4845 else => |status| return syscall.unexpectedNtstatus(status),
4298 };4846 };
4299 return .{ .handle = handle };4847 return .{
4848 .handle = handle,
4849 .flags = .{ .nonblocking = false },
4850 };
4300}4851}
43014852
4302fn dirOpenFileWasi(4853fn dirOpenFileWasi(
...@@ -4378,7 +4929,7 @@ fn dirOpenFileWasi(...@@ -4378,7 +4929,7 @@ fn dirOpenFileWasi(
43784929
4379 if (!flags.allow_directory) {4930 if (!flags.allow_directory) {
4380 const is_dir = is_dir: {4931 const is_dir = is_dir: {
4381 const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) {4932 const stat = fileStat(t, .{ .handle = fd, .flags = .{ .nonblocking = false } }) catch |err| switch (err) {
4382 // The directory-ness is either unknown or unknowable4933 // The directory-ness is either unknown or unknowable
4383 error.Streaming => break :is_dir false,4934 error.Streaming => break :is_dir false,
4384 else => |e| return e,4935 else => |e| return e,
...@@ -4388,7 +4939,10 @@ fn dirOpenFileWasi(...@@ -4388,7 +4939,10 @@ fn dirOpenFileWasi(
4388 if (is_dir) return error.IsDir;4939 if (is_dir) return error.IsDir;
4389 }4940 }
43904941
4391 return .{ .handle = fd };4942 return .{
4943 .handle = fd,
4944 .flags = .{ .nonblocking = false },
4945 };
4392}4946}
43934947
4394const dirOpenDir = switch (native_os) {4948const dirOpenDir = switch (native_os) {
...@@ -5277,7 +5831,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,...@@ -5277,7 +5831,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,
52775831
5278fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {5832fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
5279 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;5833 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
5280 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks5834 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
5281 try Thread.checkCancel();5835 try Thread.checkCancel();
5282 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);5836 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
52835837
...@@ -8275,14 +8829,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void {...@@ -8275,14 +8829,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void {
8275 for (files) |file| posix.close(file.handle);8829 for (files) |file| posix.close(file.handle);
8276}8830}
82778831
8278fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize {8832fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize {
8279 const t: *Threaded = @ptrCast(@alignCast(userdata));8833 const t: *Threaded = @ptrCast(@alignCast(userdata));
8280 _ = t;8834 _ = t;
8281 if (is_windows) return fileReadStreamingWindows(file, data);8835 if (is_windows) return fileReadStreamingWindows(file, data);
8282 return fileReadStreamingPosix(file, data);8836 return fileReadStreamingPosix(file, data);
8283}8837}
82848838
8285fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize {8839fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize {
8286 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;8840 var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined;
8287 var i: usize = 0;8841 var i: usize = 0;
8288 for (data) |buf| {8842 for (data) |buf| {
...@@ -8303,28 +8857,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz...@@ -8303,28 +8857,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
8303 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {8857 switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) {
8304 .SUCCESS => {8858 .SUCCESS => {
8305 syscall.finish();8859 syscall.finish();
8860 if (nread == 0) return error.EndOfStream;
8306 return nread;8861 return nread;
8307 },8862 },
8308 .INTR, .TIMEDOUT => {8863 .INTR, .TIMEDOUT => {
8309 try syscall.checkCancel();8864 try syscall.checkCancel();
8310 continue;8865 continue;
8311 },8866 },
8312 else => |e| {8867 .BADF => return syscall.fail(error.IsDir), // File operation on directory.
8313 syscall.finish();8868 .IO => return syscall.fail(error.InputOutput),
8314 switch (e) {8869 .ISDIR => return syscall.fail(error.IsDir),
8315 .INVAL => |err| return errnoBug(err),8870 .NOBUFS => return syscall.fail(error.SystemResources),
8316 .FAULT => |err| return errnoBug(err),8871 .NOMEM => return syscall.fail(error.SystemResources),
8317 .BADF => return error.IsDir, // File operation on directory.8872 .NOTCONN => return syscall.fail(error.SocketUnconnected),
8318 .IO => return error.InputOutput,8873 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
8319 .ISDIR => return error.IsDir,8874 .NOTCAPABLE => return syscall.fail(error.AccessDenied),
8320 .NOBUFS => return error.SystemResources,8875 .INVAL => |err| return syscall.errnoBug(err),
8321 .NOMEM => return error.SystemResources,8876 .FAULT => |err| return syscall.errnoBug(err),
8322 .NOTCONN => return error.SocketUnconnected,8877 else => |err| return syscall.unexpectedErrno(err),
8323 .CONNRESET => return error.ConnectionResetByPeer,
8324 .NOTCAPABLE => return error.AccessDenied,
8325 else => |err| return posix.unexpectedErrno(err),
8326 }
8327 },
8328 }8878 }
8329 }8879 }
8330 }8880 }
...@@ -8335,75 +8885,115 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz...@@ -8335,75 +8885,115 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
8335 switch (posix.errno(rc)) {8885 switch (posix.errno(rc)) {
8336 .SUCCESS => {8886 .SUCCESS => {
8337 syscall.finish();8887 syscall.finish();
8888 if (rc == 0) return error.EndOfStream;
8338 return @intCast(rc);8889 return @intCast(rc);
8339 },8890 },
8340 .INTR, .TIMEDOUT => {8891 .INTR, .TIMEDOUT => {
8341 try syscall.checkCancel();8892 try syscall.checkCancel();
8342 continue;8893 continue;
8343 },8894 },
8344 else => |e| {8895 .BADF => {
8345 syscall.finish();8896 syscall.finish();
8346 switch (e) {8897 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8347 .INVAL => |err| return errnoBug(err),8898 return error.NotOpenForReading;
8348 .FAULT => |err| return errnoBug(err),
8349 .AGAIN => return error.WouldBlock,
8350 .BADF => {
8351 if (native_os == .wasi) return error.IsDir; // File operation on directory.
8352 return error.NotOpenForReading;
8353 },
8354 .IO => return error.InputOutput,
8355 .ISDIR => return error.IsDir,
8356 .NOBUFS => return error.SystemResources,
8357 .NOMEM => return error.SystemResources,
8358 .NOTCONN => return error.SocketUnconnected,
8359 .CONNRESET => return error.ConnectionResetByPeer,
8360 else => |err| return posix.unexpectedErrno(err),
8361 }
8362 },8899 },
8900 .AGAIN => return syscall.fail(error.WouldBlock),
8901 .IO => return syscall.fail(error.InputOutput),
8902 .ISDIR => return syscall.fail(error.IsDir),
8903 .NOBUFS => return syscall.fail(error.SystemResources),
8904 .NOMEM => return syscall.fail(error.SystemResources),
8905 .NOTCONN => return syscall.fail(error.SocketUnconnected),
8906 .CONNRESET => return syscall.fail(error.ConnectionResetByPeer),
8907 .INVAL => |err| return syscall.errnoBug(err),
8908 .FAULT => |err| return syscall.errnoBug(err),
8909 else => |err| return syscall.unexpectedErrno(err),
8363 }8910 }
8364 }8911 }
8365}8912}
83668913
8367fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {8914fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize {
8368 const DWORD = windows.DWORD;
8369 var index: usize = 0;8915 var index: usize = 0;
8370 while (index < data.len and data[index].len == 0) index += 1;8916 while (data.len - index != 0 and data[index].len == 0) index += 1;
8371 if (index == data.len) return 0;8917 if (data.len - index == 0) return 0;
8372 const buffer = data[index];8918 const buffer = data[index];
8373 const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len);8919 const short_buffer_len = @min(std.math.maxInt(u32), buffer.len);
83748920
8375 const syscall: Syscall = try .start();8921 var iosb: windows.IO_STATUS_BLOCK = undefined;
8376 while (true) {8922
8377 var n: DWORD = undefined;8923 if (!file.flags.nonblocking) {
8378 if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) {8924 const syscall: Syscall = try .start();
8379 syscall.finish();8925 while (true) switch (windows.ntdll.NtReadFile(
8380 return n;8926 file.handle,
8381 }8927 null, // event
8382 switch (windows.GetLastError()) {8928 null, // APC routine
8383 .IO_PENDING => |err| {8929 null, // APC context
8384 syscall.finish();8930 &iosb,
8385 return windows.errorBug(err);8931 buffer.ptr,
8386 },8932 short_buffer_len,
8387 .OPERATION_ABORTED => {8933 null, // byte offset
8934 null, // key
8935 )) {
8936 .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag
8937 .CANCELLED => {
8388 try syscall.checkCancel();8938 try syscall.checkCancel();
8389 continue;8939 continue;
8390 },8940 },
8391 .BROKEN_PIPE, .HANDLE_EOF => {8941 else => |status| {
8392 syscall.finish();8942 syscall.finish();
8393 return 0;8943 iosb.u.Status = status;
8394 },8944 return ntReadFileResult(&iosb);
8395 .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected,
8396 .LOCK_VIOLATION => return syscall.fail(error.LockViolation),
8397 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8398 .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected,
8399 // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing
8400 // a handle to a directory.
8401 .INVALID_FUNCTION => return syscall.fail(error.IsDir),
8402 else => |err| {
8403 syscall.finish();
8404 return windows.unexpectedError(err);
8405 },8945 },
8406 }8946 };
8947 }
8948
8949 var done: bool = false;
8950
8951 switch (windows.ntdll.NtReadFile(
8952 file.handle,
8953 null, // event
8954 flagApc,
8955 &done, // APC context
8956 &iosb,
8957 buffer.ptr,
8958 short_buffer_len,
8959 null, // byte offset
8960 null, // key
8961 )) {
8962 // We must wait for the APC routine.
8963 .PENDING, .SUCCESS => while (!done) {
8964 // Once we get here we must not return from the function until the
8965 // operation completes, thereby releasing reference to io_status_block.
8966 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
8967 error.Canceled => |e| {
8968 var cancel_iosb: windows.IO_STATUS_BLOCK = undefined;
8969 _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb);
8970 while (!done) waitForApcOrAlert();
8971 return e;
8972 },
8973 };
8974 waitForApcOrAlert();
8975 alertable_syscall.finish();
8976 },
8977 else => |status| iosb.u.Status = status,
8978 }
8979 return ntReadFileResult(&iosb);
8980}
8981
8982fn flagApc(userdata: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void {
8983 const flag: *bool = @ptrCast(userdata);
8984 flag.* = true;
8985}
8986
8987fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize {
8988 switch (io_status_block.u.Status) {
8989 .PENDING => unreachable,
8990 .CANCELLED => unreachable,
8991 .SUCCESS => return io_status_block.Information,
8992 .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream,
8993 .INVALID_DEVICE_REQUEST => return error.IsDir,
8994 .LOCK_NOT_GRANTED => return error.LockViolation,
8995 .ACCESS_DENIED => return error.AccessDenied,
8996 else => |status| return windows.unexpectedStatus(status),
8407 }8997 }
8408}8998}
84098999
...@@ -9037,7 +9627,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut...@@ -9037,7 +9627,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut
9037 };9627 };
9038 defer w.CloseHandle(h_file);9628 defer w.CloseHandle(h_file);
90399629
9040 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks9630 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
9041 try Thread.checkCancel();9631 try Thread.checkCancel();
9042 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);9632 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
90439633
...@@ -9359,6 +9949,7 @@ fn writeFileStreamingWindows(...@@ -9359,6 +9949,7 @@ fn writeFileStreamingWindows(
9359 handle: windows.HANDLE,9949 handle: windows.HANDLE,
9360 bytes: []const u8,9950 bytes: []const u8,
9361) File.Writer.Error!usize {9951) File.Writer.Error!usize {
9952 assert(bytes.len != 0);
9362 var bytes_written: windows.DWORD = undefined;9953 var bytes_written: windows.DWORD = undefined;
9363 const adjusted_len = std.math.lossyCast(u32, bytes.len);9954 const adjusted_len = std.math.lossyCast(u32, bytes.len);
9364 const syscall: Syscall = try .start();9955 const syscall: Syscall = try .start();
...@@ -10075,6 +10666,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {...@@ -10075,6 +10666,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
1007510666
10076fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {10667fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void {
10077 const t: *Threaded = @ptrCast(@alignCast(userdata));10668 const t: *Threaded = @ptrCast(@alignCast(userdata));
10669 if (timeout == .none) return;
10078 if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t)));10670 if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t)));
10079 if (native_os == .wasi) return sleepWasi(t, timeout);10671 if (native_os == .wasi) return sleepWasi(t, timeout);
10080 if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout);10672 if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout);
...@@ -12707,7 +13299,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr...@@ -12707,7 +13299,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
1270713299
12708 if (is_windows) {13300 if (is_windows) {
12709 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;13301 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
12710 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks13302 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
12711 try Thread.checkCancel();13303 try Thread.checkCancel();
12712 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);13304 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
12713 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;13305 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
...@@ -13898,15 +14490,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp...@@ -13898,15 +14490,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp
13898 .pid = pid,14490 .pid = pid,
13899 .err_fd = err_pipe[0],14491 .err_fd = err_pipe[0],
13900 .stdin = switch (options.stdin) {14492 .stdin = switch (options.stdin) {
13901 .pipe => .{ .handle = stdin_pipe[1] },14493 .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } },
13902 else => null,14494 else => null,
13903 },14495 },
13904 .stdout = switch (options.stdout) {14496 .stdout = switch (options.stdout) {
13905 .pipe => .{ .handle = stdout_pipe[0] },14497 .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } },
13906 else => null,14498 else => null,
13907 },14499 },
13908 .stderr = switch (options.stderr) {14500 .stderr = switch (options.stderr) {
13909 .pipe => .{ .handle = stderr_pipe[0] },14501 .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } },
13910 else => null,14502 else => null,
13911 },14503 },
13912 };14504 };
...@@ -14560,9 +15152,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro...@@ -14560,9 +15152,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro
14560 return .{15152 return .{
14561 .id = piProcInfo.hProcess,15153 .id = piProcInfo.hProcess,
14562 .thread_handle = piProcInfo.hThread,15154 .thread_handle = piProcInfo.hThread,
14563 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null,15155 .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null,
14564 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null,15156 .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14565 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null,15157 .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null,
14566 .request_resource_usage_statistics = options.request_resource_usage_statistics,15158 .request_resource_usage_statistics = options.request_resource_usage_statistics,
14567 };15159 };
14568}15160}
...@@ -14607,7 +15199,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {...@@ -14607,7 +15199,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE {
14607 t.mutex.lock(); // Another thread might have won the race.15199 t.mutex.lock(); // Another thread might have won the race.
14608 defer t.mutex.unlock();15200 defer t.mutex.unlock();
14609 if (t.random_file.handle) |prev_handle| {15201 if (t.random_file.handle) |prev_handle| {
14610 _ = windows.ntdll.NtClose(fresh_handle);15202 windows.CloseHandle(fresh_handle);
14611 return prev_handle;15203 return prev_handle;
14612 } else {15204 } else {
14613 t.random_file.handle = fresh_handle;15205 t.random_file.handle = fresh_handle;
...@@ -15696,6 +16288,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {...@@ -15696,6 +16288,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File {
15696 .pointer => @ptrFromInt(int),16288 .pointer => @ptrFromInt(int),
15697 else => return error.UnsupportedOperation,16289 else => return error.UnsupportedOperation,
15698 },16290 },
16291 .flags = .{ .nonblocking = false },
15699 };16292 };
15700}16293}
1570116294
...@@ -16375,7 +16968,7 @@ const parking_sleep = struct {...@@ -16375,7 +16968,7 @@ const parking_sleep = struct {
16375/// Spurious wakeups are possible.16968/// Spurious wakeups are possible.
16376///16969///
16377/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.16970/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
16378fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void {16971fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void {
16379 comptime assert(use_parking_futex or use_parking_sleep);16972 comptime assert(use_parking_futex or use_parking_sleep);
16380 switch (native_os) {16973 switch (native_os) {
16381 .windows => {16974 .windows => {
...@@ -16431,6 +17024,22 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err...@@ -16431,6 +17024,22 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err
16431 }17024 }
16432}17025}
1643317026
17027fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) Io.Clock.Error!windows.LARGE_INTEGER {
17028 // ntdll only supports two combinations:
17029 // * real-time (`.real`) sleeps with absolute deadlines
17030 // * monotonic (`.awake`/`.boot`) sleeps with relative durations
17031 switch (deadline.clock) {
17032 .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time
17033 .real => {
17034 return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0));
17035 },
17036 .awake, .boot => {
17037 const duration = try deadline.durationFromNow(ioBasic(t));
17038 return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1));
17039 },
17040 }
17041}
17042
16434const UnparkTid = switch (native_os) {17043const UnparkTid = switch (native_os) {
16435 // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?17044 // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?
16436 .windows => usize,17045 .windows => usize,
lib/std/Io/Threaded/test.zig+2-2
...@@ -188,8 +188,8 @@ test "cancel blocked read from pipe" {...@@ -188,8 +188,8 @@ test "cancel blocked read from pipe" {
188 }),188 }),
189 else => {189 else => {
190 const pipe = try std.Io.Threaded.pipe2(.{});190 const pipe = try std.Io.Threaded.pipe2(.{});
191 read_end = .{ .handle = pipe[0] };191 read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } };
192 write_end = .{ .handle = pipe[1] };192 write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } };
193 },193 },
194 }194 }
195 defer {195 defer {
lib/std/Progress.zig+2-2
...@@ -979,12 +979,13 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -979,12 +979,13 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
979 if (main_parent == .unused) continue;979 if (main_parent == .unused) continue;
980 const file: Io.File = .{980 const file: Io.File = .{
981 .handle = main_storage.getIpcFd() orelse continue,981 .handle = main_storage.getIpcFd() orelse continue,
982 .flags = .{ .nonblocking = true },
982 };983 };
983 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);984 const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata);
984 var bytes_read: usize = 0;985 var bytes_read: usize = 0;
985 while (true) {986 while (true) {
986 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {987 const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) {
987 error.WouldBlock => break,988 error.WouldBlock, error.EndOfStream => break,
988 else => |e| {989 else => |e| {
989 std.log.debug("failed to read child progress data: {t}", .{e});990 std.log.debug("failed to read child progress data: {t}", .{e});
990 main_storage.completed_count = 0;991 main_storage.completed_count = 0;
...@@ -992,7 +993,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff...@@ -992,7 +993,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff
992 continue :main_loop;993 continue :main_loop;
993 },994 },
994 };995 };
995 if (n == 0) break;
996 if (opt_saved_metadata) |m| {996 if (opt_saved_metadata) |m| {
997 if (m.remaining_read_trash_bytes > 0) {997 if (m.remaining_read_trash_bytes > 0) {
998 assert(bytes_read == 0);998 assert(bytes_read == 0);
lib/std/crypto/tls/Client.zig+2-1
...@@ -336,10 +336,11 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -336,10 +336,11 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
336 // Ensure the input buffer pointer is stable in this scope.336 // Ensure the input buffer pointer is stable in this scope.
337 input.rebase(tls.max_ciphertext_record_len) catch |err| switch (err) {337 input.rebase(tls.max_ciphertext_record_len) catch |err| switch (err) {
338 error.EndOfStream => {}, // We have assurance the remainder of stream can be buffered.338 error.EndOfStream => {}, // We have assurance the remainder of stream can be buffered.
339 error.ReadFailed => |e| return e,
339 };340 };
340 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {341 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
341 error.EndOfStream => return error.TlsConnectionTruncated,342 error.EndOfStream => return error.TlsConnectionTruncated,
342 error.ReadFailed => return error.ReadFailed,343 error.ReadFailed => |e| return e,
343 };344 };
344 const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked345 const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked
345 input.toss(2); // legacy_version346 input.toss(2); // legacy_version
lib/std/os/windows/kernel32.zig-3
...@@ -188,9 +188,6 @@ pub extern "kernel32" fn PostQueuedCompletionStatus(...@@ -188,9 +188,6 @@ pub extern "kernel32" fn PostQueuedCompletionStatus(
188 lpOverlapped: ?*OVERLAPPED,188 lpOverlapped: ?*OVERLAPPED,
189) callconv(.winapi) BOOL;189) callconv(.winapi) BOOL;
190190
191// TODO:
192// GetOverlappedResultEx with bAlertable=false, which calls: GetStdHandle + WaitForSingleObjectEx.
193// Uses the SwitchBack system to run implementations for older programs; Do we care about this?
194pub extern "kernel32" fn GetOverlappedResult(191pub extern "kernel32" fn GetOverlappedResult(
195 hFile: HANDLE,192 hFile: HANDLE,
196 lpOverlapped: *OVERLAPPED,193 lpOverlapped: *OVERLAPPED,
lib/std/os/windows/ntdll.zig+9-2
...@@ -594,6 +594,13 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(...@@ -594,6 +594,13 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile(
594 IoStatusBlock: *IO_STATUS_BLOCK,594 IoStatusBlock: *IO_STATUS_BLOCK,
595) callconv(.winapi) NTSTATUS;595) callconv(.winapi) NTSTATUS;
596596
597/// This function has been observed to return SUCCESS on timeout on Windows 10
598/// and TIMEOUT on Wine 10.0.
599///
600/// This function has been observed on Windows 11 such that positive interval
601/// is real time, which can cause waits to be interrupted by changing system
602/// time, however negative intervals are not affected by changes to system
603/// time.
597pub extern "ntdll" fn NtDelayExecution(604pub extern "ntdll" fn NtDelayExecution(
598 Alertable: BOOLEAN,605 Alertable: BOOLEAN,
599 DelayInterval: *const LARGE_INTEGER,606 DelayInterval: *const LARGE_INTEGER,
...@@ -606,6 +613,6 @@ pub extern "ntdll" fn NtCancelIoFileEx(...@@ -606,6 +613,6 @@ pub extern "ntdll" fn NtCancelIoFileEx(
606) callconv(.winapi) NTSTATUS;613) callconv(.winapi) NTSTATUS;
607614
608pub extern "ntdll" fn NtCancelIoFile(615pub extern "ntdll" fn NtCancelIoFile(
609 handle: HANDLE,616 FileHandle: HANDLE,
610 iosbToCancel: *const IO_STATUS_BLOCK,617 IoStatusBlock: *IO_STATUS_BLOCK,
611) callconv(.winapi) NTSTATUS;618) callconv(.winapi) NTSTATUS;
lib/std/posix/test.zig+6-3
...@@ -126,8 +126,8 @@ test "pipe" {...@@ -126,8 +126,8 @@ test "pipe" {
126 const io = testing.io;126 const io = testing.io;
127127
128 const fds = try std.Io.Threaded.pipe2(.{});128 const fds = try std.Io.Threaded.pipe2(.{});
129 const out: Io.File = .{ .handle = fds[0] };129 const out: Io.File = .{ .handle = fds[0], .flags = .{ .nonblocking = false } };
130 const in: Io.File = .{ .handle = fds[1] };130 const in: Io.File = .{ .handle = fds[1], .flags = .{ .nonblocking = false } };
131 try in.writeStreamingAll(io, "hello");131 try in.writeStreamingAll(io, "hello");
132 var buf: [16]u8 = undefined;132 var buf: [16]u8 = undefined;
133 try expect((try out.readStreaming(io, &.{&buf})) == 5);133 try expect((try out.readStreaming(io, &.{&buf})) == 5);
...@@ -150,7 +150,10 @@ test "memfd_create" {...@@ -150,7 +150,10 @@ test "memfd_create" {
150 else => return error.SkipZigTest,150 else => return error.SkipZigTest,
151 }151 }
152152
153 const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) };153 const file: Io.File = .{
154 .handle = try posix.memfd_create("test", 0),
155 .flags = .{ .nonblocking = false },
156 };
154 defer file.close(io);157 defer file.close(io);
155 try file.writePositionalAll(io, "test", 0);158 try file.writePositionalAll(io, "test", 0);
156159
lib/std/process.zig+37-15
...@@ -453,14 +453,16 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child {...@@ -453,14 +453,16 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child {
453 return io.vtable.processSpawnPath(io.userdata, dir, options);453 return io.vtable.processSpawnPath(io.userdata, dir, options);
454}454}
455455
456pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{456pub const RunError = error{
457 StdoutStreamTooLong,457 StreamTooLong,
458 StderrStreamTooLong,458} || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error;
459};
460459
461pub const RunOptions = struct {460pub const RunOptions = struct {
462 argv: []const []const u8,461 argv: []const []const u8,
463 max_output_bytes: usize = 50 * 1024,462 stderr_limit: Io.Limit = .unlimited,
463 stdout_limit: Io.Limit = .unlimited,
464 /// How many bytes to initially allocate for stderr and stdout.
465 reserve_amount: usize = 64,
464466
465 /// Set to change the current working directory when spawning the child process.467 /// Set to change the current working directory when spawning the child process.
466 cwd: ?[]const u8 = null,468 cwd: ?[]const u8 = null,
...@@ -486,6 +488,7 @@ pub const RunOptions = struct {...@@ -486,6 +488,7 @@ pub const RunOptions = struct {
486 create_no_window: bool = true,488 create_no_window: bool = true,
487 /// Darwin-only. Disable ASLR for the child process.489 /// Darwin-only. Disable ASLR for the child process.
488 disable_aslr: bool = false,490 disable_aslr: bool = false,
491 timeout: Io.Timeout = .none,
489};492};
490493
491pub const RunResult = struct {494pub const RunResult = struct {
...@@ -513,22 +516,41 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {...@@ -513,22 +516,41 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult {
513 });516 });
514 defer child.kill(io);517 defer child.kill(io);
515518
516 var stdout: std.ArrayList(u8) = .empty;519 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
517 defer stdout.deinit(gpa);520 var multi_reader: Io.File.MultiReader = undefined;
518 var stderr: std.ArrayList(u8) = .empty;521 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
519 defer stderr.deinit(gpa);522 defer multi_reader.deinit();
523
524 const stdout_reader = multi_reader.reader(0);
525 const stderr_reader = multi_reader.reader(1);
526
527 while (multi_reader.fill(options.reserve_amount, options.timeout)) |_| {
528 if (options.stdout_limit.toInt()) |limit| {
529 if (stdout_reader.buffered().len > limit)
530 return error.StreamTooLong;
531 }
532 if (options.stderr_limit.toInt()) |limit| {
533 if (stderr_reader.buffered().len > limit)
534 return error.StreamTooLong;
535 }
536 } else |err| switch (err) {
537 error.EndOfStream => {},
538 else => |e| return e,
539 }
520540
521 try child.collectOutput(gpa, &stdout, &stderr, options.max_output_bytes);541 try multi_reader.checkAnyError();
522542
523 const term = try child.wait(io);543 const term = try child.wait(io);
524544
525 const owned_stdout = try stdout.toOwnedSlice(gpa);545 const stdout_slice = try multi_reader.toOwnedSlice(0);
526 errdefer gpa.free(owned_stdout);546 errdefer gpa.free(stdout_slice);
527 const owned_stderr = try stderr.toOwnedSlice(gpa);547
548 const stderr_slice = try multi_reader.toOwnedSlice(1);
549 errdefer gpa.free(stderr_slice);
528550
529 return .{551 return .{
530 .stdout = owned_stdout,552 .stdout = stdout_slice,
531 .stderr = owned_stderr,553 .stderr = stderr_slice,
532 .term = term,554 .term = term,
533 };555 };
534}556}
lib/std/process/Child.zig-52
...@@ -9,7 +9,6 @@ const process = std.process;...@@ -9,7 +9,6 @@ const process = std.process;
9const File = std.Io.File;9const File = std.Io.File;
10const assert = std.debug.assert;10const assert = std.debug.assert;
11const Allocator = std.mem.Allocator;11const Allocator = std.mem.Allocator;
12const ArrayList = std.ArrayList;
1312
14pub const Id = switch (native_os) {13pub const Id = switch (native_os) {
15 .windows => std.os.windows.HANDLE,14 .windows => std.os.windows.HANDLE,
...@@ -125,54 +124,3 @@ pub fn wait(child: *Child, io: Io) WaitError!Term {...@@ -125,54 +124,3 @@ pub fn wait(child: *Child, io: Io) WaitError!Term {
125 assert(child.id != null);124 assert(child.id != null);
126 return io.vtable.childWait(io.userdata, child);125 return io.vtable.childWait(io.userdata, child);
127}126}
128
129/// Collect the output from the process's stdout and stderr. Will return once all output
130/// has been collected. This does not mean that the process has ended. `wait` should still
131/// be called to wait for and clean up the process.
132///
133/// The process must have been started with stdout and stderr set to
134/// `process.SpawnOptions.StdIo.pipe`.
135pub fn collectOutput(
136 child: *const Child,
137 /// Used for `stdout` and `stderr`.
138 allocator: Allocator,
139 stdout: *ArrayList(u8),
140 stderr: *ArrayList(u8),
141 max_output_bytes: usize,
142) !void {
143 var poller = std.Io.poll(allocator, enum { stdout, stderr }, .{
144 .stdout = child.stdout.?,
145 .stderr = child.stderr.?,
146 });
147 defer poller.deinit();
148
149 const stdout_r = poller.reader(.stdout);
150 stdout_r.buffer = stdout.allocatedSlice();
151 stdout_r.seek = 0;
152 stdout_r.end = stdout.items.len;
153
154 const stderr_r = poller.reader(.stderr);
155 stderr_r.buffer = stderr.allocatedSlice();
156 stderr_r.seek = 0;
157 stderr_r.end = stderr.items.len;
158
159 defer {
160 stdout.* = .{
161 .items = stdout_r.buffer[0..stdout_r.end],
162 .capacity = stdout_r.buffer.len,
163 };
164 stderr.* = .{
165 .items = stderr_r.buffer[0..stderr_r.end],
166 .capacity = stderr_r.buffer.len,
167 };
168 stdout_r.buffer = &.{};
169 stderr_r.buffer = &.{};
170 }
171
172 while (try poller.poll()) {
173 if (stdout_r.bufferedLen() > max_output_bytes)
174 return error.StdoutStreamTooLong;
175 if (stderr_r.bufferedLen() > max_output_bytes)
176 return error.StderrStreamTooLong;
177 }
178}
lib/std/process/Preopens.zig+4-1
...@@ -29,7 +29,10 @@ pub fn get(p: *const Preopens, name: []const u8) ?Resource {...@@ -29,7 +29,10 @@ pub fn get(p: *const Preopens, name: []const u8) ?Resource {
29 switch (native_os) {29 switch (native_os) {
30 .wasi => {30 .wasi => {
31 const index = p.map.getIndex(name) orelse return null;31 const index = p.map.getIndex(name) orelse return null;
32 if (index <= 2) return .{ .file = .{ .handle = @intCast(index) } };32 if (index <= 2) return .{ .file = .{
33 .handle = @intCast(index),
34 .flags = .{ .nonblocking = false },
35 } };
33 return .{ .dir = .{ .handle = @intCast(index) } };36 return .{ .dir = .{ .handle = @intCast(index) } };
34 },37 },
35 else => {38 else => {
lib/std/zig/LibCInstallation.zig+4-2
...@@ -268,7 +268,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar...@@ -268,7 +268,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar
268 });268 });
269269
270 const run_res = std.process.run(gpa, io, .{270 const run_res = std.process.run(gpa, io, .{
271 .max_output_bytes = 1024 * 1024,271 .stdout_limit = .limited(1024 * 1024),
272 .stderr_limit = .limited(1024 * 1024),
272 .argv = argv.items,273 .argv = argv.items,
273 .environ_map = &environ_map,274 .environ_map = &environ_map,
274 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path275 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path
...@@ -584,7 +585,8 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 {...@@ -584,7 +585,8 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 {
584 try argv.append(arg1);585 try argv.append(arg1);
585586
586 const run_res = std.process.run(gpa, io, .{587 const run_res = std.process.run(gpa, io, .{
587 .max_output_bytes = 1024 * 1024,588 .stdout_limit = .limited(1024 * 1024),
589 .stderr_limit = .limited(1024 * 1024),
588 .argv = argv.items,590 .argv = argv.items,
589 .environ_map = &environ_map,591 .environ_map = &environ_map,
590 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path592 // Some C compilers, such as Clang, are known to rely on argv[0] to find the path
lib/std/zig/system.zig-1
...@@ -420,7 +420,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {...@@ -420,7 +420,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target {
420 error.Canceled => |e| return e,420 error.Canceled => |e| return e,
421 error.Unexpected => |e| return e,421 error.Unexpected => |e| return e,
422 error.WouldBlock => return error.Unexpected,422 error.WouldBlock => return error.Unexpected,
423 error.BrokenPipe => return error.Unexpected,
424 error.ConnectionResetByPeer => return error.Unexpected,423 error.ConnectionResetByPeer => return error.Unexpected,
425 error.NotOpenForReading => return error.Unexpected,424 error.NotOpenForReading => return error.Unexpected,
426 error.SocketUnconnected => return error.Unexpected,425 error.SocketUnconnected => return error.Unexpected,
src/Compilation.zig+33-18
...@@ -6873,6 +6873,7 @@ fn spawnZigRc(...@@ -6873,6 +6873,7 @@ fn spawnZigRc(
6873 child_progress_node: std.Progress.Node,6873 child_progress_node: std.Progress.Node,
6874) !void {6874) !void {
6875 const io = comp.io;6875 const io = comp.io;
6876 const gpa = comp.gpa;
6876 var node_name: std.ArrayList(u8) = .empty;6877 var node_name: std.ArrayList(u8) = .empty;
6877 defer node_name.deinit(arena);6878 defer node_name.deinit(arena);
68786879
...@@ -6887,55 +6888,69 @@ fn spawnZigRc(...@@ -6887,55 +6888,69 @@ fn spawnZigRc(
6887 });6888 });
6888 defer child.kill(io);6889 defer child.kill(io);
68896890
6890 var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{6891 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
6891 .stdout = child.stdout.?,6892 var multi_reader: Io.File.MultiReader = undefined;
6892 .stderr = child.stderr.?,6893 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
6893 });6894 defer multi_reader.deinit();
6894 defer poller.deinit();
68956895
6896 const stdout = poller.reader(.stdout);6896 const stdout = multi_reader.fileReader(0);
6897 const MessageHeader = std.zig.Server.Message.Header;
68976898
6898 poll: while (true) {6899 var eos_err: error{EndOfStream}!void = {};
6899 const MessageHeader = std.zig.Server.Message.Header;
6900 while (stdout.buffered().len < @sizeOf(MessageHeader)) if (!try poller.poll()) break :poll;
6901 const header = stdout.takeStruct(MessageHeader, .little) catch unreachable;
6902 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;
6903 const body = stdout.take(header.bytes_len) catch unreachable;
69046900
6901 while (true) {
6902 const header = stdout.interface.takeStruct(MessageHeader, .little) catch |err| switch (err) {
6903 error.EndOfStream => break,
6904 error.ReadFailed => return stdout.err.?,
6905 };
6906 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
6907 error.EndOfStream => |e| {
6908 // Better to report the crash with stderr below, but we set
6909 // this in case the child exits successfully while violating
6910 // this protocol.
6911 eos_err = e;
6912 break;
6913 },
6914 error.ReadFailed => return stdout.err.?,
6915 };
6905 switch (header.tag) {6916 switch (header.tag) {
6906 // We expect exactly one ErrorBundle, and if any error_bundle header is6917 // We expect exactly one ErrorBundle, and if any error_bundle header is
6907 // sent then it's a fatal error.6918 // sent then it's a fatal error.
6908 .error_bundle => {6919 .error_bundle => {
6909 const error_bundle = try std.zig.Server.allocErrorBundle(comp.gpa, body);6920 const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body);
6910 return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle);6921 return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle);
6911 },6922 },
6912 else => {}, // ignore other messages6923 else => {}, // ignore other messages
6913 }6924 }
6914 }6925 }
69156926
6916 // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)6927 try multi_reader.fillRemaining(.none);
6917 const stderr = poller.reader(.stderr);
69186928
6929 // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace)
6919 const term = child.wait(io) catch |err| {6930 const term = child.wait(io) catch |err| {
6920 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {t}", .{ argv[0], err });6931 return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {t}", .{ argv[0], err });
6921 };6932 };
69226933
6934 const stderr = multi_reader.reader(1).buffered();
6935
6923 switch (term) {6936 switch (term) {
6924 .exited => |code| {6937 .exited => |code| {
6925 if (code != 0) {6938 if (code != 0) {
6926 log.err("zig rc failed with stderr:\n{s}", .{stderr.buffered()});6939 log.err("zig rc failed with stderr:\n{s}", .{stderr});
6927 return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});6940 return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code});
6928 }6941 }
6929 },6942 },
6930 .signal => |sig| {6943 .signal => |sig| {
6931 log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr.buffered() });6944 log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr });
6932 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});6945 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
6933 },6946 },
6934 else => {6947 else => {
6935 log.err("zig rc terminated with stderr:\n{s}", .{stderr.buffered()});6948 log.err("zig rc terminated with stderr:\n{s}", .{stderr});
6936 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});6949 return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{});
6937 },6950 },
6938 }6951 }
6952
6953 try eos_err;
6939}6954}
69406955
6941pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {6956pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 {
src/codegen/c/Type.zig+2-2
...@@ -2389,7 +2389,7 @@ pub const Pool = struct {...@@ -2389,7 +2389,7 @@ pub const Pool = struct {
2389 .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) {2389 .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) {
2390 .none => true,2390 .none => true,
2391 .zero_u8 => false,2391 .zero_u8 => false,
2392 else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq),2392 else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu),
2393 },2393 },
2394 });2394 });
2395 },2395 },
...@@ -2438,7 +2438,7 @@ pub const Pool = struct {...@@ -2438,7 +2438,7 @@ pub const Pool = struct {
2438 .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) {2438 .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) {
2439 .none => true,2439 .none => true,
2440 .zero_u8 => false,2440 .zero_u8 => false,
2441 else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq),2441 else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu),
2442 },2442 },
2443 });2443 });
2444 if (!kind.isParameter()) return array_ctype;2444 if (!kind.isParameter()) return array_ctype;
src/link.zig+9-2
...@@ -605,8 +605,8 @@ pub const File = struct {...@@ -605,8 +605,8 @@ pub const File = struct {
605 switch (base.tag) {605 switch (base.tag) {
606 .lld => assert(base.file == null),606 .lld => assert(base.file == null),
607 .elf, .macho, .wasm => {607 .elf, .macho, .wasm => {
608 if (base.file != null) return;
609 dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker });608 dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker });
609 if (base.file != null) return;
610 const emit = base.emit;610 const emit = base.emit;
611 if (base.child_pid) |pid| {611 if (base.child_pid) |pid| {
612 if (builtin.os.tag == .windows) {612 if (builtin.os.tag == .windows) {
...@@ -645,6 +645,7 @@ pub const File = struct {...@@ -645,6 +645,7 @@ pub const File = struct {
645 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });645 base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write });
646 },646 },
647 .elf2, .coff2 => if (base.file == null) {647 .elf2, .coff2 => if (base.file == null) {
648 dev.checkAny(&.{ .elf2_linker, .coff2_linker });
648 const mf = if (base.cast(.elf2)) |elf|649 const mf = if (base.cast(.elf2)) |elf|
649 &elf.mf650 &elf.mf
650 else if (base.cast(.coff2)) |coff|651 else if (base.cast(.coff2)) |coff|
...@@ -657,7 +658,13 @@ pub const File = struct {...@@ -657,7 +658,13 @@ pub const File = struct {
657 base.file = mf.memory_map.file;658 base.file = mf.memory_map.file;
658 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));659 try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1]));
659 },660 },
660 .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }),661 .c => if (base.file == null) {
662 dev.check(.c_linker);
663 base.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{
664 .mode = .write_only,
665 });
666 },
667 .spirv => dev.check(.spirv_linker),
661 .plan9 => unreachable,668 .plan9 => unreachable,
662 }669 }
663 }670 }
tools/doctest.zig-6
...@@ -201,7 +201,6 @@ fn printOutput(...@@ -201,7 +201,6 @@ fn printOutput(
201 .argv = build_args.items,201 .argv = build_args.items,
202 .cwd = tmp_dir_path,202 .cwd = tmp_dir_path,
203 .environ_map = environ_map,203 .environ_map = environ_map,
204 .max_output_bytes = max_doc_file_size,
205 });204 });
206 switch (result.term) {205 switch (result.term) {
207 .exited => |exit_code| {206 .exited => |exit_code| {
...@@ -257,7 +256,6 @@ fn printOutput(...@@ -257,7 +256,6 @@ fn printOutput(
257 .argv = run_args,256 .argv = run_args,
258 .environ_map = environ_map,257 .environ_map = environ_map,
259 .cwd = tmp_dir_path,258 .cwd = tmp_dir_path,
260 .max_output_bytes = max_doc_file_size,
261 });259 });
262 switch (result.term) {260 switch (result.term) {
263 .exited => |exit_code| {261 .exited => |exit_code| {
...@@ -376,7 +374,6 @@ fn printOutput(...@@ -376,7 +374,6 @@ fn printOutput(
376 .argv = test_args.items,374 .argv = test_args.items,
377 .environ_map = environ_map,375 .environ_map = environ_map,
378 .cwd = tmp_dir_path,376 .cwd = tmp_dir_path,
379 .max_output_bytes = max_doc_file_size,
380 });377 });
381 switch (result.term) {378 switch (result.term) {
382 .exited => |exit_code| {379 .exited => |exit_code| {
...@@ -432,7 +429,6 @@ fn printOutput(...@@ -432,7 +429,6 @@ fn printOutput(
432 .argv = test_args.items,429 .argv = test_args.items,
433 .environ_map = environ_map,430 .environ_map = environ_map,
434 .cwd = tmp_dir_path,431 .cwd = tmp_dir_path,
435 .max_output_bytes = max_doc_file_size,
436 });432 });
437 switch (result.term) {433 switch (result.term) {
438 .exited => |exit_code| {434 .exited => |exit_code| {
...@@ -508,7 +504,6 @@ fn printOutput(...@@ -508,7 +504,6 @@ fn printOutput(
508 .argv = build_args.items,504 .argv = build_args.items,
509 .environ_map = environ_map,505 .environ_map = environ_map,
510 .cwd = tmp_dir_path,506 .cwd = tmp_dir_path,
511 .max_output_bytes = max_doc_file_size,
512 });507 });
513 switch (result.term) {508 switch (result.term) {
514 .exited => |exit_code| {509 .exited => |exit_code| {
...@@ -1132,7 +1127,6 @@ fn run(...@@ -1132,7 +1127,6 @@ fn run(
1132 .argv = args,1127 .argv = args,
1133 .environ_map = environ_map,1128 .environ_map = environ_map,
1134 .cwd = cwd,1129 .cwd = cwd,
1135 .max_output_bytes = max_doc_file_size,
1136 });1130 });
1137 switch (result.term) {1131 switch (result.term) {
1138 .exited => |exit_code| {1132 .exited => |exit_code| {
tools/incr-check.zig+45-37
...@@ -28,6 +28,7 @@ fn logImpl(...@@ -28,6 +28,7 @@ fn logImpl(
28}28}
2929
30pub fn main(init: std.process.Init) !void {30pub fn main(init: std.process.Init) !void {
31 const gpa = init.gpa;
31 const fatal = std.process.fatal;32 const fatal = std.process.fatal;
32 const arena = init.arena.allocator();33 const arena = init.arena.allocator();
33 const io = init.io;34 const io = init.io;
...@@ -224,11 +225,10 @@ pub fn main(init: std.process.Init) !void {...@@ -224,11 +225,10 @@ pub fn main(init: std.process.Init) !void {
224 .enable_darling = enable_darling,225 .enable_darling = enable_darling,
225 };226 };
226227
227 var poller = Io.poll(arena, Eval.StreamEnum, .{228 var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined;
228 .stdout = child.stdout.?,229 var multi_reader: Io.File.MultiReader = undefined;
229 .stderr = child.stderr.?,230 multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? });
230 });231 defer multi_reader.deinit();
231 defer poller.deinit();
232232
233 for (case.updates) |update| {233 for (case.updates) |update| {
234 var update_node = target_prog_node.start(update.name, 0);234 var update_node = target_prog_node.start(update.name, 0);
...@@ -243,10 +243,10 @@ pub fn main(init: std.process.Init) !void {...@@ -243,10 +243,10 @@ pub fn main(init: std.process.Init) !void {
243243
244 eval.write(update);244 eval.write(update);
245 try eval.requestUpdate();245 try eval.requestUpdate();
246 try eval.check(&poller, update, update_node);246 try eval.check(&multi_reader, update, update_node);
247 }247 }
248248
249 try eval.end(&poller);249 try eval.end(&multi_reader);
250250
251 waitChild(&child, &eval);251 waitChild(&child, &eval);
252 }252 }
...@@ -272,9 +272,6 @@ const Eval = struct {...@@ -272,9 +272,6 @@ const Eval = struct {
272 enable_wasmtime: bool,272 enable_wasmtime: bool,
273 enable_darling: bool,273 enable_darling: bool,
274274
275 const StreamEnum = enum { stdout, stderr };
276 const Poller = Io.Poller(StreamEnum);
277
278 /// Currently this function assumes the previous updates have already been written.275 /// Currently this function assumes the previous updates have already been written.
279 fn write(eval: *Eval, update: Case.Update) void {276 fn write(eval: *Eval, update: Case.Update) void {
280 const io = eval.io;277 const io = eval.io;
...@@ -293,23 +290,29 @@ const Eval = struct {...@@ -293,23 +290,29 @@ const Eval = struct {
293 }290 }
294 }291 }
295292
296 fn check(eval: *Eval, poller: *Poller, update: Case.Update, prog_node: std.Progress.Node) !void {293 fn check(eval: *Eval, mr: *Io.File.MultiReader, update: Case.Update, prog_node: std.Progress.Node) !void {
297 const arena = eval.arena;294 const arena = eval.arena;
298 const stdout = poller.reader(.stdout);295 const stdout = mr.fileReader(0);
299 const stderr = poller.reader(.stderr);296 const stderr = &mr.fileReader(1).interface;
300297 const Header = std.zig.Server.Message.Header;
301 poll: while (true) {298
302 const Header = std.zig.Server.Message.Header;299 while (true) {
303 while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll;300 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
304 const header = stdout.takeStruct(Header, .little) catch unreachable;301 error.EndOfStream => break,
305 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;302 error.ReadFailed => return stdout.err.?,
306 const body = stdout.take(header.bytes_len) catch unreachable;303 };
304 const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) {
305 // If this panic triggers it might be helpful to rework this
306 // code to print the stderr from the abnormally terminated child.
307 error.EndOfStream => @panic("unexpected mid-message end of stream"),
308 error.ReadFailed => return stdout.err.?,
309 };
307310
308 switch (header.tag) {311 switch (header.tag) {
309 .error_bundle => {312 .error_bundle => {
310 const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);313 const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body);
311 if (stderr.bufferedLen() > 0) {314 if (stderr.bufferedLen() > 0) {
312 const stderr_data = try poller.toOwnedSlice(.stderr);315 const stderr_data = try mr.toOwnedSlice(1);
313 if (eval.allow_stderr) {316 if (eval.allow_stderr) {
314 std.log.info("error_bundle stderr:\n{s}", .{stderr_data});317 std.log.info("error_bundle stderr:\n{s}", .{stderr_data});
315 } else {318 } else {
...@@ -326,7 +329,7 @@ const Eval = struct {...@@ -326,7 +329,7 @@ const Eval = struct {
326 var r: std.Io.Reader = .fixed(body);329 var r: std.Io.Reader = .fixed(body);
327 _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;330 _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable;
328 if (stderr.bufferedLen() > 0) {331 if (stderr.bufferedLen() > 0) {
329 const stderr_data = try poller.toOwnedSlice(.stderr);332 const stderr_data = try mr.toOwnedSlice(1);
330 if (eval.allow_stderr) {333 if (eval.allow_stderr) {
331 std.log.info("emit_digest stderr:\n{s}", .{stderr_data});334 std.log.info("emit_digest stderr:\n{s}", .{stderr_data});
332 } else {335 } else {
...@@ -358,11 +361,12 @@ const Eval = struct {...@@ -358,11 +361,12 @@ const Eval = struct {
358 }361 }
359 }362 }
360363
361 if (stderr.bufferedLen() > 0) {364 const buffered_stderr = stderr.buffered();
365 if (buffered_stderr.len > 0) {
362 if (eval.allow_stderr) {366 if (eval.allow_stderr) {
363 std.log.info("stderr:\n{s}", .{stderr.buffered()});367 std.log.info("stderr:\n{s}", .{buffered_stderr});
364 } else {368 } else {
365 eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()});369 eval.fatal("unexpected stderr:\n{s}", .{buffered_stderr});
366 }370 }
367 }371 }
368372
...@@ -588,23 +592,27 @@ const Eval = struct {...@@ -588,23 +592,27 @@ const Eval = struct {
588 };592 };
589 }593 }
590594
591 fn end(eval: *Eval, poller: *Poller) !void {595 fn end(eval: *Eval, mr: *Io.File.MultiReader) !void {
592 requestExit(eval.child, eval);596 requestExit(eval.child, eval);
593597
594 const stdout = poller.reader(.stdout);598 const stdout = mr.fileReader(0);
595 const stderr = poller.reader(.stderr);599 const Header = std.zig.Server.Message.Header;
596600
597 poll: while (true) {601 while (true) {
598 const Header = std.zig.Server.Message.Header;602 const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) {
599 while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll;603 error.EndOfStream => break,
600 const header = stdout.takeStruct(Header, .little) catch unreachable;604 error.ReadFailed => return stdout.err.?,
601 while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll;605 };
602 stdout.toss(header.bytes_len);606 stdout.interface.discardAll(header.bytes_len) catch |err| switch (err) {
607 error.ReadFailed => return stdout.err.?,
608 error.EndOfStream => |e| return e,
609 };
603 }610 }
604611
605 if (stderr.bufferedLen() > 0) {612 try mr.fillRemaining(.none);
606 eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()});613
607 }614 const stderr = mr.reader(1).buffered();
615 if (stderr.len > 0) eval.fatal("unexpected stderr:\n{s}", .{stderr});
608 }616 }
609617
610 fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void {618 fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void {
tools/update_clang_options.zig-1
...@@ -676,7 +676,6 @@ pub fn main(init: std.process.Init) !void {...@@ -676,7 +676,6 @@ pub fn main(init: std.process.Init) !void {
676676
677 const child_result = try std.process.run(arena, io, .{677 const child_result = try std.process.run(arena, io, .{
678 .argv = &child_args,678 .argv = &child_args,
679 .max_output_bytes = 100 * 1024 * 1024,
680 });679 });
681680
682 std.debug.print("{s}\n", .{child_result.stderr});681 std.debug.print("{s}\n", .{child_result.stderr});
tools/update_cpu_features.zig-1
...@@ -1987,7 +1987,6 @@ fn processOneTarget(io: Io, job: Job) void {...@@ -1987,7 +1987,6 @@ fn processOneTarget(io: Io, job: Job) void {
19871987
1988 const child_result = try std.process.run(arena, io, .{1988 const child_result = try std.process.run(arena, io, .{
1989 .argv = &child_args,1989 .argv = &child_args,
1990 .max_output_bytes = 500 * 1024 * 1024,
1991 });1990 });
1992 tblgen_progress.end();1991 tblgen_progress.end();
1993 if (child_result.stderr.len != 0) {1992 if (child_result.stderr.len != 0) {