From 2c395e326f2db4e2389af729b88bd93e76e4d27c Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 30 Dec 2025 20:43:53 +0000 Subject: [PATCH] std.Io.Threaded: always use robust cancelation As of this branch, the performance impact of robust cancelation is now negligible (and in fact entirely unmeasurable in almost all cases), so there is no good reason to not enable it in all cases. The performance issues before were primarily down to a typo in the robust cancelation logic which resulted in every canceled syscall potentially being sent hundreds of signals in quick succession, because the delay between signals started out at 1ns instead of 1us! --- lib/std/Io/Threaded.zig | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 611b9e6592..361126dbec 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -39,7 +39,6 @@ cpu_count_error: ?std.Thread.CpuCountError, busy_count: usize = 0, worker_threads: std.atomic.Value(?*Thread), pid: Pid = .unknown, -robust_cancel: RobustCancel, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -105,8 +104,6 @@ pub const Environ = struct { }; }; -pub const RobustCancel = enum { enabled, disabled }; - pub const Pid = if (native_os == .linux) enum(posix.pid_t) { unknown = 0, _, @@ -315,7 +312,7 @@ const Group = struct { var need_signal: bool = !skip_signals and g.cancelThreads(t); var timeout_ns: u64 = 1 << 10; while (true) { - need_signal = need_signal and g.signalAllCanceledSyscalls(t) and t.robust_cancel == .enabled; + need_signal = need_signal and g.signalAllCanceledSyscalls(t); Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); switch (num_completed.load(.acquire)) { // acquire task results 0 => {}, @@ -469,7 +466,7 @@ const Future = struct { var need_signal: bool = thread != null and thread.?.cancelAwaitable(.fromFuture(future)); var timeout_ns: u64 = 1 << 10; while (true) { - need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)) and t.robust_cancel == .enabled; + need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)); Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); switch (num_completed.load(.acquire)) { // acquire task results 0 => {}, @@ -1112,17 +1109,6 @@ pub const InitOptions = struct { /// concurrent tasks. After this number, calls to `Io.concurrent` return /// `error.ConcurrencyUnavailable`. concurrent_limit: Io.Limit = .unlimited, - /// When a cancel request is made, blocking syscalls can be unblocked by - /// issuing a signal. However, if the signal arrives after the check and before - /// the syscall instruction, it is missed. - /// - /// This option solves the race condition by retrying the signal delivery - /// until it is acknowledged, with an exponential backoff. - /// - /// Unfortunately, trying again until the cancellation request is acknowledged - /// has been observed to be relatively slow, and usually strong cancellation - /// guarantees are not needed, so this defaults to off. - robust_cancel: RobustCancel = .disabled, /// Affects the following operations: /// * `processExecutablePath` on OpenBSD and Haiku. argv0: Argv0 = .{}, @@ -1160,7 +1146,6 @@ pub fn init( .have_signal_handler = false, .argv0 = options.argv0, .environ = options.environ, - .robust_cancel = options.robust_cancel, .worker_threads = .init(null), }; @@ -1195,7 +1180,6 @@ pub const init_single_threaded: Threaded = .{ .old_sig_io = undefined, .old_sig_pipe = undefined, .have_signal_handler = false, - .robust_cancel = .disabled, .argv0 = .{}, .environ = .{}, .worker_threads = .init(null),