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 22:03:14-08:00
log523aa213c9f7466bdff5c7030de7d221f1547621
tree0d042c69bd2122822128c634e112917b68e33b99
parent37316a3cf61a0b193003a19b44116da346c6cd3e

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(
17211721 // a crash of some kind. Either way, the child will terminate by itself -- wait for it.
17221722 const stderr_reader = multi_reader.reader(1);
17231723 const stderr_owned = try arena.dupe(u8, stderr_reader.buffered());
1724 stderr_reader.tossBuffered();
17251724
17261725 // Clean up everything and wait for the child to exit.
17271726 child.stdin.?.close(io);
lib/std/Io.zig+8
......@@ -350,6 +350,8 @@ pub const Batch = struct {
350350 }
351351 };
352352
353 /// After calling this, it is safe to unconditionally defer a call to
354 /// `cancel`.
353355 pub fn init(operations: []Operation, ring: []u32) Batch {
354356 const len: u31 = @intCast(operations.len);
355357 assert(ring.len == len);
......@@ -408,12 +410,18 @@ pub const Batch = struct {
408410 /// Starts work on any submitted operations and returns when at least one has completeed.
409411 ///
410412 /// 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.
411416 pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void {
412417 return io.vtable.batchWait(io.userdata, b, timeout);
413418 }
414419
415420 /// Returns after all `operations` have completed. Operations which have not completed
416421 /// 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`.
417425 pub fn cancel(b: *Batch, io: Io) void {
418426 return io.vtable.batchCancel(io.userdata, b);
419427 }
lib/std/Io/Threaded.zig+209-123
......@@ -1255,6 +1255,32 @@ const AlertableSyscall = struct {
12551255 assert(is_windows);
12561256 }
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
12581284 fn checkCancel(s: AlertableSyscall) Io.Cancelable!void {
12591285 comptime assert(is_windows);
12601286 const thread = s.thread orelse return;
......@@ -2501,10 +2527,10 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.
25012527 const op = ring[submit_head.index(len)];
25022528 const operation = &operations[op];
25032529 switch (operation.*) {
2504 .noop => {
2505 try operate(t, operation);
2506 ring[complete_tail.index(len)] = op;
2507 complete_tail = complete_tail.next(len);
2530 .noop => |*o| {
2531 _ = o.status.unstarted;
2532 o.status = .{ .result = {} };
2533 submitComplete(ring, &complete_tail, op);
25082534 },
25092535 .file_read_streaming => |*o| {
25102536 _ = o.status.unstarted;
......@@ -2524,8 +2550,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.
25242550 1 => if (timeout == .none) {
25252551 const op = map_buffer[0];
25262552 try operate(t, &operations[op]);
2527 ring[complete_tail.index(len)] = op;
2528 complete_tail = complete_tail.next(len);
2553 submitComplete(ring, &complete_tail, op);
25292554 poll_i = 0;
25302555 return;
25312556 },
......@@ -2560,8 +2585,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.
25602585 ring[submit_head.index(len)] = op;
25612586 } else {
25622587 try operate(t, &operations[op]);
2563 ring[complete_tail.index(len)] = op;
2564 complete_tail = complete_tail.next(len);
2588 submitComplete(ring, &complete_tail, op);
25652589 }
25662590 }
25672591 return;
......@@ -2584,19 +2608,49 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void {
25842608 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {
25852609 const op = ring[submit_head.index(len)];
25862610 switch (operations[op]) {
2587 .noop => {
2588 operate(t, &operations[op]) catch unreachable;
2589 ring[complete_tail.index(len)] = op;
2590 complete_tail = complete_tail.next(len);
2611 .noop => |*o| {
2612 _ = o.status.unstarted;
2613 o.status = .{ .result = {} };
2614 submitComplete(ring, &complete_tail, op);
25912615 },
25922616 .file_read_streaming => |*o| _ = o.status.unstarted,
25932617 }
25942618 }
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 }
25952644 b.impl.submit_head = submit_tail;
25962645 b.impl.complete_tail = complete_tail;
25972646 b.user.complete_tail = complete_tail;
25982647}
25992648
2649const WinOpMetadata = struct {
2650 iosb: windows.IO_STATUS_BLOCK,
2651 pending: bool,
2652};
2653
26002654fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void {
26012655 const operations = b.operations;
26022656 const len: u31 = @intCast(operations.len);
......@@ -2606,16 +2660,16 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
26062660 b.impl.submit_tail = submit_tail;
26072661 var complete_tail = b.impl.complete_tail;
26082662
2609 var overlapped_buffer: [poll_buffer_len]windows.OVERLAPPED = undefined;
2610 var handles_buffer: [poll_buffer_len]windows.HANDLE = undefined;
2611 var map_buffer: [poll_buffer_len]u32 = undefined; // handles_buffer index to operations index
2612 var buffer_i: usize = 0;
2663 const metadatas_ptr: [*]WinOpMetadata = if (b.impl.reserved) |reserved| @ptrCast(@alignCast(reserved)) else a: {
2664 const gpa = t.allocator;
2665 const metadatas = gpa.alloc(WinOpMetadata, operations.len) catch return error.ConcurrencyUnavailable;
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
26142672 defer {
2615 for (map_buffer[0..buffer_i]) |op| {
2616 submit_head = submit_head.prev(len);
2617 ring[submit_head.index(len)] = op;
2618 }
26192673 b.impl.submit_head = submit_head;
26202674 b.impl.complete_tail = complete_tail;
26212675 b.user.complete_tail = complete_tail;
......@@ -2624,74 +2678,76 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
26242678 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {
26252679 const op = ring[submit_head.index(len)];
26262680 const operation = &operations[op];
2681 const metadata = &metadatas[op];
2682 metadata.* = .{ .iosb = undefined, .pending = false };
26272683 switch (operation.*) {
2628 .noop => {
2629 try operate(t, operation);
2630 ring[complete_tail.index(len)] = op;
2631 complete_tail = complete_tail.next(len);
2684 .noop => |*o| {
2685 _ = o.status.unstarted;
2686 o.status = .{ .result = {} };
2687 submitComplete(ring, &complete_tail, op);
26322688 },
26332689 .file_read_streaming => |*o| {
26342690 _ = o.status.unstarted;
2635 if (handles_buffer.len - buffer_i == 0) return error.ConcurrencyUnavailable;
2636 const overlapped = &overlapped_buffer[buffer_i];
2637 overlapped.* = .{
2638 .Internal = 0,
2639 .InternalHigh = 0,
2640 .DUMMYUNIONNAME = .{ .Pointer = null },
2641 .hEvent = null,
2642 };
2643 var n: windows.DWORD = undefined;
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");
2691 switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) {
2692 .status => {
2693 o.status = .{ .result = ntReadFileResult(&metadata.iosb) };
2694 submitComplete(ring, &complete_tail, op);
2695 },
2696 .pending => {
2697 o.status = .{ .pending = b };
2698 metadata.pending = true;
2699 },
26482700 }
2649 handles_buffer[buffer_i] = o.file.handle;
2650 map_buffer[buffer_i] = op;
2651 buffer_i += 1;
26522701 },
26532702 }
26542703 }
26552704
2656 switch (buffer_i) {
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];
2705 var delay_interval: windows.LARGE_INTEGER = timeoutToWindowsInterval(timeout);
26712706
2672 const syscall: Syscall = try .start();
2673 const index_result = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true);
2674 syscall.finish();
2675 const index = index_result catch |err| switch (err) {
2676 error.Unexpected => @panic("TODO"),
2677 error.WaitAbandoned => @panic("TODO"),
2678 error.WaitTimeOut => @panic("TODO"),
2679 };
2680 var n: windows.DWORD = undefined;
2681 if (0 == windows.kernel32.GetOverlappedResult(handles[index], &overlapped_buffer[index], &n, 0)) {
2682 switch (windows.GetLastError()) {
2683 .BROKEN_PIPE => @panic("TODO"),
2684 .OPERATION_ABORTED => @panic("TODO"),
2685 else => @panic("TODO"),
2707 while (true) {
2708 const alertable_syscall = try AlertableSyscall.start();
2709 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);
2710 alertable_syscall.finish();
2711 switch (delay_rc) {
2712 .SUCCESS => {
2713 // The thread woke due to the timeout. Although spurious
2714 // timeouts are OK, when no deadline is passed we must not
2715 // return `error.Timeout`.
2716 if (timeout != .none) return error.Timeout;
2717 },
2718 else => {},
26862719 }
2687 } else switch (operations[map[index]]) {
2688 .noop => unreachable,
2689 .file_read_streaming => |*o| {
2690 o.status = .{ .result = n };
2691 },
2720 var any_done = false;
2721 var any_pending = false;
2722 for (metadatas, 0..) |*metadata, op_usize| {
2723 if (!metadata.pending) continue;
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;
26922741 }
26932742}
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
26952751const dirCreateDir = switch (native_os) {
26962752 .windows => dirCreateDirWindows,
26972753 .wasi => dirCreateDirWasi,
......@@ -5529,7 +5585,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8,
55295585
55305586fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize {
55315587 var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined;
5532 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
5588 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
55335589 try Thread.checkCancel();
55345590 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
86178673}
86188674
86198675fn 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 } {
86208720 var index: usize = 0;
86218721 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 }
86238727 const buffer = data[index];
86248728
8625 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
86268729 const syscall: Syscall = try .start();
86278730 while (true) {
8628 io_status_block.u.Status = .PENDING;
8731 iosb.u.Status = .PENDING;
86298732 switch (windows.ntdll.NtReadFile(
8630 file.handle,
8733 handle,
86318734 null, // event
86328735 noopApc, // apc callback
86338736 null, // apc context
8634 &io_status_block,
8737 iosb,
86358738 buffer.ptr,
86368739 @min(std.math.maxInt(u32), buffer.len),
86378740 null, // byte offset
86388741 null, // key
86398742 )) {
8640 .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {
8743 .PENDING => {
86418744 syscall.finish();
8642 return io_status_block.Information;
8745 return .pending;
86438746 },
8644 .PENDING => break,
86458747 .CANCELLED => {
86468748 try syscall.checkCancel();
86478749 continue;
86488750 },
8649 .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir),
8650 .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation),
8651 .ACCESS_DENIED => return syscall.fail(error.AccessDenied),
8652 .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file
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;
8751 else => |status| {
8752 syscall.finish();
8753 iosb.u.Status = status;
8754 return .status;
86678755 },
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();
86798756 }
86808757 }
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 }
86898758}
86908759
86918760fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize {
......@@ -9318,7 +9387,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut
93189387 };
93199388 defer w.CloseHandle(h_file);
93209389
9321 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
9390 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
93229391 try Thread.checkCancel();
93239392 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
1298913058
1299013059 if (is_windows) {
1299113060 var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined;
12992 // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks
13061 // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks
1299313062 try Thread.checkCancel();
1299413063 const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer);
1299513064 const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong;
......@@ -16657,7 +16726,7 @@ const parking_sleep = struct {
1665716726/// Spurious wakeups are possible.
1665816727///
1665916728/// `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 {
1666116730 comptime assert(use_parking_futex or use_parking_sleep);
1666216731 switch (native_os) {
1666316732 .windows => {
......@@ -16713,6 +16782,23 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err
1671316782 }
1671416783}
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
1671616802const UnparkTid = switch (native_os) {
1671716803 // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles?
1671816804 .windows => usize,