authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-11-28 18:07:42-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-01 19:17:52-08:00
logd60760d61e19018aa354fbb2f241444db88ca51b
tree07e87ca4feedf01615c9090ed6c910c226431681
parent29e418cbfb3d5e5800ac2ad267b21cf2b8942c2c

std.Io.Threaded: tune requestCancel

On a heavily loaded Linux 6.17.5, I observed a maximum of 20 attempts not acknowledged before the timeout (including exponential backoff) was sufficient, despite the heavy load. The time wasted here sleeping is mitigated by the fact that, later on, the system will likely wait for the canceled task, causing it to indefinitely yield until the canceled task finishes, and the task must acknowledge the cancel before it proceeds to that point.

1 files changed, 22 insertions(+), 5 deletions(-)

lib/std/Io/Threaded.zig+22-5
...@@ -201,8 +201,22 @@ const Closure = struct {...@@ -201,8 +201,22 @@ const Closure = struct {
201 // We can send a signal to interrupt the syscall, but if it arrives before201 // We can send a signal to interrupt the syscall, but if it arrives before
202 // the syscall instruction, it will be missed. Therefore, this code tries202 // the syscall instruction, it will be missed. Therefore, this code tries
203 // again until the cancellation request is acknowledged.203 // again until the cancellation request is acknowledged.
204 const max_attempts = 3;204
205 for (0..max_attempts) |_| {205 // 1 << 10 ns is about 1 microsecond, approximately syscall overhead.
206 // 1 << 20 ns is about 1 millisecond.
207 // 1 << 30 ns is about 1 second.
208 //
209 // On a heavily loaded Linux 6.17.5, I observed a maximum of 20
210 // attempts not acknowledged before the timeout (including exponential
211 // backoff) was sufficient, despite the heavy load.
212 //
213 // The time wasted here sleeping is mitigated by the fact that, later
214 // on, the system will likely wait for the canceled task, causing it
215 // to indefinitely yield until the canceled task finishes, and the
216 // task must acknowledge the cancel before it proceeds to that point.
217 const max_attempts = 22;
218
219 for (0..max_attempts) |attempt_index| {
206 if (std.Thread.use_pthreads) {220 if (std.Thread.use_pthreads) {
207 const rc = std.c.pthread_kill(signal_id, .IO);221 const rc = std.c.pthread_kill(signal_id, .IO);
208 if (is_debug) assert(rc == 0);222 if (is_debug) assert(rc == 0);
...@@ -219,11 +233,14 @@ const Closure = struct {...@@ -219,11 +233,14 @@ const Closure = struct {
219 return;233 return;
220 }234 }
221235
222 // TODO make this a nanosleep with 1 << attempt duration236 var timespec: posix.timespec = .{
223 std.Thread.yield() catch {};237 .sec = 0,
238 .nsec = @as(isize, 1) << @intCast(attempt_index),
239 };
240 _ = posix.system.nanosleep(&timespec, &timespec);
224241
225 switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) {242 switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) {
226 .requested => continue,243 .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall.
227 .none, .acknowledged => return,244 .none, .acknowledged => return,
228 .signal_id => |new_signal_id| signal_id = new_signal_id,245 .signal_id => |new_signal_id| signal_id = new_signal_id,
229 }246 }