authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-26 19:07:01-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 12:10:01-08:00
loge1604c19230bf67773501603f5c772b95ce01ad9
tree4b1048ad96d4799d58ed1afb0d6ca20cfc0226a6
parent351a3dd997ec99589e0689e631625551adc34422

std.Io.Threaded: batchWait and batchCancel for Windows


3 files changed, 217 insertions(+), 124 deletions(-)

lib/std/Build/Step/Run.zig-1
...@@ -1721,7 +1721,6 @@ fn evalZigTest(...@@ -1721,7 +1721,6 @@ fn evalZigTest(
1721 // a crash of some kind. Either way, the child will terminate by itself -- wait for it.1721 // a crash of some kind. Either way, the child will terminate by itself -- wait for it.
1722 const stderr_reader = multi_reader.reader(1);1722 const stderr_reader = multi_reader.reader(1);
1723 const stderr_owned = try arena.dupe(u8, stderr_reader.buffered());1723 const stderr_owned = try arena.dupe(u8, stderr_reader.buffered());
1724 stderr_reader.tossBuffered();
17251724
1726 // Clean up everything and wait for the child to exit.1725 // Clean up everything and wait for the child to exit.
1727 child.stdin.?.close(io);1726 child.stdin.?.close(io);
lib/std/Io.zig+8
...@@ -350,6 +350,8 @@ pub const Batch = struct {...@@ -350,6 +350,8 @@ pub const Batch = struct {
350 }350 }
351 };351 };
352352
353 /// After calling this, it is safe to unconditionally defer a call to
354 /// `cancel`.
353 pub fn init(operations: []Operation, ring: []u32) Batch {355 pub fn init(operations: []Operation, ring: []u32) Batch {
354 const len: u31 = @intCast(operations.len);356 const len: u31 = @intCast(operations.len);
355 assert(ring.len == len);357 assert(ring.len == len);
...@@ -408,12 +410,18 @@ pub const Batch = struct {...@@ -408,12 +410,18 @@ pub const Batch = struct {
408 /// Starts work on any submitted operations and returns when at least one has completeed.410 /// Starts work on any submitted operations and returns when at least one has completeed.
409 ///411 ///
410 /// Returns `error.Timeout` if `timeout` expires first.412 /// Returns `error.Timeout` if `timeout` expires first.
413 ///
414 /// Depending on the `Io` implementation, may allocate resources that are
415 /// freed with `cancel`, even if an error is returned.
411 pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void {416 pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void {
412 return io.vtable.batchWait(io.userdata, b, timeout);417 return io.vtable.batchWait(io.userdata, b, timeout);
413 }418 }
414419
415 /// Returns after all `operations` have completed. Operations which have not completed420 /// Returns after all `operations` have completed. Operations which have not completed
416 /// after this function returns were successfully dropped and had no side effects.421 /// after this function returns were successfully dropped and had no side effects.
422 ///
423 /// This function is idempotent with respect to itself and `wait`. It is
424 /// safe to unconditionally `defer` a call to this function after `init`.
417 pub fn cancel(b: *Batch, io: Io) void {425 pub fn cancel(b: *Batch, io: Io) void {
418 return io.vtable.batchCancel(io.userdata, b);426 return io.vtable.batchCancel(io.userdata, b);
419 }427 }
lib/std/Io/Threaded.zig+209-123
...@@ -1255,6 +1255,32 @@ const AlertableSyscall = struct {...@@ -1255,6 +1255,32 @@ const AlertableSyscall = struct {
1255 assert(is_windows);1255 assert(is_windows);
1256 }1256 }
12571257
1258 fn start() Io.Cancelable!AlertableSyscall {
1259 const thread = Thread.current orelse return .{ .thread = null };
1260 switch (thread.cancel_protection) {
1261 .blocked => return .{ .thread = null },
1262 .unblocked => {},
1263 }
1264 const old_status = thread.status.fetchOr(.{
1265 .cancelation = @enumFromInt(0b010),
1266 .awaitable = .null,
1267 }, .monotonic);
1268 switch (old_status.cancelation) {
1269 .parked => unreachable,
1270 .blocked => unreachable,
1271 .blocked_alertable => unreachable,
1272 .blocked_canceling => unreachable,
1273 .blocked_alertable_canceling => unreachable,
1274 .none => return .{ .thread = thread }, // new status is `.blocked_alertable`
1275 .canceling => {
1276 // Status is unchanged (still `.canceling`)---change to `.canceled` before return.
1277 thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic);
1278 return error.Canceled;
1279 },
1280 .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged)
1281 }
1282 }
1283
1258 fn checkCancel(s: AlertableSyscall) Io.Cancelable!void {1284 fn checkCancel(s: AlertableSyscall) Io.Cancelable!void {
1259 comptime assert(is_windows);1285 comptime assert(is_windows);
1260 const thread = s.thread orelse return;1286 const thread = s.thread orelse return;
...@@ -2501,10 +2527,10 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch....@@ -2501,10 +2527,10 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.
2501 const op = ring[submit_head.index(len)];2527 const op = ring[submit_head.index(len)];
2502 const operation = &operations[op];2528 const operation = &operations[op];
2503 switch (operation.*) {2529 switch (operation.*) {
2504 .noop => {2530 .noop => |*o| {
2505 try operate(t, operation);2531 _ = o.status.unstarted;
2506 ring[complete_tail.index(len)] = op;2532 o.status = .{ .result = {} };
2507 complete_tail = complete_tail.next(len);2533 submitComplete(ring, &complete_tail, op);
2508 },2534 },
2509 .file_read_streaming => |*o| {2535 .file_read_streaming => |*o| {
2510 _ = o.status.unstarted;2536 _ = o.status.unstarted;
...@@ -2524,8 +2550,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch....@@ -2524,8 +2550,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.
2524 1 => if (timeout == .none) {2550 1 => if (timeout == .none) {
2525 const op = map_buffer[0];2551 const op = map_buffer[0];
2526 try operate(t, &operations[op]);2552 try operate(t, &operations[op]);
2527 ring[complete_tail.index(len)] = op;2553 submitComplete(ring, &complete_tail, op);
2528 complete_tail = complete_tail.next(len);
2529 poll_i = 0;2554 poll_i = 0;
2530 return;2555 return;
2531 },2556 },
...@@ -2560,8 +2585,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch....@@ -2560,8 +2585,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.
2560 ring[submit_head.index(len)] = op;2585 ring[submit_head.index(len)] = op;
2561 } else {2586 } else {
2562 try operate(t, &operations[op]);2587 try operate(t, &operations[op]);
2563 ring[complete_tail.index(len)] = op;2588 submitComplete(ring, &complete_tail, op);
2564 complete_tail = complete_tail.next(len);
2565 }2589 }
2566 }2590 }
2567 return;2591 return;
...@@ -2584,19 +2608,49 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void {...@@ -2584,19 +2608,49 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void {
2584 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {2608 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {
2585 const op = ring[submit_head.index(len)];2609 const op = ring[submit_head.index(len)];
2586 switch (operations[op]) {2610 switch (operations[op]) {
2587 .noop => {2611 .noop => |*o| {
2588 operate(t, &operations[op]) catch unreachable;2612 _ = o.status.unstarted;
2589 ring[complete_tail.index(len)] = op;2613 o.status = .{ .result = {} };
2590 complete_tail = complete_tail.next(len);2614 submitComplete(ring, &complete_tail, op);
2591 },2615 },
2592 .file_read_streaming => |*o| _ = o.status.unstarted,2616 .file_read_streaming => |*o| _ = o.status.unstarted,
2593 }2617 }
2594 }2618 }
2619 if (is_windows) {
2620 // Iterate over pending and issue cancelations, then free the allocation for IO_STATUS_BLOCK
2621 if (b.impl.reserved) |reserved| {
2622 const gpa = t.allocator;
2623 const metadatas_ptr: [*]WinOpMetadata = @ptrCast(@alignCast(reserved));
2624 const metadatas = metadatas_ptr[0..b.operations.len];
2625 for (metadatas, 0..) |*metadata, op| {
2626 const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING;
2627 if (done) continue;
2628 switch (operations[op]) {
2629 .noop => unreachable,
2630 .file_read_streaming => |*o| {
2631 _ = windows.ntdll.NtCancelIoFile(o.file.handle, &metadata.iosb);
2632 },
2633 }
2634 }
2635 for (metadatas) |*metadata| {
2636 while (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) {
2637 waitForApcOrAlert();
2638 }
2639 }
2640 gpa.free(metadatas);
2641 b.impl.reserved = null;
2642 }
2643 }
2595 b.impl.submit_head = submit_tail;2644 b.impl.submit_head = submit_tail;
2596 b.impl.complete_tail = complete_tail;2645 b.impl.complete_tail = complete_tail;
2597 b.user.complete_tail = complete_tail;2646 b.user.complete_tail = complete_tail;
2598}2647}
25992648
2649const WinOpMetadata = struct {
2650 iosb: windows.IO_STATUS_BLOCK,
2651 pending: bool,
2652};
2653
2600fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void {2654fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void {
2601 const operations = b.operations;2655 const operations = b.operations;
2602 const len: u31 = @intCast(operations.len);2656 const len: u31 = @intCast(operations.len);
...@@ -2606,16 +2660,16 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa...@@ -2606,16 +2660,16 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
2606 b.impl.submit_tail = submit_tail;2660 b.impl.submit_tail = submit_tail;
2607 var complete_tail = b.impl.complete_tail;2661 var complete_tail = b.impl.complete_tail;
26082662
2609 var overlapped_buffer: [poll_buffer_len]windows.OVERLAPPED = undefined;2663 const metadatas_ptr: [*]WinOpMetadata = if (b.impl.reserved) |reserved| @ptrCast(@alignCast(reserved)) else a: {
2610 var handles_buffer: [poll_buffer_len]windows.HANDLE = undefined;2664 const gpa = t.allocator;
2611 var map_buffer: [poll_buffer_len]u32 = undefined; // handles_buffer index to operations index2665 const metadatas = gpa.alloc(WinOpMetadata, operations.len) catch return error.ConcurrencyUnavailable;
2612 var buffer_i: usize = 0;2666 b.impl.reserved = metadatas.ptr;
2667 @memset(metadatas, .{ .iosb = undefined, .pending = false });
2668 break :a metadatas.ptr;
2669 };
2670 const metadatas = metadatas_ptr[0..operations.len];
26132671
2614 defer {2672 defer {
2615 for (map_buffer[0..buffer_i]) |op| {
2616 submit_head = submit_head.prev(len);
2617 ring[submit_head.index(len)] = op;
2618 }
2619 b.impl.submit_head = submit_head;2673 b.impl.submit_head = submit_head;
2620 b.impl.complete_tail = complete_tail;2674 b.impl.complete_tail = complete_tail;
2621 b.user.complete_tail = complete_tail;2675 b.user.complete_tail = complete_tail;
...@@ -2624,74 +2678,76 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa...@@ -2624,74 +2678,76 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
2624 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {2678 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {
2625 const op = ring[submit_head.index(len)];2679 const op = ring[submit_head.index(len)];
2626 const operation = &operations[op];2680 const operation = &operations[op];
2681 const metadata = &metadatas[op];
2682 metadata.* = .{ .iosb = undefined, .pending = false };
2627 switch (operation.*) {2683 switch (operation.*) {
2628 .noop => {2684 .noop => |*o| {
2629 try operate(t, operation);2685 _ = o.status.unstarted;
2630 ring[complete_tail.index(len)] = op;2686 o.status = .{ .result = {} };
2631 complete_tail = complete_tail.next(len);2687 submitComplete(ring, &complete_tail, op);
2632 },2688 },
2633 .file_read_streaming => |*o| {2689 .file_read_streaming => |*o| {
2634 _ = o.status.unstarted;2690 _ = o.status.unstarted;
2635 if (handles_buffer.len - buffer_i == 0) return error.ConcurrencyUnavailable;2691 switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) {
2636 const overlapped = &overlapped_buffer[buffer_i];2692 .status => {
2637 overlapped.* = .{2693 o.status = .{ .result = ntReadFileResult(&metadata.iosb) };
2638 .Internal = 0,2694 submitComplete(ring, &complete_tail, op);
2639 .InternalHigh = 0,2695 },
2640 .DUMMYUNIONNAME = .{ .Pointer = null },2696 .pending => {
2641 .hEvent = null,2697 o.status = .{ .pending = b };
2642 };2698 metadata.pending = true;
2643 var n: windows.DWORD = undefined;2699 },
2644 const buf = o.data[0];
2645 const buf_len = std.math.lossyCast(windows.DWORD, buf.len);
2646 if (windows.kernel32.ReadFile(o.file.handle, buf.ptr, buf_len, &n, overlapped) == 0) {
2647 @panic("TODO");
2648 }2700 }
2649 handles_buffer[buffer_i] = o.file.handle;
2650 map_buffer[buffer_i] = op;
2651 buffer_i += 1;
2652 },2701 },
2653 }2702 }
2654 }2703 }
26552704
2656 switch (buffer_i) {2705 var delay_interval: windows.LARGE_INTEGER = timeoutToWindowsInterval(timeout);
2657 0 => return,
2658 1 => if (timeout == .none) {
2659 const op = map_buffer[0];
2660 try operate(t, &operations[op]);
2661 ring[complete_tail.index(len)] = op;
2662 complete_tail = complete_tail.next(len);
2663 buffer_i = 0;
2664 return;
2665 },
2666 else => {},
2667 }
2668
2669 const handles = handles_buffer[0..buffer_i];
2670 const map = map_buffer[0..buffer_i];
26712706
2672 const syscall: Syscall = try .start();2707 while (true) {
2673 const index_result = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true);2708 const alertable_syscall = try AlertableSyscall.start();
2674 syscall.finish();2709 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);
2675 const index = index_result catch |err| switch (err) {2710 alertable_syscall.finish();
2676 error.Unexpected => @panic("TODO"),2711 switch (delay_rc) {
2677 error.WaitAbandoned => @panic("TODO"),2712 .SUCCESS => {
2678 error.WaitTimeOut => @panic("TODO"),2713 // The thread woke due to the timeout. Although spurious
2679 };2714 // timeouts are OK, when no deadline is passed we must not
2680 var n: windows.DWORD = undefined;2715 // return `error.Timeout`.
2681 if (0 == windows.kernel32.GetOverlappedResult(handles[index], &overlapped_buffer[index], &n, 0)) {2716 if (timeout != .none) return error.Timeout;
2682 switch (windows.GetLastError()) {2717 },
2683 .BROKEN_PIPE => @panic("TODO"),2718 else => {},
2684 .OPERATION_ABORTED => @panic("TODO"),
2685 else => @panic("TODO"),
2686 }2719 }
2687 } else switch (operations[map[index]]) {2720 var any_done = false;
2688 .noop => unreachable,2721 var any_pending = false;
2689 .file_read_streaming => |*o| {2722 for (metadatas, 0..) |*metadata, op_usize| {
2690 o.status = .{ .result = n };2723 if (!metadata.pending) continue;
2691 },2724 any_pending = true;
2725 const op: u31 = @intCast(op_usize);
2726 const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING;
2727 switch (operations[op]) {
2728 .noop => unreachable,
2729 .file_read_streaming => |*o| {
2730 assert(o.status.pending == b);
2731 if (!done) continue;
2732 o.status = .{ .result = ntReadFileResult(&metadata.iosb) };
2733 },
2734 }
2735 any_done = true;
2736 metadata.pending = false;
2737 submitComplete(ring, &complete_tail, op);
2738 }
2739 if (any_done) return;
2740 if (!any_pending) return;
2692 }2741 }
2693}2742}
26942743
2744fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void {
2745 const ct = complete_tail.*;
2746 const len: u31 = @intCast(ring.len);
2747 ring[ct.index(len)] = op;
2748 complete_tail.* = ct.next(len);
2749}
2750
2695const dirCreateDir = switch (native_os) {2751const dirCreateDir = switch (native_os) {
2696 .windows => dirCreateDirWindows,2752 .windows => dirCreateDirWindows,
2697 .wasi => dirCreateDirWasi,2753 .wasi => dirCreateDirWasi,
...@@ -5529,7 +5585,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,...@@ -5529,7 +5585,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,
55295585
5530fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {5586fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
5531 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;5587 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
5532 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks5588 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
5533 try Thread.checkCancel();5589 try Thread.checkCancel();
5534 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);5590 const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf);
55355591
...@@ -8617,75 +8673,88 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz...@@ -8617,75 +8673,88 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz
8617}8673}
86188674
8619fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {8675fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize {
8676 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8677 if (ntReadFile(file.handle, data, &io_status_block)) |result| switch (result) {
8678 .status => return ntReadFileResult(&io_status_block),
8679 .pending => {
8680 // Once we get here we received PENDING so we must not return from the
8681 // function until the operation completes.
8682 defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8683 waitForApcOrAlert();
8684 };
8685
8686 const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) {
8687 error.Canceled => |e| {
8688 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
8689 return e;
8690 },
8691 };
8692 defer alertable_syscall.finish();
8693 waitForApcOrAlert();
8694 while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8695 alertable_syscall.checkCancel() catch |err| switch (err) {
8696 error.Canceled => |e| {
8697 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
8698 return e;
8699 },
8700 };
8701 waitForApcOrAlert();
8702 }
8703 },
8704 } else |err| return err;
8705 return ntReadFileResult(&io_status_block);
8706}
8707
8708fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize {
8709 switch (io_status_block.u.Status) {
8710 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information,
8711 .PENDING => unreachable,
8712 .INVALID_DEVICE_REQUEST => return error.IsDir,
8713 .LOCK_NOT_GRANTED => return error.LockViolation,
8714 .ACCESS_DENIED => return error.AccessDenied,
8715 else => |status| return windows.unexpectedStatus(status),
8716 }
8717}
8718
8719fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STATUS_BLOCK) Io.Cancelable!enum { status, pending } {
8620 var index: usize = 0;8720 var index: usize = 0;
8621 while (index < data.len and data[index].len == 0) index += 1;8721 while (index < data.len and data[index].len == 0) index += 1;
8622 if (index == data.len) return 0;8722 if (index == data.len) {
8723 iosb.u.Status = .SUCCESS;
8724 iosb.Information = 0;
8725 return .status;
8726 }
8623 const buffer = data[index];8727 const buffer = data[index];
86248728
8625 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
8626 const syscall: Syscall = try .start();8729 const syscall: Syscall = try .start();
8627 while (true) {8730 while (true) {
8628 io_status_block.u.Status = .PENDING;8731 iosb.u.Status = .PENDING;
8629 switch (windows.ntdll.NtReadFile(8732 switch (windows.ntdll.NtReadFile(
8630 file.handle,8733 handle,
8631 null, // event8734 null, // event
8632 noopApc, // apc callback8735 noopApc, // apc callback
8633 null, // apc context8736 null, // apc context
8634 &io_status_block,8737 iosb,
8635 buffer.ptr,8738 buffer.ptr,
8636 @min(std.math.maxInt(u32), buffer.len),8739 @min(std.math.maxInt(u32), buffer.len),
8637 null, // byte offset8740 null, // byte offset
8638 null, // key8741 null, // key
8639 )) {8742 )) {
8640 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {8743 .PENDING => {
8641 syscall.finish();8744 syscall.finish();
8642 return io_status_block.Information;8745 return .pending;
8643 },8746 },
8644 .PENDING => break,
8645 .CANCELLED => {8747 .CANCELLED => {
8646 try syscall.checkCancel();8748 try syscall.checkCancel();
8647 continue;8749 continue;
8648 },8750 },
8649 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),8751 else => |status| {
8650 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),8752 syscall.finish();
8651 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),8753 iosb.u.Status = status;
8652 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file8754 return .status;
8653 else => |status| return syscall.unexpectedNtstatus(status),
8654 }
8655 }
8656 {
8657 // Once we get here we received PENDING so we must not return from the
8658 // function until the operation completes.
8659 defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8660 waitForApcOrAlert();
8661 };
8662
8663 const alertable_syscall = syscall.toAlertable() catch |err| switch (err) {
8664 error.Canceled => |e| {
8665 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
8666 return e;
8667 },8755 },
8668 };
8669 defer alertable_syscall.finish();
8670 waitForApcOrAlert();
8671 while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) {
8672 alertable_syscall.checkCancel() catch |err| switch (err) {
8673 error.Canceled => |e| {
8674 _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block);
8675 return e;
8676 },
8677 };
8678 waitForApcOrAlert();
8679 }8756 }
8680 }8757 }
8681 switch (io_status_block.u.Status) {
8682 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information,
8683 .PENDING => unreachable, // cannot return until the operation completes
8684 .INVALID_DEVICE_REQUEST => return error.IsDir,
8685 .LOCK_NOT_GRANTED => return error.LockViolation,
8686 .ACCESS_DENIED => return error.AccessDenied,
8687 else => |status| return windows.unexpectedStatus(status),
8688 }
8689}8758}
86908759
8691fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {8760fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
...@@ -9318,7 +9387,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut...@@ -9318,7 +9387,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut
9318 };9387 };
9319 defer w.CloseHandle(h_file);9388 defer w.CloseHandle(h_file);
93209389
9321 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks9390 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
9322 try Thread.checkCancel();9391 try Thread.checkCancel();
9323 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);9392 const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data);
93249393
...@@ -12989,7 +13058,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr...@@ -12989,7 +13058,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr
1298913058
12990 if (is_windows) {13059 if (is_windows) {
12991 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;13060 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
12992 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks13061 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
12993 try Thread.checkCancel();13062 try Thread.checkCancel();
12994 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);13063 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
12995 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;13064 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
...@@ -16657,7 +16726,7 @@ const parking_sleep = struct {...@@ -16657,7 +16726,7 @@ const parking_sleep = struct {
16657/// Spurious wakeups are possible.16726/// Spurious wakeups are possible.
16658///16727///
16659/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.16728/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation.
16660fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void {16729fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void {
16661 comptime assert(use_parking_futex or use_parking_sleep);16730 comptime assert(use_parking_futex or use_parking_sleep);
16662 switch (native_os) {16731 switch (native_os) {
16663 .windows => {16732 .windows => {
...@@ -16713,6 +16782,23 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err...@@ -16713,6 +16782,23 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err
16713 }16782 }
16714}16783}
1671516784
16785fn timeoutToWindowsInterval(timeout: Io.Timeout) windows.LARGE_INTEGER {
16786 switch (timeout) {
16787 .none => {
16788 return std.math.minInt(windows.LARGE_INTEGER); // infinite timeout
16789 },
16790 .deadline => |deadline| {
16791 const nanoseconds = deadline.raw.nanoseconds;
16792 return @intCast(@divTrunc(nanoseconds, 100));
16793 },
16794 .duration => |duration| {
16795 const now_timestamp = nowWindows(duration.clock) catch unreachable;
16796 const deadline_ns = now_timestamp.nanoseconds + duration.raw.nanoseconds;
16797 return @intCast(@divTrunc(deadline_ns, 100));
16798 },
16799 }
16800}
16801
16716const UnparkTid = switch (native_os) {16802const UnparkTid = switch (native_os) {
16717 // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?16803 // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?
16718 .windows => usize,16804 .windows => usize,