| author | |
| committer | |
| log | 2b19134c86223236b3fffc1360577a31d0251604 |
| tree | 6e98fbfeea5309faef0105c4f29b5c1d0235681a |
| parent | 5ccc2ea85d5d4c23daae8a3afe6b7784071597ac |
| parent | 9646801bed8f0f36b59deecff32ef02868ed72f2 |
Reviewed-on: https://codeberg.org/ziglang/zig/pulls/3074328 files changed, 1608 insertions(+), 902 deletions(-)
build.zig+3-39| ... | ... | @@ -29,7 +29,7 @@ pub fn build(b: *std.Build) !void { |
| 29 | 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; |
| 30 | 30 | |
| 31 | 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 | 33 | const skip_install_langref = b.option(bool, "no-langref", "skip copying of langref to the installation prefix") orelse skip_install_lib_files; |
| 34 | 34 | const std_docs = b.option(bool, "std-docs", "include standard library autodocs") orelse false; |
| 35 | 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 | 472 | .skip_linux = skip_linux, |
| 473 | 473 | .skip_llvm = skip_llvm, |
| 474 | 474 | .skip_libc = skip_libc, |
| 475 | .max_rss = switch (b.graph.host.result.os.tag) { | |
| 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 | }, | |
| 475 | .max_rss = 3_300_000_000, | |
| 496 | 476 | })); |
| 497 | 477 | |
| 498 | 478 | test_modules_step.dependOn(tests.addModuleTests(b, .{ |
| ... | ... | @@ -518,23 +498,7 @@ pub fn build(b: *std.Build) !void { |
| 518 | 498 | .skip_llvm = skip_llvm, |
| 519 | 499 | .skip_libc = true, |
| 520 | 500 | .no_builtin = true, |
| 521 | .max_rss = switch (b.graph.host.result.os.tag) { | |
| 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 | }, | |
| 501 | .max_rss = 900_000_000, | |
| 538 | 502 | })); |
| 539 | 503 | |
| 540 | 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 | 381 | |
| 382 | 382 | pub const ZigProcess = struct { |
| 383 | 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 | 386 | progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void, |
| 386 | 387 | |
| 387 | 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 | }; |
| 389 | 396 | |
| 390 | 397 | /// Assumes that argv contains `--listen=-` and that the process being spawned |
| ... | ... | @@ -409,7 +416,8 @@ pub fn evalZigProcess( |
| 409 | 416 | assert(watch); |
| 410 | 417 | if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd); |
| 411 | 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 | 421 | // Process restart required. |
| 414 | 422 | const term = zp.child.wait(io) catch |e| { |
| 415 | 423 | return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); |
| ... | ... | @@ -455,18 +463,18 @@ pub fn evalZigProcess( |
| 455 | 463 | .request_resource_usage_statistics = true, |
| 456 | 464 | .progress_node = prog_node, |
| 457 | 465 | }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); |
| 458 | defer if (!watch) zp.child.kill(io); | |
| 459 | 466 | |
| 460 | 467 | zp.* = .{ |
| 461 | 468 | .child = zp.child, |
| 462 | .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{ | |
| 463 | .stdout = zp.child.stdout.?, | |
| 464 | .stderr = zp.child.stderr.?, | |
| 465 | }), | |
| 469 | .multi_reader_buffer = undefined, | |
| 470 | .multi_reader = undefined, | |
| 466 | 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 | 476 | if (watch) s.setZigProcess(zp); |
| 469 | defer if (!watch) zp.poller.deinit(); | |
| 477 | defer if (!watch) zp.deinit(io); | |
| 470 | 478 | |
| 471 | 479 | const result = try zigProcessUpdate(s, zp, watch, web_server, gpa); |
| 472 | 480 | |
| ... | ... | @@ -532,15 +540,26 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. |
| 532 | 540 | if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); |
| 533 | 541 | |
| 534 | 542 | var result: ?Path = null; |
| 543 | var eos_err: error{EndOfStream}!void = {}; | |
| 535 | 544 | |
| 536 | const stdout = zp.poller.reader(.stdout); | |
| 545 | const stdout = zp.multi_reader.fileReader(0); | |
| 537 | 546 | |
| 538 | poll: while (true) { | |
| 547 | while (true) { | |
| 539 | 548 | const Header = std.zig.Server.Message.Header; |
| 540 | while (stdout.buffered().len < @sizeOf(Header)) if (!try zp.poller.poll()) break :poll; | |
| 541 | const header = stdout.takeStruct(Header, .little) catch unreachable; | |
| 542 | while (stdout.buffered().len < header.bytes_len) if (!try zp.poller.poll()) break :poll; | |
| 543 | const body = stdout.take(header.bytes_len) catch unreachable; | |
| 549 | const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { | |
| 550 | error.EndOfStream => break, | |
| 551 | error.ReadFailed => return stdout.err.?, | |
| 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 | 563 | switch (header.tag) { |
| 545 | 564 | .zig_version => { |
| 546 | 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 | 572 | .error_bundle => { |
| 554 | 573 | s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); |
| 555 | 574 | // This message indicates the end of the update. |
| 556 | if (watch) break :poll; | |
| 575 | if (watch) break; | |
| 557 | 576 | }, |
| 558 | 577 | .emit_digest => { |
| 559 | 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 | 580 | s.result_cached = emit_digest.flags.cache_hit; |
| 562 | 581 | const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len]; |
| 563 | 582 | result = .{ |
| ... | ... | @@ -631,11 +650,13 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. |
| 631 | 650 | |
| 632 | 651 | s.result_duration_ns = timer.read(); |
| 633 | 652 | |
| 634 | const stderr_contents = try zp.poller.toOwnedSlice(.stderr); | |
| 653 | const stderr_contents = zp.multi_reader.reader(1).buffered(); | |
| 635 | 654 | if (stderr_contents.len > 0) { |
| 636 | 655 | try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); |
| 637 | 656 | } |
| 638 | 657 | |
| 658 | try eos_err; | |
| 659 | ||
| 639 | 660 | return result; |
| 640 | 661 | } |
| 641 | 662 |
lib/std/Build/Step/Run.zig+105-85| ... | ... | @@ -1385,14 +1385,12 @@ fn runCommand( |
| 1385 | 1385 | break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| { |
| 1386 | 1386 | if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; |
| 1387 | 1387 | if (e == error.MakeFailed) return error.MakeFailed; // error already reported |
| 1388 | return step.fail("unable to spawn interpreter {s}: {s}", .{ | |
| 1389 | interp_argv.items[0], @errorName(e), | |
| 1390 | }); | |
| 1388 | return step.fail("unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e }); | |
| 1391 | 1389 | }; |
| 1392 | 1390 | } |
| 1393 | 1391 | if (err == error.MakeFailed) return error.MakeFailed; // error already reported |
| 1394 | 1392 | |
| 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 | }; |
| 1397 | 1395 | |
| 1398 | 1396 | const generic_result = opt_generic_result orelse { |
| ... | ... | @@ -1589,9 +1587,13 @@ fn spawnChildAndCollect( |
| 1589 | 1587 | }; |
| 1590 | 1588 | |
| 1591 | 1589 | if (run.stdio == .zig_test) { |
| 1592 | var timer = try std.time.Timer.start(); | |
| 1593 | defer run.step.result_duration_ns = timer.read(); | |
| 1594 | try evalZigTest(run, spawn_options, options, fuzz_context); | |
| 1590 | const started: Io.Clock.Timestamp = try .now(io, .awake); | |
| 1591 | const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) { | |
| 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 | 1597 | return null; |
| 1596 | 1598 | } else { |
| 1597 | 1599 | const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; |
| ... | ... | @@ -1604,10 +1606,14 @@ fn spawnChildAndCollect( |
| 1604 | 1606 | } else .no_color; |
| 1605 | 1607 | defer if (inherit) io.unlockStderr(); |
| 1606 | 1608 | try setColorEnvironmentVariables(run, environ_map, terminal_mode); |
| 1607 | var timer = try std.time.Timer.start(); | |
| 1608 | const res = try evalGeneric(run, spawn_options); | |
| 1609 | run.step.result_duration_ns = timer.read(); | |
| 1610 | return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr }; | |
| 1609 | ||
| 1610 | const started: Io.Clock.Timestamp = try .now(io, .awake); | |
| 1611 | const result = evalGeneric(run, spawn_options) catch |err| switch (err) { | |
| 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 | } |
| 1613 | 1619 | |
| ... | ... | @@ -1669,39 +1675,42 @@ fn evalZigTest( |
| 1669 | 1675 | |
| 1670 | 1676 | while (true) { |
| 1671 | 1677 | var child = try process.spawn(io, spawn_options); |
| 1672 | var poller = std.Io.poll(gpa, StdioPollEnum, .{ | |
| 1673 | .stdout = child.stdout.?, | |
| 1674 | .stderr = child.stderr.?, | |
| 1675 | }); | |
| 1678 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 1679 | var multi_reader: Io.File.MultiReader = undefined; | |
| 1680 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); | |
| 1676 | 1681 | var child_killed = false; |
| 1677 | 1682 | defer if (!child_killed) { |
| 1678 | 1683 | child.kill(io); |
| 1679 | poller.deinit(); | |
| 1684 | multi_reader.deinit(); | |
| 1680 | 1685 | run.step.result_peak_rss = @max( |
| 1681 | 1686 | run.step.result_peak_rss, |
| 1682 | 1687 | child.resource_usage_statistics.getMaxRss() orelse 0, |
| 1683 | 1688 | ); |
| 1684 | 1689 | }; |
| 1685 | 1690 | |
| 1686 | switch (try pollZigTest( | |
| 1691 | switch (try waitZigTest( | |
| 1687 | 1692 | run, |
| 1688 | 1693 | &child, |
| 1689 | 1694 | options, |
| 1690 | 1695 | fuzz_context, |
| 1691 | &poller, | |
| 1696 | &multi_reader, | |
| 1692 | 1697 | &test_metadata, |
| 1693 | 1698 | &test_results, |
| 1694 | 1699 | )) { |
| 1695 | 1700 | .write_failed => |err| { |
| 1696 | 1701 | // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured |
| 1697 | 1702 | // all available stderr to make our error output as useful as possible. |
| 1698 | while (try poller.poll()) {} | |
| 1699 | run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered()); | |
| 1703 | const stderr_fr = multi_reader.fileReader(1); | |
| 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()); | |
| 1700 | 1709 | |
| 1701 | 1710 | // Clean up everything and wait for the child to exit. |
| 1702 | 1711 | child.stdin.?.close(io); |
| 1703 | 1712 | child.stdin = null; |
| 1704 | poller.deinit(); | |
| 1713 | multi_reader.deinit(); | |
| 1705 | 1714 | child_killed = true; |
| 1706 | 1715 | const term = try child.wait(io); |
| 1707 | 1716 | run.step.result_peak_rss = @max( |
| ... | ... | @@ -1716,13 +1725,13 @@ fn evalZigTest( |
| 1716 | 1725 | .no_poll => |no_poll| { |
| 1717 | 1726 | // This might be a success (we requested exit and the child dutifully closed stdout) or |
| 1718 | 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()); | |
| 1720 | poller.reader(.stderr).tossBuffered(); | |
| 1728 | const stderr_reader = multi_reader.reader(1); | |
| 1729 | const stderr_owned = try arena.dupe(u8, stderr_reader.buffered()); | |
| 1721 | 1730 | |
| 1722 | 1731 | // Clean up everything and wait for the child to exit. |
| 1723 | 1732 | child.stdin.?.close(io); |
| 1724 | 1733 | child.stdin = null; |
| 1725 | poller.deinit(); | |
| 1734 | multi_reader.deinit(); | |
| 1726 | 1735 | child_killed = true; |
| 1727 | 1736 | const term = try child.wait(io); |
| 1728 | 1737 | run.step.result_peak_rss = @max( |
| ... | ... | @@ -1770,8 +1779,9 @@ fn evalZigTest( |
| 1770 | 1779 | return; |
| 1771 | 1780 | }, |
| 1772 | 1781 | .timeout => |timeout| { |
| 1773 | const stderr = poller.reader(.stderr).buffered(); | |
| 1774 | poller.reader(.stderr).tossBuffered(); | |
| 1782 | const stderr_reader = multi_reader.reader(1); | |
| 1783 | const stderr = stderr_reader.buffered(); | |
| 1784 | stderr_reader.tossBuffered(); | |
| 1775 | 1785 | if (timeout.active_test_index) |test_index| { |
| 1776 | 1786 | // A test was running. Report the timeout against that test, and continue on to |
| 1777 | 1787 | // the next test. |
| ... | ... | @@ -1796,16 +1806,16 @@ fn evalZigTest( |
| 1796 | 1806 | } |
| 1797 | 1807 | } |
| 1798 | 1808 | |
| 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 | 1810 | /// * A write fails, indicating the child unexpectedly closed stdin |
| 1801 | 1811 | /// * A test (or a response from the test runner) times out |
| 1802 | /// * `poll` fails, indicating the child closed stdout and stderr | |
| 1803 | fn pollZigTest( | |
| 1812 | /// * The wait fails, indicating the child closed stdout and stderr | |
| 1813 | fn waitZigTest( | |
| 1804 | 1814 | run: *Run, |
| 1805 | 1815 | child: *process.Child, |
| 1806 | 1816 | options: Step.MakeOptions, |
| 1807 | 1817 | fuzz_context: ?FuzzContext, |
| 1808 | poller: *std.Io.Poller(StdioPollEnum), | |
| 1818 | multi_reader: *Io.File.MultiReader, | |
| 1809 | 1819 | opt_metadata: *?TestMetadata, |
| 1810 | 1820 | results: *Step.TestResults, |
| 1811 | 1821 | ) !union(enum) { |
| ... | ... | @@ -1859,9 +1869,7 @@ fn pollZigTest( |
| 1859 | 1869 | |
| 1860 | 1870 | var active_test_index: ?u32 = null; |
| 1861 | 1871 | |
| 1862 | // `null` means this host does not support `std.time.Timer`. This timer is `reset()` whenever we | |
| 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; | |
| 1872 | var last_update: Io.Clock.Timestamp = try .now(io, .awake); | |
| 1865 | 1873 | |
| 1866 | 1874 | var coverage_id: ?u64 = null; |
| 1867 | 1875 | |
| ... | ... | @@ -1869,16 +1877,26 @@ fn pollZigTest( |
| 1869 | 1877 | // test. For instance, if the test runner leaves this much time between us requesting a test to |
| 1870 | 1878 | // start and it acknowledging the test starting, we terminate the child and raise an error. This |
| 1871 | 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: { | |
| 1873 | if (fuzz_context != null) break :ns null; // don't timeout fuzz tests | |
| 1874 | break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); | |
| 1880 | const response_timeout: ?Io.Clock.Duration = t: { | |
| 1881 | if (fuzz_context != null) break :t null; // don't timeout fuzz tests | |
| 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; | |
| 1876 | 1889 | |
| 1877 | const stdout = poller.reader(.stdout); | |
| 1878 | const stderr = poller.reader(.stderr); | |
| 1890 | const stdout = multi_reader.reader(0); | |
| 1891 | const stderr = multi_reader.reader(1); | |
| 1892 | const Header = std.zig.Server.Message.Header; | |
| 1879 | 1893 | |
| 1880 | 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 | }; | |
| 1882 | 1900 | |
| 1883 | 1901 | // This block is exited when `stdout` contains enough bytes for a `Header`. |
| 1884 | 1902 | header_ready: { |
| ... | ... | @@ -1887,47 +1905,37 @@ fn pollZigTest( |
| 1887 | 1905 | break :header_ready; |
| 1888 | 1906 | } |
| 1889 | 1907 | |
| 1890 | // Always `null` if `timer` is `null`. | |
| 1891 | const opt_timeout_ns: ?u64 = ns: { | |
| 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 = .{ | |
| 1908 | multi_reader.fill(64, timeout) catch |err| switch (err) { | |
| 1909 | error.Timeout => return .{ .timeout = .{ | |
| 1900 | 1910 | .active_test_index = active_test_index, |
| 1901 | .ns_elapsed = if (timer) |*t| t.read() else 0, | |
| 1902 | } }; | |
| 1903 | } else { | |
| 1904 | if (!try poller.poll()) return .{ .no_poll = .{ | |
| 1911 | .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), | |
| 1912 | } }, | |
| 1913 | error.EndOfStream => return .{ .no_poll = .{ | |
| 1905 | 1914 | .active_test_index = active_test_index, |
| 1906 | .ns_elapsed = if (timer) |*t| t.read() else 0, | |
| 1907 | } }; | |
| 1908 | } | |
| 1909 | ||
| 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 | } | |
| 1915 | .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), | |
| 1916 | } }, | |
| 1917 | else => |e| return e, | |
| 1918 | }; | |
| 1914 | 1919 | |
| 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 | 1920 | continue; |
| 1923 | 1921 | } |
| 1924 | 1922 | // There is definitely a header available now -- read it. |
| 1925 | 1923 | const header = stdout.takeStruct(Header, .little) catch unreachable; |
| 1926 | 1924 | |
| 1927 | while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) return .{ .no_poll = .{ | |
| 1928 | .active_test_index = active_test_index, | |
| 1929 | .ns_elapsed = if (timer) |*t| t.read() else 0, | |
| 1930 | } }; | |
| 1925 | while (stdout.buffered().len < header.bytes_len) { | |
| 1926 | multi_reader.fill(64, timeout) catch |err| switch (err) { | |
| 1927 | error.Timeout => return .{ .timeout = .{ | |
| 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 | 1939 | const body = stdout.take(header.bytes_len) catch unreachable; |
| 1932 | 1940 | var body_r: std.Io.Reader = .fixed(body); |
| 1933 | 1941 | switch (header.tag) { |
| ... | ... | @@ -1968,13 +1976,13 @@ fn pollZigTest( |
| 1968 | 1976 | @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); |
| 1969 | 1977 | |
| 1970 | 1978 | active_test_index = null; |
| 1971 | if (timer) |*t| t.reset(); | |
| 1979 | last_update = try .now(io, .awake); | |
| 1972 | 1980 | |
| 1973 | 1981 | requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; |
| 1974 | 1982 | }, |
| 1975 | 1983 | .test_started => { |
| 1976 | 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 | 1987 | .test_results => { |
| 1980 | 1988 | assert(fuzz_context == null); |
| ... | ... | @@ -2017,7 +2025,10 @@ fn pollZigTest( |
| 2017 | 2025 | } |
| 2018 | 2026 | |
| 2019 | 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; | |
| 2021 | 2032 | |
| 2022 | 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 | 2175 | const b = run.step.owner; |
| 2165 | 2176 | const io = b.graph.io; |
| 2166 | 2177 | const arena = b.allocator; |
| 2178 | const gpa = b.allocator; | |
| 2167 | 2179 | |
| 2168 | 2180 | var child = try process.spawn(io, spawn_options); |
| 2169 | 2181 | defer child.kill(io); |
| ... | ... | @@ -2211,23 +2223,31 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul |
| 2211 | 2223 | |
| 2212 | 2224 | if (child.stdout) |stdout| { |
| 2213 | 2225 | if (child.stderr) |stderr| { |
| 2214 | var poller = std.Io.poll(arena, enum { stdout, stderr }, .{ | |
| 2215 | .stdout = stdout, | |
| 2216 | .stderr = stderr, | |
| 2217 | }); | |
| 2218 | defer poller.deinit(); | |
| 2226 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 2227 | var multi_reader: Io.File.MultiReader = undefined; | |
| 2228 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr }); | |
| 2229 | defer multi_reader.deinit(); | |
| 2230 | ||
| 2231 | const stdout_reader = multi_reader.reader(0); | |
| 2232 | const stderr_reader = multi_reader.reader(1); | |
| 2219 | 2233 | |
| 2220 | while (try poller.poll()) { | |
| 2234 | while (multi_reader.fill(64, .none)) |_| { | |
| 2221 | 2235 | if (run.stdio_limit.toInt()) |limit| { |
| 2222 | if (poller.reader(.stderr).buffered().len > limit) | |
| 2236 | if (stdout_reader.buffered().len > limit) | |
| 2223 | 2237 | return error.StdoutStreamTooLong; |
| 2224 | if (poller.reader(.stderr).buffered().len > limit) | |
| 2238 | if (stderr_reader.buffered().len > limit) | |
| 2225 | 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 | } |
| 2228 | 2246 | |
| 2229 | stdout_bytes = try poller.toOwnedSlice(.stdout); | |
| 2230 | stderr_bytes = try poller.toOwnedSlice(.stderr); | |
| 2247 | try multi_reader.checkAnyError(); | |
| 2248 | ||
| 2249 | stdout_bytes = try multi_reader.toOwnedSlice(0); | |
| 2250 | stderr_bytes = try multi_reader.toOwnedSlice(1); | |
| 2231 | 2251 | } else { |
| 2232 | 2252 | var stdout_reader = stdout.readerStreaming(io, &.{}); |
| 2233 | 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 | 588 | }); |
| 589 | 589 | defer child.kill(io); |
| 590 | 590 | |
| 591 | var poller = Io.poll(gpa, enum { stdout, stderr }, .{ | |
| 592 | .stdout = child.stdout.?, | |
| 593 | .stderr = child.stderr.?, | |
| 594 | }); | |
| 595 | defer poller.deinit(); | |
| 591 | var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited }); | |
| 592 | defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {}; | |
| 593 | ||
| 594 | var stdout_buffer: [512]u8 = undefined; | |
| 595 | var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer); | |
| 596 | const stdout = &stdout_reader.interface; | |
| 596 | 597 | |
| 597 | 598 | try child.stdin.?.writeStreamingAll(io, @ptrCast(@as([]const std.zig.Client.Message.Header, &.{ |
| 598 | 599 | .{ .tag = .update, .bytes_len = 0 }, |
| ... | ... | @@ -600,16 +601,17 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 600 | 601 | }))); |
| 601 | 602 | |
| 602 | 603 | const Header = std.zig.Server.Message.Header; |
| 604 | ||
| 603 | 605 | var result: ?Cache.Path = null; |
| 604 | 606 | var result_error_bundle = std.zig.ErrorBundle.empty; |
| 607 | var body_buffer: std.ArrayList(u8) = .empty; | |
| 608 | defer body_buffer.deinit(gpa); | |
| 605 | 609 | |
| 606 | const stdout = poller.reader(.stdout); | |
| 607 | ||
| 608 | poll: while (true) { | |
| 609 | while (stdout.buffered().len < @sizeOf(Header)) if (!(try poller.poll())) break :poll; | |
| 610 | const header = stdout.takeStruct(Header, .little) catch unreachable; | |
| 611 | while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; | |
| 612 | const body = stdout.take(header.bytes_len) catch unreachable; | |
| 610 | while (true) { | |
| 611 | const header = try stdout.takeStruct(Header, .little); | |
| 612 | body_buffer.clearRetainingCapacity(); | |
| 613 | try stdout.appendExact(gpa, &body_buffer, header.bytes_len); | |
| 614 | const body = body_buffer.items; | |
| 613 | 615 | |
| 614 | 616 | switch (header.tag) { |
| 615 | 617 | .zig_version => { |
| ... | ... | @@ -636,7 +638,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 636 | 638 | } |
| 637 | 639 | } |
| 638 | 640 | |
| 639 | const stderr_contents = try poller.toOwnedSlice(.stderr); | |
| 641 | const stderr_contents = try stderr_task.await(io); | |
| 640 | 642 | if (stderr_contents.len > 0) { |
| 641 | 643 | std.debug.print("{s}", .{stderr_contents}); |
| 642 | 644 | } |
| ... | ... | @@ -697,6 +699,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim |
| 697 | 699 | return base_path.join(arena, bin_name); |
| 698 | 700 | } |
| 699 | 701 | |
| 702 | fn 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 | ||
| 700 | 710 | pub fn updateTimeReportCompile(ws: *WebServer, opts: struct { |
| 701 | 711 | compile: *Build.Step.Compile, |
| 702 | 712 |
lib/std/Io.zig+241-461| ... | ... | @@ -15,463 +15,13 @@ |
| 15 | 15 | const Io = @This(); |
| 16 | 16 | |
| 17 | 17 | const builtin = @import("builtin"); |
| 18 | const is_windows = builtin.os.tag == .windows; | |
| 19 | 18 | |
| 20 | 19 | const std = @import("std.zig"); |
| 21 | const windows = std.os.windows; | |
| 22 | const posix = std.posix; | |
| 23 | 20 | const math = std.math; |
| 24 | 21 | const assert = std.debug.assert; |
| 25 | 22 | const Allocator = std.mem.Allocator; |
| 26 | 23 | const Alignment = std.mem.Alignment; |
| 27 | 24 | |
| 28 | pub 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 | ||
| 67 | pub 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. | |
| 471 | pub fn PollFiles(comptime StreamEnum: type) type { | |
| 472 | return @Struct(.auto, null, std.meta.fieldNames(StreamEnum), &@splat(Io.File), &@splat(.{})); | |
| 473 | } | |
| 474 | ||
| 475 | 25 | userdata: ?*anyopaque, |
| 476 | 26 | vtable: *const VTable, |
| 477 | 27 | |
| ... | ... | @@ -599,6 +149,11 @@ pub const VTable = struct { |
| 599 | 149 | futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, |
| 600 | 150 | futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, |
| 601 | 151 | |
| 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 | 157 | dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, |
| 603 | 158 | dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, |
| 604 | 159 | dirCreateDirPathOpen: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir, |
| ... | ... | @@ -633,9 +188,7 @@ pub const VTable = struct { |
| 633 | 188 | fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, |
| 634 | 189 | fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, |
| 635 | 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. | |
| 637 | fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.Reader.Error!usize, | |
| 638 | /// Returns 0 on end of stream. | |
| 191 | /// Returns 0 if reading at or past the end. | |
| 639 | 192 | fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize, |
| 640 | 193 | fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, |
| 641 | 194 | fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, |
| ... | ... | @@ -702,20 +255,247 @@ pub const VTable = struct { |
| 702 | 255 | netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void, |
| 703 | 256 | }; |
| 704 | 257 | |
| 258 | pub 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`. | |
| 354 | pub 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`. | |
| 363 | pub 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 | ||
| 705 | 485 | pub const Limit = enum(usize) { |
| 706 | 486 | nothing = 0, |
| 707 | unlimited = std.math.maxInt(usize), | |
| 487 | unlimited = math.maxInt(usize), | |
| 708 | 488 | _, |
| 709 | 489 | |
| 710 | /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`. | |
| 490 | /// `math.maxInt(usize)` is interpreted to mean `.unlimited`. | |
| 711 | 491 | pub fn limited(n: usize) Limit { |
| 712 | 492 | return @enumFromInt(n); |
| 713 | 493 | } |
| 714 | 494 | |
| 715 | /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean | |
| 495 | /// Any value grater than `math.maxInt(usize)` is interpreted to mean | |
| 716 | 496 | /// `.unlimited`. |
| 717 | 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 | } |
| 720 | 500 | |
| 721 | 501 | pub fn countVec(data: []const []const u8) Limit { |
| ... | ... | @@ -929,9 +709,9 @@ pub const Clock = enum { |
| 929 | 709 | }; |
| 930 | 710 | } |
| 931 | 711 | |
| 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 | 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 | }; |
| 937 | 717 | |
| ... | ... | @@ -996,7 +776,7 @@ pub const Duration = struct { |
| 996 | 776 | nanoseconds: i96, |
| 997 | 777 | |
| 998 | 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) }; | |
| 1000 | 780 | |
| 1001 | 781 | pub fn fromNanoseconds(x: i96) Duration { |
| 1002 | 782 | return .{ .nanoseconds = x }; |
| ... | ... | @@ -1652,7 +1432,7 @@ pub const Event = enum(u32) { |
| 1652 | 1432 | pub fn set(e: *Event, io: Io) void { |
| 1653 | 1433 | switch (@atomicRmw(Event, e, .Xchg, .is_set, .release)) { |
| 1654 | 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 | } |
| 1658 | 1438 |
lib/std/Io/File.zig+29-3| ... | ... | @@ -10,6 +10,18 @@ const assert = std.debug.assert; |
| 10 | 10 | const Dir = std.Io.Dir; |
| 11 | 11 | |
| 12 | 12 | handle: Handle, |
| 13 | flags: Flags, | |
| 14 | ||
| 15 | pub 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 | }; | |
| 13 | 25 | |
| 14 | 26 | pub const Handle = std.posix.fd_t; |
| 15 | 27 | |
| ... | ... | @@ -18,6 +30,9 @@ pub const Writer = @import("File/Writer.zig"); |
| 18 | 30 | pub const Atomic = @import("File/Atomic.zig"); |
| 19 | 31 | /// Memory intended to remain consistent with file contents. |
| 20 | 32 | pub const MemoryMap = @import("File/MemoryMap.zig"); |
| 33 | /// Concurrently read from multiple file streams, eliminating risk of | |
| 34 | /// deadlocking. | |
| 35 | pub const MultiReader = @import("File/MultiReader.zig"); | |
| 21 | 36 | |
| 22 | 37 | pub const INode = std.posix.ino_t; |
| 23 | 38 | pub const NLink = std.posix.nlink_t; |
| ... | ... | @@ -77,9 +92,11 @@ pub fn stdout() File { |
| 77 | 92 | return switch (native_os) { |
| 78 | 93 | .windows => .{ |
| 79 | 94 | .handle = std.os.windows.peb().ProcessParameters.hStdOutput, |
| 95 | .flags = .{ .nonblocking = false }, | |
| 80 | 96 | }, |
| 81 | 97 | else => .{ |
| 82 | 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 | 105 | return switch (native_os) { |
| 89 | 106 | .windows => .{ |
| 90 | 107 | .handle = std.os.windows.peb().ProcessParameters.hStdError, |
| 108 | .flags = .{ .nonblocking = false }, | |
| 91 | 109 | }, |
| 92 | 110 | else => .{ |
| 93 | 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 | 118 | return switch (native_os) { |
| 100 | 119 | .windows => .{ |
| 101 | 120 | .handle = std.os.windows.peb().ProcessParameters.hStdInput, |
| 121 | .flags = .{ .nonblocking = false }, | |
| 102 | 122 | }, |
| 103 | 123 | else => .{ |
| 104 | 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 | 570 | }); |
| 550 | 571 | } |
| 551 | 572 | |
| 573 | pub const ReadStreamingError = error{EndOfStream} || Reader.Error; | |
| 574 | ||
| 552 | 575 | /// Returns 0 on stream end or if `buffer` has no space available for data. |
| 553 | 576 | /// |
| 554 | 577 | /// See also: |
| 555 | 578 | /// * `reader` |
| 556 | pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize { | |
| 557 | return io.vtable.fileReadStreaming(io.userdata, file, buffer); | |
| 579 | pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize { | |
| 580 | const result = try io.operate(.{ .file_read_streaming = .{ | |
| 581 | .file = file, | |
| 582 | .data = buffer, | |
| 583 | } }); | |
| 584 | return result.file_read_streaming; | |
| 558 | 585 | } |
| 559 | 586 | |
| 560 | 587 | pub const ReadPositionalError = error{ |
| ... | ... | @@ -562,7 +589,6 @@ pub const ReadPositionalError = error{ |
| 562 | 589 | SystemResources, |
| 563 | 590 | /// Trying to read a directory file descriptor as if it were a file. |
| 564 | 591 | IsDir, |
| 565 | BrokenPipe, | |
| 566 | 592 | /// Non-blocking has been enabled, and reading from the file descriptor |
| 567 | 593 | /// would block. |
| 568 | 594 | WouldBlock, |
lib/std/Io/File/MultiReader.zig created+269| ... | ... | @@ -0,0 +1,269 @@ |
| 1 | const MultiReader = @This(); | |
| 2 | ||
| 3 | const std = @import("../../std.zig"); | |
| 4 | const Io = std.Io; | |
| 5 | const File = Io.File; | |
| 6 | const Allocator = std.mem.Allocator; | |
| 7 | const assert = std.debug.assert; | |
| 8 | ||
| 9 | gpa: Allocator, | |
| 10 | streams: *Streams, | |
| 11 | batch: Io.Batch, | |
| 12 | ||
| 13 | pub const Context = struct { | |
| 14 | mr: *MultiReader, | |
| 15 | fr: File.Reader, | |
| 16 | vec: [1][]u8, | |
| 17 | err: ?Error, | |
| 18 | }; | |
| 19 | ||
| 20 | pub const Error = UnendingError || error{EndOfStream}; | |
| 21 | pub const UnendingError = Allocator.Error || File.Reader.Error || Io.ConcurrentError; | |
| 22 | ||
| 23 | /// Trailing: | |
| 24 | /// * `contexts: [len]Context` | |
| 25 | /// * `storage: [len]Io.Operation.Storage` | |
| 26 | pub 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 | ||
| 43 | pub 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. | |
| 57 | pub 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 | ||
| 99 | pub 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 | ||
| 109 | pub fn fileReader(mr: *MultiReader, index: usize) *File.Reader { | |
| 110 | return &mr.streams.contexts()[index].fr; | |
| 111 | } | |
| 112 | ||
| 113 | pub 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`. | |
| 119 | pub 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 | ||
| 132 | pub 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 | ||
| 149 | fn 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 | ||
| 158 | fn 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 | ||
| 166 | fn 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 | ||
| 174 | fn 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 | ||
| 180 | fn 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 | ||
| 195 | pub 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. | |
| 202 | pub 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. | |
| 236 | pub 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 | ||
| 243 | fn 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 | 26 | seek_err: ?SeekError = null, |
| 27 | 27 | interface: Io.Reader, |
| 28 | 28 | |
| 29 | pub const Error = error{ | |
| 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; | |
| 29 | pub const Error = Io.Operation.FileReadStreaming.UnendingError || Io.Cancelable; | |
| 50 | 30 | |
| 51 | 31 | pub const SizeError = File.StatError || error{ |
| 52 | 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 | 280 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); |
| 301 | 281 | const dest = iovecs_buffer[0..dest_n]; |
| 302 | 282 | assert(dest[0].len > 0); |
| 303 | const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| { | |
| 304 | r.err = err; | |
| 305 | return error.ReadFailed; | |
| 283 | const n = r.file.readStreaming(io, dest) catch |err| switch (err) { | |
| 284 | error.EndOfStream => { | |
| 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 | 293 | r.pos += n; |
| 312 | 294 | if (n > data_size) { |
| 313 | 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 | 337 | const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); |
| 356 | 338 | const dest = iovecs_buffer[0..dest_n]; |
| 357 | 339 | assert(dest[0].len > 0); |
| 358 | const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| { | |
| 359 | r.err = err; | |
| 360 | return error.ReadFailed; | |
| 340 | const n = file.readStreaming(io, dest) catch |err| switch (err) { | |
| 341 | error.EndOfStream => { | |
| 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 | 350 | r.pos += n; |
| 367 | 351 | if (n > data_size) { |
| 368 | 352 | r.interface.end += n - data_size; |
lib/std/Io/Reader.zig+24-5| ... | ... | @@ -127,9 +127,7 @@ pub const ShortError = error{ |
| 127 | 127 | ReadFailed, |
| 128 | 128 | }; |
| 129 | 129 | |
| 130 | pub const RebaseError = error{ | |
| 131 | EndOfStream, | |
| 132 | }; | |
| 130 | pub const RebaseError = Error; | |
| 133 | 131 | |
| 134 | 132 | pub const failing: Reader = .{ |
| 135 | 133 | .vtable = &.{ |
| ... | ... | @@ -315,6 +313,27 @@ pub fn allocRemainingAlignedSentinel( |
| 315 | 313 | } |
| 316 | 314 | } |
| 317 | 315 | |
| 316 | pub const AppendExactError = Allocator.Error || Error; | |
| 317 | ||
| 318 | /// Transfers exactly `n` bytes from the reader to the `ArrayList`. | |
| 319 | /// | |
| 320 | /// See also: | |
| 321 | /// * `appendRemaining` | |
| 322 | pub 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 | 337 | /// Transfers all bytes from the current position to the end of the stream, up |
| 319 | 338 | /// to `limit`, appending them to `list`. |
| 320 | 339 | /// |
| ... | ... | @@ -1381,7 +1400,7 @@ pub fn takeLeb128(r: *Reader, comptime T: type) TakeLeb128Error!T { |
| 1381 | 1400 | } |
| 1382 | 1401 | |
| 1383 | 1402 | /// Ensures `capacity` data can be buffered without rebasing. |
| 1384 | pub fn rebase(r: *Reader, capacity: usize) RebaseError!void { | |
| 1403 | pub fn rebase(r: *Reader, capacity: usize) Error!void { | |
| 1385 | 1404 | if (r.buffer.len - r.seek >= capacity) { |
| 1386 | 1405 | @branchHint(.likely); |
| 1387 | 1406 | return; |
| ... | ... | @@ -1389,7 +1408,7 @@ pub fn rebase(r: *Reader, capacity: usize) RebaseError!void { |
| 1389 | 1408 | return r.vtable.rebase(r, capacity); |
| 1390 | 1409 | } |
| 1391 | 1410 | |
| 1392 | pub fn defaultRebase(r: *Reader, capacity: usize) RebaseError!void { | |
| 1411 | pub fn defaultRebase(r: *Reader, capacity: usize) Error!void { | |
| 1393 | 1412 | assert(r.buffer.len - r.seek < capacity); |
| 1394 | 1413 | const data = r.buffer[r.seek..r.end]; |
| 1395 | 1414 | @memmove(r.buffer[0..data.len], data); |
lib/std/Io/Threaded.zig+702-93| ... | ... | @@ -1255,6 +1255,32 @@ const AlertableSyscall = struct { |
| 1255 | 1255 | assert(is_windows); |
| 1256 | 1256 | } |
| 1257 | 1257 | |
| 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 | 1284 | fn checkCancel(s: AlertableSyscall) Io.Cancelable!void { |
| 1259 | 1285 | comptime assert(is_windows); |
| 1260 | 1286 | const thread = s.thread orelse return; |
| ... | ... | @@ -1314,8 +1340,17 @@ const AlertableSyscall = struct { |
| 1314 | 1340 | } |
| 1315 | 1341 | }; |
| 1316 | 1342 | |
| 1343 | fn 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 | ||
| 1317 | 1348 | const max_iovecs_len = 8; |
| 1318 | 1349 | const 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. | |
| 1353 | const poll_buffer_len = 64; | |
| 1319 | 1354 | const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; |
| 1320 | 1355 | |
| 1321 | 1356 | comptime { |
| ... | ... | @@ -1579,6 +1614,11 @@ pub fn io(t: *Threaded) Io { |
| 1579 | 1614 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1580 | 1615 | .futexWake = futexWake, |
| 1581 | 1616 | |
| 1617 | .operate = operate, | |
| 1618 | .batchAwaitAsync = batchAwaitAsync, | |
| 1619 | .batchAwaitConcurrent = batchAwaitConcurrent, | |
| 1620 | .batchCancel = batchCancel, | |
| 1621 | ||
| 1582 | 1622 | .dirCreateDir = dirCreateDir, |
| 1583 | 1623 | .dirCreateDirPath = dirCreateDirPath, |
| 1584 | 1624 | .dirCreateDirPathOpen = dirCreateDirPathOpen, |
| ... | ... | @@ -1613,7 +1653,6 @@ pub fn io(t: *Threaded) Io { |
| 1613 | 1653 | .fileWritePositional = fileWritePositional, |
| 1614 | 1654 | .fileWriteFileStreaming = fileWriteFileStreaming, |
| 1615 | 1655 | .fileWriteFilePositional = fileWriteFilePositional, |
| 1616 | .fileReadStreaming = fileReadStreaming, | |
| 1617 | 1656 | .fileReadPositional = fileReadPositional, |
| 1618 | 1657 | .fileSeekBy = fileSeekBy, |
| 1619 | 1658 | .fileSeekTo = fileSeekTo, |
| ... | ... | @@ -1739,6 +1778,11 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1739 | 1778 | .futexWaitUncancelable = futexWaitUncancelable, |
| 1740 | 1779 | .futexWake = futexWake, |
| 1741 | 1780 | |
| 1781 | .operate = operate, | |
| 1782 | .batchAwaitAsync = batchAwaitAsync, | |
| 1783 | .batchAwaitConcurrent = batchAwaitConcurrent, | |
| 1784 | .batchCancel = batchCancel, | |
| 1785 | ||
| 1742 | 1786 | .dirCreateDir = dirCreateDir, |
| 1743 | 1787 | .dirCreateDirPath = dirCreateDirPath, |
| 1744 | 1788 | .dirCreateDirPathOpen = dirCreateDirPathOpen, |
| ... | ... | @@ -1773,7 +1817,6 @@ pub fn ioBasic(t: *Threaded) Io { |
| 1773 | 1817 | .fileWritePositional = fileWritePositional, |
| 1774 | 1818 | .fileWriteFileStreaming = fileWriteFileStreaming, |
| 1775 | 1819 | .fileWriteFilePositional = fileWriteFilePositional, |
| 1776 | .fileReadStreaming = fileReadStreaming, | |
| 1777 | 1820 | .fileReadPositional = fileReadPositional, |
| 1778 | 1821 | .fileSeekBy = fileSeekBy, |
| 1779 | 1822 | .fileSeekTo = fileSeekTo, |
| ... | ... | @@ -2440,6 +2483,485 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { |
| 2440 | 2483 | Thread.futexWake(ptr, max_waiters); |
| 2441 | 2484 | } |
| 2442 | 2485 | |
| 2486 | fn 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 | ||
| 2498 | fn 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 | ||
| 2610 | fn 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 | ||
| 2771 | const 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 | ||
| 2790 | fn 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 | ||
| 2826 | fn 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. | |
| 2866 | fn 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 | ||
| 2958 | fn 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 | ||
| 2443 | 2965 | const dirCreateDir = switch (native_os) { |
| 2444 | 2966 | .windows => dirCreateDirWindows, |
| 2445 | 2967 | .wasi => dirCreateDirWasi, |
| ... | ... | @@ -2759,8 +3281,10 @@ fn dirCreateDirPathOpenWasi( |
| 2759 | 3281 | |
| 2760 | 3282 | fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { |
| 2761 | 3283 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 2762 | const file: File = .{ .handle = dir.handle }; | |
| 2763 | return fileStat(t, file); | |
| 3284 | return fileStat(t, .{ | |
| 3285 | .handle = dir.handle, | |
| 3286 | .flags = .{ .nonblocking = false }, | |
| 3287 | }); | |
| 2764 | 3288 | } |
| 2765 | 3289 | |
| 2766 | 3290 | const dirStatFile = switch (native_os) { |
| ... | ... | @@ -3552,7 +4076,10 @@ fn dirCreateFilePosix( |
| 3552 | 4076 | } |
| 3553 | 4077 | } |
| 3554 | 4078 | |
| 3555 | return .{ .handle = fd }; | |
| 4079 | return .{ | |
| 4080 | .handle = fd, | |
| 4081 | .flags = .{ .nonblocking = false }, | |
| 4082 | }; | |
| 3556 | 4083 | } |
| 3557 | 4084 | |
| 3558 | 4085 | fn dirCreateFileWindows( |
| ... | ... | @@ -3682,7 +4209,10 @@ fn dirCreateFileWindows( |
| 3682 | 4209 | errdefer windows.CloseHandle(handle); |
| 3683 | 4210 | |
| 3684 | 4211 | const exclusive = switch (flags.lock) { |
| 3685 | .none => return .{ .handle = handle }, | |
| 4212 | .none => return .{ | |
| 4213 | .handle = handle, | |
| 4214 | .flags = .{ .nonblocking = false }, | |
| 4215 | }, | |
| 3686 | 4216 | .shared => false, |
| 3687 | 4217 | .exclusive => true, |
| 3688 | 4218 | }; |
| ... | ... | @@ -3702,7 +4232,10 @@ fn dirCreateFileWindows( |
| 3702 | 4232 | )) { |
| 3703 | 4233 | .SUCCESS => { |
| 3704 | 4234 | syscall.finish(); |
| 3705 | return .{ .handle = handle }; | |
| 4235 | return .{ | |
| 4236 | .handle = handle, | |
| 4237 | .flags = .{ .nonblocking = false }, | |
| 4238 | }; | |
| 3706 | 4239 | }, |
| 3707 | 4240 | .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), |
| 3708 | 4241 | .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), |
| ... | ... | @@ -3751,7 +4284,10 @@ fn dirCreateFileWasi( |
| 3751 | 4284 | switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { |
| 3752 | 4285 | .SUCCESS => { |
| 3753 | 4286 | syscall.finish(); |
| 3754 | return .{ .handle = fd }; | |
| 4287 | return .{ | |
| 4288 | .handle = fd, | |
| 4289 | .flags = .{ .nonblocking = false }, | |
| 4290 | }; | |
| 3755 | 4291 | }, |
| 3756 | 4292 | .INTR => { |
| 3757 | 4293 | try syscall.checkCancel(); |
| ... | ... | @@ -3846,7 +4382,10 @@ fn dirCreateFileAtomic( |
| 3846 | 4382 | .SUCCESS => { |
| 3847 | 4383 | syscall.finish(); |
| 3848 | 4384 | return .{ |
| 3849 | .file = .{ .handle = @intCast(rc) }, | |
| 4385 | .file = .{ | |
| 4386 | .handle = @intCast(rc), | |
| 4387 | .flags = .{ .nonblocking = false }, | |
| 4388 | }, | |
| 3850 | 4389 | .file_basename_hex = 0, |
| 3851 | 4390 | .dest_sub_path = dest_path, |
| 3852 | 4391 | .file_open = true, |
| ... | ... | @@ -4054,7 +4593,10 @@ fn dirOpenFilePosix( |
| 4054 | 4593 | |
| 4055 | 4594 | if (!flags.allow_directory) { |
| 4056 | 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 | 4600 | // The directory-ness is either unknown or unknowable |
| 4059 | 4601 | error.Streaming => break :is_dir false, |
| 4060 | 4602 | else => |e| return e, |
| ... | ... | @@ -4140,7 +4682,10 @@ fn dirOpenFilePosix( |
| 4140 | 4682 | } |
| 4141 | 4683 | } |
| 4142 | 4684 | |
| 4143 | return .{ .handle = fd }; | |
| 4685 | return .{ | |
| 4686 | .handle = fd, | |
| 4687 | .flags = .{ .nonblocking = false }, | |
| 4688 | }; | |
| 4144 | 4689 | } |
| 4145 | 4690 | |
| 4146 | 4691 | fn dirOpenFileWindows( |
| ... | ... | @@ -4273,7 +4818,10 @@ pub fn dirOpenFileWtf16( |
| 4273 | 4818 | errdefer w.CloseHandle(handle); |
| 4274 | 4819 | |
| 4275 | 4820 | const exclusive = switch (flags.lock) { |
| 4276 | .none => return .{ .handle = handle }, | |
| 4821 | .none => return .{ | |
| 4822 | .handle = handle, | |
| 4823 | .flags = .{ .nonblocking = false }, | |
| 4824 | }, | |
| 4277 | 4825 | .shared => false, |
| 4278 | 4826 | .exclusive => true, |
| 4279 | 4827 | }; |
| ... | ... | @@ -4296,7 +4844,10 @@ pub fn dirOpenFileWtf16( |
| 4296 | 4844 | .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer |
| 4297 | 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 | } |
| 4301 | 4852 | |
| 4302 | 4853 | fn dirOpenFileWasi( |
| ... | ... | @@ -4378,7 +4929,7 @@ fn dirOpenFileWasi( |
| 4378 | 4929 | |
| 4379 | 4930 | if (!flags.allow_directory) { |
| 4380 | 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 | 4933 | // The directory-ness is either unknown or unknowable |
| 4383 | 4934 | error.Streaming => break :is_dir false, |
| 4384 | 4935 | else => |e| return e, |
| ... | ... | @@ -4388,7 +4939,10 @@ fn dirOpenFileWasi( |
| 4388 | 4939 | if (is_dir) return error.IsDir; |
| 4389 | 4940 | } |
| 4390 | 4941 | |
| 4391 | return .{ .handle = fd }; | |
| 4942 | return .{ | |
| 4943 | .handle = fd, | |
| 4944 | .flags = .{ .nonblocking = false }, | |
| 4945 | }; | |
| 4392 | 4946 | } |
| 4393 | 4947 | |
| 4394 | 4948 | const dirOpenDir = switch (native_os) { |
| ... | ... | @@ -5277,7 +5831,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, |
| 5277 | 5831 | |
| 5278 | 5832 | fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { |
| 5279 | 5833 | var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 5280 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks | |
| 5834 | // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks | |
| 5281 | 5835 | try Thread.checkCancel(); |
| 5282 | 5836 | const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); |
| 5283 | 5837 | |
| ... | ... | @@ -8275,14 +8829,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void { |
| 8275 | 8829 | for (files) |file| posix.close(file.handle); |
| 8276 | 8830 | } |
| 8277 | 8831 | |
| 8278 | fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { | |
| 8832 | fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize { | |
| 8279 | 8833 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 8280 | 8834 | _ = t; |
| 8281 | 8835 | if (is_windows) return fileReadStreamingWindows(file, data); |
| 8282 | 8836 | return fileReadStreamingPosix(file, data); |
| 8283 | 8837 | } |
| 8284 | 8838 | |
| 8285 | fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize { | |
| 8839 | fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize { | |
| 8286 | 8840 | var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; |
| 8287 | 8841 | var i: usize = 0; |
| 8288 | 8842 | for (data) |buf| { |
| ... | ... | @@ -8303,28 +8857,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz |
| 8303 | 8857 | switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { |
| 8304 | 8858 | .SUCCESS => { |
| 8305 | 8859 | syscall.finish(); |
| 8860 | if (nread == 0) return error.EndOfStream; | |
| 8306 | 8861 | return nread; |
| 8307 | 8862 | }, |
| 8308 | 8863 | .INTR, .TIMEDOUT => { |
| 8309 | 8864 | try syscall.checkCancel(); |
| 8310 | 8865 | continue; |
| 8311 | 8866 | }, |
| 8312 | else => |e| { | |
| 8313 | syscall.finish(); | |
| 8314 | switch (e) { | |
| 8315 | .INVAL => |err| return errnoBug(err), | |
| 8316 | .FAULT => |err| return errnoBug(err), | |
| 8317 | .BADF => return error.IsDir, // File operation on directory. | |
| 8318 | .IO => return error.InputOutput, | |
| 8319 | .ISDIR => return error.IsDir, | |
| 8320 | .NOBUFS => return error.SystemResources, | |
| 8321 | .NOMEM => return error.SystemResources, | |
| 8322 | .NOTCONN => return error.SocketUnconnected, | |
| 8323 | .CONNRESET => return error.ConnectionResetByPeer, | |
| 8324 | .NOTCAPABLE => return error.AccessDenied, | |
| 8325 | else => |err| return posix.unexpectedErrno(err), | |
| 8326 | } | |
| 8327 | }, | |
| 8867 | .BADF => return syscall.fail(error.IsDir), // File operation on directory. | |
| 8868 | .IO => return syscall.fail(error.InputOutput), | |
| 8869 | .ISDIR => return syscall.fail(error.IsDir), | |
| 8870 | .NOBUFS => return syscall.fail(error.SystemResources), | |
| 8871 | .NOMEM => return syscall.fail(error.SystemResources), | |
| 8872 | .NOTCONN => return syscall.fail(error.SocketUnconnected), | |
| 8873 | .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), | |
| 8874 | .NOTCAPABLE => return syscall.fail(error.AccessDenied), | |
| 8875 | .INVAL => |err| return syscall.errnoBug(err), | |
| 8876 | .FAULT => |err| return syscall.errnoBug(err), | |
| 8877 | else => |err| return syscall.unexpectedErrno(err), | |
| 8328 | 8878 | } |
| 8329 | 8879 | } |
| 8330 | 8880 | } |
| ... | ... | @@ -8335,75 +8885,115 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz |
| 8335 | 8885 | switch (posix.errno(rc)) { |
| 8336 | 8886 | .SUCCESS => { |
| 8337 | 8887 | syscall.finish(); |
| 8888 | if (rc == 0) return error.EndOfStream; | |
| 8338 | 8889 | return @intCast(rc); |
| 8339 | 8890 | }, |
| 8340 | 8891 | .INTR, .TIMEDOUT => { |
| 8341 | 8892 | try syscall.checkCancel(); |
| 8342 | 8893 | continue; |
| 8343 | 8894 | }, |
| 8344 | else => |e| { | |
| 8895 | .BADF => { | |
| 8345 | 8896 | syscall.finish(); |
| 8346 | switch (e) { | |
| 8347 | .INVAL => |err| return errnoBug(err), | |
| 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 | } | |
| 8897 | if (native_os == .wasi) return error.IsDir; // File operation on directory. | |
| 8898 | return error.NotOpenForReading; | |
| 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 | } |
| 8366 | 8913 | |
| 8367 | fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize { | |
| 8368 | const DWORD = windows.DWORD; | |
| 8914 | fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { | |
| 8369 | 8915 | var index: usize = 0; |
| 8370 | while (index < data.len and data[index].len == 0) index += 1; | |
| 8371 | if (index == data.len) return 0; | |
| 8916 | while (data.len - index != 0 and data[index].len == 0) index += 1; | |
| 8917 | if (data.len - index == 0) return 0; | |
| 8372 | 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); | |
| 8374 | 8920 | |
| 8375 | const syscall: Syscall = try .start(); | |
| 8376 | while (true) { | |
| 8377 | var n: DWORD = undefined; | |
| 8378 | if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) { | |
| 8379 | syscall.finish(); | |
| 8380 | return n; | |
| 8381 | } | |
| 8382 | switch (windows.GetLastError()) { | |
| 8383 | .IO_PENDING => |err| { | |
| 8384 | syscall.finish(); | |
| 8385 | return windows.errorBug(err); | |
| 8386 | }, | |
| 8387 | .OPERATION_ABORTED => { | |
| 8921 | var iosb: windows.IO_STATUS_BLOCK = undefined; | |
| 8922 | ||
| 8923 | if (!file.flags.nonblocking) { | |
| 8924 | const syscall: Syscall = try .start(); | |
| 8925 | while (true) switch (windows.ntdll.NtReadFile( | |
| 8926 | file.handle, | |
| 8927 | null, // event | |
| 8928 | null, // APC routine | |
| 8929 | null, // APC context | |
| 8930 | &iosb, | |
| 8931 | buffer.ptr, | |
| 8932 | short_buffer_len, | |
| 8933 | null, // byte offset | |
| 8934 | null, // key | |
| 8935 | )) { | |
| 8936 | .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag | |
| 8937 | .CANCELLED => { | |
| 8388 | 8938 | try syscall.checkCancel(); |
| 8389 | 8939 | continue; |
| 8390 | 8940 | }, |
| 8391 | .BROKEN_PIPE, .HANDLE_EOF => { | |
| 8941 | else => |status| { | |
| 8392 | 8942 | syscall.finish(); |
| 8393 | return 0; | |
| 8394 | }, | |
| 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); | |
| 8943 | iosb.u.Status = status; | |
| 8944 | return ntReadFileResult(&iosb); | |
| 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 | ||
| 8982 | fn flagApc(userdata: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void { | |
| 8983 | const flag: *bool = @ptrCast(userdata); | |
| 8984 | flag.* = true; | |
| 8985 | } | |
| 8986 | ||
| 8987 | fn 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 | } |
| 8409 | 8999 | |
| ... | ... | @@ -9037,7 +9627,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut |
| 9037 | 9627 | }; |
| 9038 | 9628 | defer w.CloseHandle(h_file); |
| 9039 | 9629 | |
| 9040 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks | |
| 9630 | // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks | |
| 9041 | 9631 | try Thread.checkCancel(); |
| 9042 | 9632 | const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); |
| 9043 | 9633 | |
| ... | ... | @@ -9359,6 +9949,7 @@ fn writeFileStreamingWindows( |
| 9359 | 9949 | handle: windows.HANDLE, |
| 9360 | 9950 | bytes: []const u8, |
| 9361 | 9951 | ) File.Writer.Error!usize { |
| 9952 | assert(bytes.len != 0); | |
| 9362 | 9953 | var bytes_written: windows.DWORD = undefined; |
| 9363 | 9954 | const adjusted_len = std.math.lossyCast(u32, bytes.len); |
| 9364 | 9955 | const syscall: Syscall = try .start(); |
| ... | ... | @@ -10075,6 +10666,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { |
| 10075 | 10666 | |
| 10076 | 10667 | fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { |
| 10077 | 10668 | const t: *Threaded = @ptrCast(@alignCast(userdata)); |
| 10669 | if (timeout == .none) return; | |
| 10078 | 10670 | if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t))); |
| 10079 | 10671 | if (native_os == .wasi) return sleepWasi(t, timeout); |
| 10080 | 10672 | if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); |
| ... | ... | @@ -12707,7 +13299,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr |
| 12707 | 13299 | |
| 12708 | 13300 | if (is_windows) { |
| 12709 | 13301 | var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; |
| 12710 | // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks | |
| 13302 | // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks | |
| 12711 | 13303 | try Thread.checkCancel(); |
| 12712 | 13304 | const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); |
| 12713 | 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 | 14490 | .pid = pid, |
| 13899 | 14491 | .err_fd = err_pipe[0], |
| 13900 | 14492 | .stdin = switch (options.stdin) { |
| 13901 | .pipe => .{ .handle = stdin_pipe[1] }, | |
| 14493 | .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } }, | |
| 13902 | 14494 | else => null, |
| 13903 | 14495 | }, |
| 13904 | 14496 | .stdout = switch (options.stdout) { |
| 13905 | .pipe => .{ .handle = stdout_pipe[0] }, | |
| 14497 | .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } }, | |
| 13906 | 14498 | else => null, |
| 13907 | 14499 | }, |
| 13908 | 14500 | .stderr = switch (options.stderr) { |
| 13909 | .pipe => .{ .handle = stderr_pipe[0] }, | |
| 14501 | .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } }, | |
| 13910 | 14502 | else => null, |
| 13911 | 14503 | }, |
| 13912 | 14504 | }; |
| ... | ... | @@ -14560,9 +15152,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro |
| 14560 | 15152 | return .{ |
| 14561 | 15153 | .id = piProcInfo.hProcess, |
| 14562 | 15154 | .thread_handle = piProcInfo.hThread, |
| 14563 | .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null, | |
| 14564 | .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null, | |
| 14565 | .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null, | |
| 15155 | .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null, | |
| 15156 | .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, | |
| 15157 | .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, | |
| 14566 | 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 | 15199 | t.mutex.lock(); // Another thread might have won the race. |
| 14608 | 15200 | defer t.mutex.unlock(); |
| 14609 | 15201 | if (t.random_file.handle) |prev_handle| { |
| 14610 | _ = windows.ntdll.NtClose(fresh_handle); | |
| 15202 | windows.CloseHandle(fresh_handle); | |
| 14611 | 15203 | return prev_handle; |
| 14612 | 15204 | } else { |
| 14613 | 15205 | t.random_file.handle = fresh_handle; |
| ... | ... | @@ -15696,6 +16288,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { |
| 15696 | 16288 | .pointer => @ptrFromInt(int), |
| 15697 | 16289 | else => return error.UnsupportedOperation, |
| 15698 | 16290 | }, |
| 16291 | .flags = .{ .nonblocking = false }, | |
| 15699 | 16292 | }; |
| 15700 | 16293 | } |
| 15701 | 16294 | |
| ... | ... | @@ -16375,7 +16968,7 @@ const parking_sleep = struct { |
| 16375 | 16968 | /// Spurious wakeups are possible. |
| 16376 | 16969 | /// |
| 16377 | 16970 | /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. |
| 16378 | fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { | |
| 16971 | fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { | |
| 16379 | 16972 | comptime assert(use_parking_futex or use_parking_sleep); |
| 16380 | 16973 | switch (native_os) { |
| 16381 | 16974 | .windows => { |
| ... | ... | @@ -16431,6 +17024,22 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err |
| 16431 | 17024 | } |
| 16432 | 17025 | } |
| 16433 | 17026 | |
| 17027 | fn 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 | ||
| 16434 | 17043 | const UnparkTid = switch (native_os) { |
| 16435 | 17044 | // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles? |
| 16436 | 17045 | .windows => usize, |
lib/std/Io/Threaded/test.zig+2-2| ... | ... | @@ -188,8 +188,8 @@ test "cancel blocked read from pipe" { |
| 188 | 188 | }), |
| 189 | 189 | else => { |
| 190 | 190 | const pipe = try std.Io.Threaded.pipe2(.{}); |
| 191 | read_end = .{ .handle = pipe[0] }; | |
| 192 | write_end = .{ .handle = pipe[1] }; | |
| 191 | read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; | |
| 192 | write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; | |
| 193 | 193 | }, |
| 194 | 194 | } |
| 195 | 195 | defer { |
lib/std/Progress.zig+2-2| ... | ... | @@ -979,12 +979,13 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff |
| 979 | 979 | if (main_parent == .unused) continue; |
| 980 | 980 | const file: Io.File = .{ |
| 981 | 981 | .handle = main_storage.getIpcFd() orelse continue, |
| 982 | .flags = .{ .nonblocking = true }, | |
| 982 | 983 | }; |
| 983 | 984 | const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); |
| 984 | 985 | var bytes_read: usize = 0; |
| 985 | 986 | while (true) { |
| 986 | 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 | 989 | else => |e| { |
| 989 | 990 | std.log.debug("failed to read child progress data: {t}", .{e}); |
| 990 | 991 | main_storage.completed_count = 0; |
| ... | ... | @@ -992,7 +993,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff |
| 992 | 993 | continue :main_loop; |
| 993 | 994 | }, |
| 994 | 995 | }; |
| 995 | if (n == 0) break; | |
| 996 | 996 | if (opt_saved_metadata) |m| { |
| 997 | 997 | if (m.remaining_read_trash_bytes > 0) { |
| 998 | 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 | 336 | // Ensure the input buffer pointer is stable in this scope. |
| 337 | 337 | input.rebase(tls.max_ciphertext_record_len) catch |err| switch (err) { |
| 338 | 338 | error.EndOfStream => {}, // We have assurance the remainder of stream can be buffered. |
| 339 | error.ReadFailed => |e| return e, | |
| 339 | 340 | }; |
| 340 | 341 | const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { |
| 341 | 342 | error.EndOfStream => return error.TlsConnectionTruncated, |
| 342 | error.ReadFailed => return error.ReadFailed, | |
| 343 | error.ReadFailed => |e| return e, | |
| 343 | 344 | }; |
| 344 | 345 | const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked |
| 345 | 346 | input.toss(2); // legacy_version |
lib/std/os/windows/kernel32.zig-3| ... | ... | @@ -188,9 +188,6 @@ pub extern "kernel32" fn PostQueuedCompletionStatus( |
| 188 | 188 | lpOverlapped: ?*OVERLAPPED, |
| 189 | 189 | ) callconv(.winapi) BOOL; |
| 190 | 190 | |
| 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? | |
| 194 | 191 | pub extern "kernel32" fn GetOverlappedResult( |
| 195 | 192 | hFile: HANDLE, |
| 196 | 193 | lpOverlapped: *OVERLAPPED, |
lib/std/os/windows/ntdll.zig+9-2| ... | ... | @@ -594,6 +594,13 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile( |
| 594 | 594 | IoStatusBlock: *IO_STATUS_BLOCK, |
| 595 | 595 | ) callconv(.winapi) NTSTATUS; |
| 596 | 596 | |
| 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. | |
| 597 | 604 | pub extern "ntdll" fn NtDelayExecution( |
| 598 | 605 | Alertable: BOOLEAN, |
| 599 | 606 | DelayInterval: *const LARGE_INTEGER, |
| ... | ... | @@ -606,6 +613,6 @@ pub extern "ntdll" fn NtCancelIoFileEx( |
| 606 | 613 | ) callconv(.winapi) NTSTATUS; |
| 607 | 614 | |
| 608 | 615 | pub extern "ntdll" fn NtCancelIoFile( |
| 609 | handle: HANDLE, | |
| 610 | iosbToCancel: *const IO_STATUS_BLOCK, | |
| 616 | FileHandle: HANDLE, | |
| 617 | IoStatusBlock: *IO_STATUS_BLOCK, | |
| 611 | 618 | ) callconv(.winapi) NTSTATUS; |
lib/std/posix/test.zig+6-3| ... | ... | @@ -126,8 +126,8 @@ test "pipe" { |
| 126 | 126 | const io = testing.io; |
| 127 | 127 | |
| 128 | 128 | const fds = try std.Io.Threaded.pipe2(.{}); |
| 129 | const out: Io.File = .{ .handle = fds[0] }; | |
| 130 | const in: Io.File = .{ .handle = fds[1] }; | |
| 129 | const out: Io.File = .{ .handle = fds[0], .flags = .{ .nonblocking = false } }; | |
| 130 | const in: Io.File = .{ .handle = fds[1], .flags = .{ .nonblocking = false } }; | |
| 131 | 131 | try in.writeStreamingAll(io, "hello"); |
| 132 | 132 | var buf: [16]u8 = undefined; |
| 133 | 133 | try expect((try out.readStreaming(io, &.{&buf})) == 5); |
| ... | ... | @@ -150,7 +150,10 @@ test "memfd_create" { |
| 150 | 150 | else => return error.SkipZigTest, |
| 151 | 151 | } |
| 152 | 152 | |
| 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 | 157 | defer file.close(io); |
| 155 | 158 | try file.writePositionalAll(io, "test", 0); |
| 156 | 159 |
lib/std/process.zig+37-15| ... | ... | @@ -453,14 +453,16 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { |
| 453 | 453 | return io.vtable.processSpawnPath(io.userdata, dir, options); |
| 454 | 454 | } |
| 455 | 455 | |
| 456 | pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{ | |
| 457 | StdoutStreamTooLong, | |
| 458 | StderrStreamTooLong, | |
| 459 | }; | |
| 456 | pub const RunError = error{ | |
| 457 | StreamTooLong, | |
| 458 | } || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error; | |
| 460 | 459 | |
| 461 | 460 | pub const RunOptions = struct { |
| 462 | 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, | |
| 464 | 466 | |
| 465 | 467 | /// Set to change the current working directory when spawning the child process. |
| 466 | 468 | cwd: ?[]const u8 = null, |
| ... | ... | @@ -486,6 +488,7 @@ pub const RunOptions = struct { |
| 486 | 488 | create_no_window: bool = true, |
| 487 | 489 | /// Darwin-only. Disable ASLR for the child process. |
| 488 | 490 | disable_aslr: bool = false, |
| 491 | timeout: Io.Timeout = .none, | |
| 489 | 492 | }; |
| 490 | 493 | |
| 491 | 494 | pub const RunResult = struct { |
| ... | ... | @@ -513,22 +516,41 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { |
| 513 | 516 | }); |
| 514 | 517 | defer child.kill(io); |
| 515 | 518 | |
| 516 | var stdout: std.ArrayList(u8) = .empty; | |
| 517 | defer stdout.deinit(gpa); | |
| 518 | var stderr: std.ArrayList(u8) = .empty; | |
| 519 | defer stderr.deinit(gpa); | |
| 519 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 520 | var multi_reader: Io.File.MultiReader = undefined; | |
| 521 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); | |
| 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 | } | |
| 520 | 540 | |
| 521 | try child.collectOutput(gpa, &stdout, &stderr, options.max_output_bytes); | |
| 541 | try multi_reader.checkAnyError(); | |
| 522 | 542 | |
| 523 | 543 | const term = try child.wait(io); |
| 524 | 544 | |
| 525 | const owned_stdout = try stdout.toOwnedSlice(gpa); | |
| 526 | errdefer gpa.free(owned_stdout); | |
| 527 | const owned_stderr = try stderr.toOwnedSlice(gpa); | |
| 545 | const stdout_slice = try multi_reader.toOwnedSlice(0); | |
| 546 | errdefer gpa.free(stdout_slice); | |
| 547 | ||
| 548 | const stderr_slice = try multi_reader.toOwnedSlice(1); | |
| 549 | errdefer gpa.free(stderr_slice); | |
| 528 | 550 | |
| 529 | 551 | return .{ |
| 530 | .stdout = owned_stdout, | |
| 531 | .stderr = owned_stderr, | |
| 552 | .stdout = stdout_slice, | |
| 553 | .stderr = stderr_slice, | |
| 532 | 554 | .term = term, |
| 533 | 555 | }; |
| 534 | 556 | } |
lib/std/process/Child.zig-52| ... | ... | @@ -9,7 +9,6 @@ const process = std.process; |
| 9 | 9 | const File = std.Io.File; |
| 10 | 10 | const assert = std.debug.assert; |
| 11 | 11 | const Allocator = std.mem.Allocator; |
| 12 | const ArrayList = std.ArrayList; | |
| 13 | 12 | |
| 14 | 13 | pub const Id = switch (native_os) { |
| 15 | 14 | .windows => std.os.windows.HANDLE, |
| ... | ... | @@ -125,54 +124,3 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { |
| 125 | 124 | assert(child.id != null); |
| 126 | 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`. | |
| 135 | pub 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 | 29 | switch (native_os) { |
| 30 | 30 | .wasi => { |
| 31 | 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 | 36 | return .{ .dir = .{ .handle = @intCast(index) } }; |
| 34 | 37 | }, |
| 35 | 38 | else => { |
lib/std/zig/LibCInstallation.zig+4-2| ... | ... | @@ -268,7 +268,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar |
| 268 | 268 | }); |
| 269 | 269 | |
| 270 | 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 | 273 | .argv = argv.items, |
| 273 | 274 | .environ_map = &environ_map, |
| 274 | 275 | // 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 | 585 | try argv.append(arg1); |
| 585 | 586 | |
| 586 | 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 | 590 | .argv = argv.items, |
| 589 | 591 | .environ_map = &environ_map, |
| 590 | 592 | // 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 | 420 | error.Canceled => |e| return e, |
| 421 | 421 | error.Unexpected => |e| return e, |
| 422 | 422 | error.WouldBlock => return error.Unexpected, |
| 423 | error.BrokenPipe => return error.Unexpected, | |
| 424 | 423 | error.ConnectionResetByPeer => return error.Unexpected, |
| 425 | 424 | error.NotOpenForReading => return error.Unexpected, |
| 426 | 425 | error.SocketUnconnected => return error.Unexpected, |
src/Compilation.zig+33-18| ... | ... | @@ -6873,6 +6873,7 @@ fn spawnZigRc( |
| 6873 | 6873 | child_progress_node: std.Progress.Node, |
| 6874 | 6874 | ) !void { |
| 6875 | 6875 | const io = comp.io; |
| 6876 | const gpa = comp.gpa; | |
| 6876 | 6877 | var node_name: std.ArrayList(u8) = .empty; |
| 6877 | 6878 | defer node_name.deinit(arena); |
| 6878 | 6879 | |
| ... | ... | @@ -6887,55 +6888,69 @@ fn spawnZigRc( |
| 6887 | 6888 | }); |
| 6888 | 6889 | defer child.kill(io); |
| 6889 | 6890 | |
| 6890 | var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{ | |
| 6891 | .stdout = child.stdout.?, | |
| 6892 | .stderr = child.stderr.?, | |
| 6893 | }); | |
| 6894 | defer poller.deinit(); | |
| 6891 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 6892 | var multi_reader: Io.File.MultiReader = undefined; | |
| 6893 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); | |
| 6894 | defer multi_reader.deinit(); | |
| 6895 | 6895 | |
| 6896 | const stdout = poller.reader(.stdout); | |
| 6896 | const stdout = multi_reader.fileReader(0); | |
| 6897 | const MessageHeader = std.zig.Server.Message.Header; | |
| 6897 | 6898 | |
| 6898 | poll: while (true) { | |
| 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; | |
| 6899 | var eos_err: error{EndOfStream}!void = {}; | |
| 6904 | 6900 | |
| 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 | 6916 | switch (header.tag) { |
| 6906 | 6917 | // We expect exactly one ErrorBundle, and if any error_bundle header is |
| 6907 | 6918 | // sent then it's a fatal error. |
| 6908 | 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 | 6921 | return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle); |
| 6911 | 6922 | }, |
| 6912 | 6923 | else => {}, // ignore other messages |
| 6913 | 6924 | } |
| 6914 | 6925 | } |
| 6915 | 6926 | |
| 6916 | // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) | |
| 6917 | const stderr = poller.reader(.stderr); | |
| 6927 | try multi_reader.fillRemaining(.none); | |
| 6918 | 6928 | |
| 6929 | // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) | |
| 6919 | 6930 | const term = child.wait(io) catch |err| { |
| 6920 | 6931 | return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {t}", .{ argv[0], err }); |
| 6921 | 6932 | }; |
| 6922 | 6933 | |
| 6934 | const stderr = multi_reader.reader(1).buffered(); | |
| 6935 | ||
| 6923 | 6936 | switch (term) { |
| 6924 | 6937 | .exited => |code| { |
| 6925 | 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 | 6940 | return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code}); |
| 6928 | 6941 | } |
| 6929 | 6942 | }, |
| 6930 | 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 | 6945 | return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); |
| 6933 | 6946 | }, |
| 6934 | 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 | 6949 | return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); |
| 6937 | 6950 | }, |
| 6938 | 6951 | } |
| 6952 | ||
| 6953 | try eos_err; | |
| 6939 | 6954 | } |
| 6940 | 6955 | |
| 6941 | 6956 | pub 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 | 2389 | .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) { |
| 2390 | 2390 | .none => true, |
| 2391 | 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 | 2438 | .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) { |
| 2439 | 2439 | .none => true, |
| 2440 | 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 | 2444 | if (!kind.isParameter()) return array_ctype; |
src/link.zig+9-2| ... | ... | @@ -605,8 +605,8 @@ pub const File = struct { |
| 605 | 605 | switch (base.tag) { |
| 606 | 606 | .lld => assert(base.file == null), |
| 607 | 607 | .elf, .macho, .wasm => { |
| 608 | if (base.file != null) return; | |
| 609 | 608 | dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker }); |
| 609 | if (base.file != null) return; | |
| 610 | 610 | const emit = base.emit; |
| 611 | 611 | if (base.child_pid) |pid| { |
| 612 | 612 | if (builtin.os.tag == .windows) { |
| ... | ... | @@ -645,6 +645,7 @@ pub const File = struct { |
| 645 | 645 | base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write }); |
| 646 | 646 | }, |
| 647 | 647 | .elf2, .coff2 => if (base.file == null) { |
| 648 | dev.checkAny(&.{ .elf2_linker, .coff2_linker }); | |
| 648 | 649 | const mf = if (base.cast(.elf2)) |elf| |
| 649 | 650 | &elf.mf |
| 650 | 651 | else if (base.cast(.coff2)) |coff| |
| ... | ... | @@ -657,7 +658,13 @@ pub const File = struct { |
| 657 | 658 | base.file = mf.memory_map.file; |
| 658 | 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 | 668 | .plan9 => unreachable, |
| 662 | 669 | } |
| 663 | 670 | } |
tools/doctest.zig-6| ... | ... | @@ -201,7 +201,6 @@ fn printOutput( |
| 201 | 201 | .argv = build_args.items, |
| 202 | 202 | .cwd = tmp_dir_path, |
| 203 | 203 | .environ_map = environ_map, |
| 204 | .max_output_bytes = max_doc_file_size, | |
| 205 | 204 | }); |
| 206 | 205 | switch (result.term) { |
| 207 | 206 | .exited => |exit_code| { |
| ... | ... | @@ -257,7 +256,6 @@ fn printOutput( |
| 257 | 256 | .argv = run_args, |
| 258 | 257 | .environ_map = environ_map, |
| 259 | 258 | .cwd = tmp_dir_path, |
| 260 | .max_output_bytes = max_doc_file_size, | |
| 261 | 259 | }); |
| 262 | 260 | switch (result.term) { |
| 263 | 261 | .exited => |exit_code| { |
| ... | ... | @@ -376,7 +374,6 @@ fn printOutput( |
| 376 | 374 | .argv = test_args.items, |
| 377 | 375 | .environ_map = environ_map, |
| 378 | 376 | .cwd = tmp_dir_path, |
| 379 | .max_output_bytes = max_doc_file_size, | |
| 380 | 377 | }); |
| 381 | 378 | switch (result.term) { |
| 382 | 379 | .exited => |exit_code| { |
| ... | ... | @@ -432,7 +429,6 @@ fn printOutput( |
| 432 | 429 | .argv = test_args.items, |
| 433 | 430 | .environ_map = environ_map, |
| 434 | 431 | .cwd = tmp_dir_path, |
| 435 | .max_output_bytes = max_doc_file_size, | |
| 436 | 432 | }); |
| 437 | 433 | switch (result.term) { |
| 438 | 434 | .exited => |exit_code| { |
| ... | ... | @@ -508,7 +504,6 @@ fn printOutput( |
| 508 | 504 | .argv = build_args.items, |
| 509 | 505 | .environ_map = environ_map, |
| 510 | 506 | .cwd = tmp_dir_path, |
| 511 | .max_output_bytes = max_doc_file_size, | |
| 512 | 507 | }); |
| 513 | 508 | switch (result.term) { |
| 514 | 509 | .exited => |exit_code| { |
| ... | ... | @@ -1132,7 +1127,6 @@ fn run( |
| 1132 | 1127 | .argv = args, |
| 1133 | 1128 | .environ_map = environ_map, |
| 1134 | 1129 | .cwd = cwd, |
| 1135 | .max_output_bytes = max_doc_file_size, | |
| 1136 | 1130 | }); |
| 1137 | 1131 | switch (result.term) { |
| 1138 | 1132 | .exited => |exit_code| { |
tools/incr-check.zig+45-37| ... | ... | @@ -28,6 +28,7 @@ fn logImpl( |
| 28 | 28 | } |
| 29 | 29 | |
| 30 | 30 | pub fn main(init: std.process.Init) !void { |
| 31 | const gpa = init.gpa; | |
| 31 | 32 | const fatal = std.process.fatal; |
| 32 | 33 | const arena = init.arena.allocator(); |
| 33 | 34 | const io = init.io; |
| ... | ... | @@ -224,11 +225,10 @@ pub fn main(init: std.process.Init) !void { |
| 224 | 225 | .enable_darling = enable_darling, |
| 225 | 226 | }; |
| 226 | 227 | |
| 227 | var poller = Io.poll(arena, Eval.StreamEnum, .{ | |
| 228 | .stdout = child.stdout.?, | |
| 229 | .stderr = child.stderr.?, | |
| 230 | }); | |
| 231 | defer poller.deinit(); | |
| 228 | var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; | |
| 229 | var multi_reader: Io.File.MultiReader = undefined; | |
| 230 | multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); | |
| 231 | defer multi_reader.deinit(); | |
| 232 | 232 | |
| 233 | 233 | for (case.updates) |update| { |
| 234 | 234 | var update_node = target_prog_node.start(update.name, 0); |
| ... | ... | @@ -243,10 +243,10 @@ pub fn main(init: std.process.Init) !void { |
| 243 | 243 | |
| 244 | 244 | eval.write(update); |
| 245 | 245 | try eval.requestUpdate(); |
| 246 | try eval.check(&poller, update, update_node); | |
| 246 | try eval.check(&multi_reader, update, update_node); | |
| 247 | 247 | } |
| 248 | 248 | |
| 249 | try eval.end(&poller); | |
| 249 | try eval.end(&multi_reader); | |
| 250 | 250 | |
| 251 | 251 | waitChild(&child, &eval); |
| 252 | 252 | } |
| ... | ... | @@ -272,9 +272,6 @@ const Eval = struct { |
| 272 | 272 | enable_wasmtime: bool, |
| 273 | 273 | enable_darling: bool, |
| 274 | 274 | |
| 275 | const StreamEnum = enum { stdout, stderr }; | |
| 276 | const Poller = Io.Poller(StreamEnum); | |
| 277 | ||
| 278 | 275 | /// Currently this function assumes the previous updates have already been written. |
| 279 | 276 | fn write(eval: *Eval, update: Case.Update) void { |
| 280 | 277 | const io = eval.io; |
| ... | ... | @@ -293,23 +290,29 @@ const Eval = struct { |
| 293 | 290 | } |
| 294 | 291 | } |
| 295 | 292 | |
| 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 | 294 | const arena = eval.arena; |
| 298 | const stdout = poller.reader(.stdout); | |
| 299 | const stderr = poller.reader(.stderr); | |
| 300 | ||
| 301 | poll: while (true) { | |
| 302 | const Header = std.zig.Server.Message.Header; | |
| 303 | while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll; | |
| 304 | const header = stdout.takeStruct(Header, .little) catch unreachable; | |
| 305 | while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; | |
| 306 | const body = stdout.take(header.bytes_len) catch unreachable; | |
| 295 | const stdout = mr.fileReader(0); | |
| 296 | const stderr = &mr.fileReader(1).interface; | |
| 297 | const Header = std.zig.Server.Message.Header; | |
| 298 | ||
| 299 | while (true) { | |
| 300 | const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { | |
| 301 | error.EndOfStream => break, | |
| 302 | error.ReadFailed => return stdout.err.?, | |
| 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 | }; | |
| 307 | 310 | |
| 308 | 311 | switch (header.tag) { |
| 309 | 312 | .error_bundle => { |
| 310 | 313 | const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); |
| 311 | 314 | if (stderr.bufferedLen() > 0) { |
| 312 | const stderr_data = try poller.toOwnedSlice(.stderr); | |
| 315 | const stderr_data = try mr.toOwnedSlice(1); | |
| 313 | 316 | if (eval.allow_stderr) { |
| 314 | 317 | std.log.info("error_bundle stderr:\n{s}", .{stderr_data}); |
| 315 | 318 | } else { |
| ... | ... | @@ -326,7 +329,7 @@ const Eval = struct { |
| 326 | 329 | var r: std.Io.Reader = .fixed(body); |
| 327 | 330 | _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; |
| 328 | 331 | if (stderr.bufferedLen() > 0) { |
| 329 | const stderr_data = try poller.toOwnedSlice(.stderr); | |
| 332 | const stderr_data = try mr.toOwnedSlice(1); | |
| 330 | 333 | if (eval.allow_stderr) { |
| 331 | 334 | std.log.info("emit_digest stderr:\n{s}", .{stderr_data}); |
| 332 | 335 | } else { |
| ... | ... | @@ -358,11 +361,12 @@ const Eval = struct { |
| 358 | 361 | } |
| 359 | 362 | } |
| 360 | 363 | |
| 361 | if (stderr.bufferedLen() > 0) { | |
| 364 | const buffered_stderr = stderr.buffered(); | |
| 365 | if (buffered_stderr.len > 0) { | |
| 362 | 366 | if (eval.allow_stderr) { |
| 363 | std.log.info("stderr:\n{s}", .{stderr.buffered()}); | |
| 367 | std.log.info("stderr:\n{s}", .{buffered_stderr}); | |
| 364 | 368 | } else { |
| 365 | eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()}); | |
| 369 | eval.fatal("unexpected stderr:\n{s}", .{buffered_stderr}); | |
| 366 | 370 | } |
| 367 | 371 | } |
| 368 | 372 | |
| ... | ... | @@ -588,23 +592,27 @@ const Eval = struct { |
| 588 | 592 | }; |
| 589 | 593 | } |
| 590 | 594 | |
| 591 | fn end(eval: *Eval, poller: *Poller) !void { | |
| 595 | fn end(eval: *Eval, mr: *Io.File.MultiReader) !void { | |
| 592 | 596 | requestExit(eval.child, eval); |
| 593 | 597 | |
| 594 | const stdout = poller.reader(.stdout); | |
| 595 | const stderr = poller.reader(.stderr); | |
| 598 | const stdout = mr.fileReader(0); | |
| 599 | const Header = std.zig.Server.Message.Header; | |
| 596 | 600 | |
| 597 | poll: while (true) { | |
| 598 | const Header = std.zig.Server.Message.Header; | |
| 599 | while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll; | |
| 600 | const header = stdout.takeStruct(Header, .little) catch unreachable; | |
| 601 | while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; | |
| 602 | stdout.toss(header.bytes_len); | |
| 601 | while (true) { | |
| 602 | const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { | |
| 603 | error.EndOfStream => break, | |
| 604 | error.ReadFailed => return stdout.err.?, | |
| 605 | }; | |
| 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 | } |
| 604 | 611 | |
| 605 | if (stderr.bufferedLen() > 0) { | |
| 606 | eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()}); | |
| 607 | } | |
| 612 | try mr.fillRemaining(.none); | |
| 613 | ||
| 614 | const stderr = mr.reader(1).buffered(); | |
| 615 | if (stderr.len > 0) eval.fatal("unexpected stderr:\n{s}", .{stderr}); | |
| 608 | 616 | } |
| 609 | 617 | |
| 610 | 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 | 676 | |
| 677 | 677 | const child_result = try std.process.run(arena, io, .{ |
| 678 | 678 | .argv = &child_args, |
| 679 | .max_output_bytes = 100 * 1024 * 1024, | |
| 680 | 679 | }); |
| 681 | 680 | |
| 682 | 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 | 1987 | |
| 1988 | 1988 | const child_result = try std.process.run(arena, io, .{ |
| 1989 | 1989 | .argv = &child_args, |
| 1990 | .max_output_bytes = 500 * 1024 * 1024, | |
| 1991 | 1990 | }); |
| 1992 | 1991 | tblgen_progress.end(); |
| 1993 | 1992 | if (child_result.stderr.len != 0) { |