authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-28 18:40:48-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-30 22:03:14-08:00
log3320e6a1ae453d40dd78ff3abf6c8543bec2555d
treed067055e1fd09de5a21f2d0aff8b04475a2ab6e1
parentd770e14e001daaea9eb921c1630af69c518468a2

std.Io.Threaded.batchWait better fix for any_done

It is legal to call batchWait with already completed operations in the ring. In such case, we need to avoid waiting in the syscall. The any_done flag was a poor way of tracking state we already have: whether the completion queue is empty. This problem affects the posix poll implementation as well. Thanks again to jacobly for finding the problem.

1 files changed, 17 insertions(+), 8 deletions(-)

lib/std/Io/Threaded.zig+17-8
...@@ -2560,17 +2560,31 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch....@@ -2560,17 +2560,31 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.
2560 const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock;2560 const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock;
2561 const max_poll_ms = std.math.maxInt(i32);2561 const max_poll_ms = std.math.maxInt(i32);
2562 while (true) {2562 while (true) {
2563 const timeout_ms: i32 = if (deadline) |d| t: {2563 const timeout_ms: i32 = t: {
2564 if (b.user.complete_head != complete_tail) {
2565 // It is legal to call batchWait with already completed
2566 // operations in the ring. In such case, we need to avoid
2567 // blocking in the poll syscall, but we can still take this
2568 // opportunity to find additional ready operations.
2569 break :t 0;
2570 }
2571 const d = deadline orelse break :t -1;
2564 const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock;2572 const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock;
2565 if (duration.raw.nanoseconds <= 0) return error.Timeout;2573 if (duration.raw.nanoseconds <= 0) return error.Timeout;
2566 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));2574 break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds()));
2567 } else -1;2575 };
2568 const syscall = try Syscall.start();2576 const syscall = try Syscall.start();
2569 const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms);2577 const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms);
2570 syscall.finish();2578 syscall.finish();
2571 switch (posix.errno(rc)) {2579 switch (posix.errno(rc)) {
2572 .SUCCESS => {2580 .SUCCESS => {
2573 if (rc == 0) {2581 if (rc == 0) {
2582 if (b.user.complete_head != complete_tail) {
2583 // Since there are already completions available in the
2584 // queue, this is neither a timeout nor a case for
2585 // retrying.
2586 return;
2587 }
2574 // Although spurious timeouts are OK, when no deadline is2588 // Although spurious timeouts are OK, when no deadline is
2575 // passed we must not return `error.Timeout`.2589 // passed we must not return `error.Timeout`.
2576 if (deadline == null) continue;2590 if (deadline == null) continue;
...@@ -2677,8 +2691,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa...@@ -2677,8 +2691,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
2677 b.user.complete_tail = complete_tail;2691 b.user.complete_tail = complete_tail;
2678 }2692 }
26792693
2680 var any_done = false;
2681
2682 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {2694 while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) {
2683 const op = ring[submit_head.index(len)];2695 const op = ring[submit_head.index(len)];
2684 const operation = &operations[op];2696 const operation = &operations[op];
...@@ -2688,7 +2700,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa...@@ -2688,7 +2700,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
2688 .noop => |*o| {2700 .noop => |*o| {
2689 _ = o.status.unstarted;2701 _ = o.status.unstarted;
2690 o.status = .{ .result = {} };2702 o.status = .{ .result = {} };
2691 any_done = true;
2692 submitComplete(ring, &complete_tail, op);2703 submitComplete(ring, &complete_tail, op);
2693 },2704 },
2694 .file_read_streaming => |*o| {2705 .file_read_streaming => |*o| {
...@@ -2696,7 +2707,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa...@@ -2696,7 +2707,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
2696 switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) {2707 switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) {
2697 .status => {2708 .status => {
2698 o.status = .{ .result = ntReadFileResult(&metadata.iosb) };2709 o.status = .{ .result = ntReadFileResult(&metadata.iosb) };
2699 any_done = true;
2700 submitComplete(ring, &complete_tail, op);2710 submitComplete(ring, &complete_tail, op);
2701 },2711 },
2702 .pending => {2712 .pending => {
...@@ -2725,11 +2735,10 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa...@@ -2725,11 +2735,10 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa
2725 o.status = .{ .result = ntReadFileResult(&metadata.iosb) };2735 o.status = .{ .result = ntReadFileResult(&metadata.iosb) };
2726 },2736 },
2727 }2737 }
2728 any_done = true;
2729 metadata.pending = false;2738 metadata.pending = false;
2730 submitComplete(ring, &complete_tail, op);2739 submitComplete(ring, &complete_tail, op);
2731 }2740 }
2732 if (any_done) return;2741 if (b.user.complete_head != complete_tail) return;
2733 if (!any_pending) return;2742 if (!any_pending) return;
2734 const alertable_syscall = try AlertableSyscall.start();2743 const alertable_syscall = try AlertableSyscall.start();
2735 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);2744 const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval);