authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-12-30 20:43:53+00:00
committergravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2026-01-03 15:45:10+00:00
log2c395e326f2db4e2389af729b88bd93e76e4d27c
treed005a7ec304236a88d310f3013e032d817211270
parentf27134d671450a2aac3d48c82ccfbc5a947849f3
signaturelock-open Commit is signed but in an unrecognized format.

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!

1 files changed, 2 insertions(+), 18 deletions(-)

lib/std/Io/Threaded.zig+2-18
...@@ -39,7 +39,6 @@ cpu_count_error: ?std.Thread.CpuCountError,...@@ -39,7 +39,6 @@ cpu_count_error: ?std.Thread.CpuCountError,
39busy_count: usize = 0,39busy_count: usize = 0,
40worker_threads: std.atomic.Value(?*Thread),40worker_threads: std.atomic.Value(?*Thread),
41pid: Pid = .unknown,41pid: Pid = .unknown,
42robust_cancel: RobustCancel,
4342
44wsa: if (is_windows) Wsa else struct {} = .{},43wsa: if (is_windows) Wsa else struct {} = .{},
4544
...@@ -105,8 +104,6 @@ pub const Environ = struct {...@@ -105,8 +104,6 @@ pub const Environ = struct {
105 };104 };
106};105};
107106
108pub const RobustCancel = enum { enabled, disabled };
109
110pub const Pid = if (native_os == .linux) enum(posix.pid_t) {107pub const Pid = if (native_os == .linux) enum(posix.pid_t) {
111 unknown = 0,108 unknown = 0,
112 _,109 _,
...@@ -315,7 +312,7 @@ const Group = struct {...@@ -315,7 +312,7 @@ const Group = struct {
315 var need_signal: bool = !skip_signals and g.cancelThreads(t);312 var need_signal: bool = !skip_signals and g.cancelThreads(t);
316 var timeout_ns: u64 = 1 << 10;313 var timeout_ns: u64 = 1 << 10;
317 while (true) {314 while (true) {
318 need_signal = need_signal and g.signalAllCanceledSyscalls(t) and t.robust_cancel == .enabled;315 need_signal = need_signal and g.signalAllCanceledSyscalls(t);
319 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);316 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
320 switch (num_completed.load(.acquire)) { // acquire task results317 switch (num_completed.load(.acquire)) { // acquire task results
321 0 => {},318 0 => {},
...@@ -469,7 +466,7 @@ const Future = struct {...@@ -469,7 +466,7 @@ const Future = struct {
469 var need_signal: bool = thread != null and thread.?.cancelAwaitable(.fromFuture(future));466 var need_signal: bool = thread != null and thread.?.cancelAwaitable(.fromFuture(future));
470 var timeout_ns: u64 = 1 << 10;467 var timeout_ns: u64 = 1 << 10;
471 while (true) {468 while (true) {
472 need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)) and t.robust_cancel == .enabled;469 need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future));
473 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);470 Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null);
474 switch (num_completed.load(.acquire)) { // acquire task results471 switch (num_completed.load(.acquire)) { // acquire task results
475 0 => {},472 0 => {},
...@@ -1112,17 +1109,6 @@ pub const InitOptions = struct {...@@ -1112,17 +1109,6 @@ pub const InitOptions = struct {
1112 /// concurrent tasks. After this number, calls to `Io.concurrent` return1109 /// concurrent tasks. After this number, calls to `Io.concurrent` return
1113 /// `error.ConcurrencyUnavailable`.1110 /// `error.ConcurrencyUnavailable`.
1114 concurrent_limit: Io.Limit = .unlimited,1111 concurrent_limit: Io.Limit = .unlimited,
1115 /// When a cancel request is made, blocking syscalls can be unblocked by
1116 /// issuing a signal. However, if the signal arrives after the check and before
1117 /// the syscall instruction, it is missed.
1118 ///
1119 /// This option solves the race condition by retrying the signal delivery
1120 /// until it is acknowledged, with an exponential backoff.
1121 ///
1122 /// Unfortunately, trying again until the cancellation request is acknowledged
1123 /// has been observed to be relatively slow, and usually strong cancellation
1124 /// guarantees are not needed, so this defaults to off.
1125 robust_cancel: RobustCancel = .disabled,
1126 /// Affects the following operations:1112 /// Affects the following operations:
1127 /// * `processExecutablePath` on OpenBSD and Haiku.1113 /// * `processExecutablePath` on OpenBSD and Haiku.
1128 argv0: Argv0 = .{},1114 argv0: Argv0 = .{},
...@@ -1160,7 +1146,6 @@ pub fn init(...@@ -1160,7 +1146,6 @@ pub fn init(
1160 .have_signal_handler = false,1146 .have_signal_handler = false,
1161 .argv0 = options.argv0,1147 .argv0 = options.argv0,
1162 .environ = options.environ,1148 .environ = options.environ,
1163 .robust_cancel = options.robust_cancel,
1164 .worker_threads = .init(null),1149 .worker_threads = .init(null),
1165 };1150 };
11661151
...@@ -1195,7 +1180,6 @@ pub const init_single_threaded: Threaded = .{...@@ -1195,7 +1180,6 @@ pub const init_single_threaded: Threaded = .{
1195 .old_sig_io = undefined,1180 .old_sig_io = undefined,
1196 .old_sig_pipe = undefined,1181 .old_sig_pipe = undefined,
1197 .have_signal_handler = false,1182 .have_signal_handler = false,
1198 .robust_cancel = .disabled,
1199 .argv0 = .{},1183 .argv0 = .{},
1200 .environ = .{},1184 .environ = .{},
1201 .worker_threads = .init(null),1185 .worker_threads = .init(null),