authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-01 19:07:39-08:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-12-01 19:17:52-08:00
logcf82064ebc3063c6a8e8f51388783ed1c01d6281
treecd9a3c3d56ae5236490f92c5feffca8830ec07a6
parentbf0ffc45b9ca76250749097522714b7e3831a0c1

std.Io.Threaded: don't use pthread_cancel with musl

It doesn't support setting the "canceled" status to false, so once a thread has been canceled, all operations on the thread start permanently failing.

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

lib/std/Io/Threaded.zig+8-44
...@@ -3,7 +3,6 @@ const Threaded = @This();...@@ -3,7 +3,6 @@ const Threaded = @This();
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const native_os = builtin.os.tag;4const native_os = builtin.os.tag;
5const is_windows = native_os == .windows;5const is_windows = native_os == .windows;
6const is_musl = native_os == .linux and builtin.link_libc and builtin.abi.isMusl();
7const windows = std.os.windows;6const windows = std.os.windows;
8const ws2_32 = std.os.windows.ws2_32;7const ws2_32 = std.os.windows.ws2_32;
9const is_debug = builtin.mode == .Debug;8const is_debug = builtin.mode == .Debug;
...@@ -63,12 +62,7 @@ pid: Pid = .unknown,...@@ -63,12 +62,7 @@ pid: Pid = .unknown,
63/// Unfortunately, trying again until the cancellation request is acknowledged62/// Unfortunately, trying again until the cancellation request is acknowledged
64/// has been observed to be relatively slow, and usually strong cancellation63/// has been observed to be relatively slow, and usually strong cancellation
65/// guarantees are not needed, so this defaults to off.64/// guarantees are not needed, so this defaults to off.
66///65robust_cancel: RobustCancel = .disabled,
67/// Musl libc does not have this problem because of a clever, undocumented
68/// extension related to pthread_cancel, which this code integrates with.
69/// When compiling with no libc, `Threaded` does not yet implement the
70/// equivalent trick (tracked by https://codeberg.org/ziglang/zig/issues/30049).
71robust_cancel: RobustCancel = if (is_musl) .enabled else .disabled,
7266
73wsa: if (is_windows) Wsa else struct {} = .{},67wsa: if (is_windows) Wsa else struct {} = .{},
7468
...@@ -76,9 +70,7 @@ have_signal_handler: bool,...@@ -76,9 +70,7 @@ have_signal_handler: bool,
76old_sig_io: if (have_sig_io) posix.Sigaction else void,70old_sig_io: if (have_sig_io) posix.Sigaction else void,
77old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,71old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
7872
79pub const RobustCancel = if (is_musl) enum {73pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
80 enabled,
81} else if (std.Thread.use_pthreads or native_os == .linux) enum {
82 enabled,74 enabled,
83 disabled,75 disabled,
84} else enum {76} else enum {
...@@ -113,15 +105,10 @@ const Thread = struct {...@@ -113,15 +105,10 @@ const Thread = struct {
113 .acknowledged,105 .acknowledged,
114 .acq_rel,106 .acq_rel,
115 .acquire,107 .acquire,
116 ) orelse {108 ) orelse return error.Canceled) {
117 if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS);
118 return error.Canceled;
119 }) {
120 .requested => unreachable,109 .requested => unreachable,
121 .acknowledged => unreachable,110 .acknowledged => unreachable,
122 .none, _ => {111 .none, _ => {},
123 if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS);
124 },
125 }112 }
126 }113 }
127114
...@@ -139,7 +126,6 @@ const Thread = struct {...@@ -139,7 +126,6 @@ const Thread = struct {
139 .none => unreachable,126 .none => unreachable,
140 .requested => {127 .requested => {
141 @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release);128 @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release);
142 if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS);
143 return error.Canceled;129 return error.Canceled;
144 },130 },
145 .acknowledged => return,131 .acknowledged => return,
...@@ -149,7 +135,7 @@ const Thread = struct {...@@ -149,7 +135,7 @@ const Thread = struct {
149135
150 fn endSyscall(thread: *Thread) void {136 fn endSyscall(thread: *Thread) void {
151 const closure = thread.current_closure orelse return;137 const closure = thread.current_closure orelse return;
152 const prev = @cmpxchgStrong(138 _ = @cmpxchgStrong(
153 CancelStatus,139 CancelStatus,
154 &closure.cancel_status,140 &closure.cancel_status,
155 .fromSignaleeId(thread.signal_id),141 .fromSignaleeId(thread.signal_id),
...@@ -157,11 +143,6 @@ const Thread = struct {...@@ -157,11 +143,6 @@ const Thread = struct {
157 .acq_rel,143 .acq_rel,
158 .acquire,144 .acquire,
159 ) orelse return;145 ) orelse return;
160 if (is_musl and prev == .requested) {
161 // They called pthread_cancel, but we want to disarm it since
162 // the next call to beginSyscall will notice requested status.
163 assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS);
164 }
165 }146 }
166147
167 fn currentSignalId() SignaleeId {148 fn currentSignalId() SignaleeId {
...@@ -233,13 +214,6 @@ const Closure = struct {...@@ -233,13 +214,6 @@ const Closure = struct {
233 .none, .acknowledged, .requested => return,214 .none, .acknowledged, .requested => return,
234 .signal_id => |signal_id| signal_id,215 .signal_id => |signal_id| signal_id,
235 };216 };
236 // Musl has an undocumented extension that makes pthread_cancel have the useful, desired
237 // behavior of causing the next syscall to return ECANCELED.
238 if (is_musl) {
239 _ = std.c.pthread_cancel(signal_id);
240 return;
241 }
242
243 // The task will enter a blocking syscall before checking for cancellation again.217 // The task will enter a blocking syscall before checking for cancellation again.
244 // We can send a signal to interrupt the syscall, but if it arrives before218 // We can send a signal to interrupt the syscall, but if it arrives before
245 // the syscall instruction, it will be missed. Therefore, this code tries219 // the syscall instruction, it will be missed. Therefore, this code tries
...@@ -328,7 +302,7 @@ pub fn init(...@@ -328,7 +302,7 @@ pub fn init(
328 .mask = posix.sigemptyset(),302 .mask = posix.sigemptyset(),
329 .flags = 0,303 .flags = 0,
330 };304 };
331 if (!is_musl and have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io);305 if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io);
332 if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe);306 if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe);
333 t.have_signal_handler = true;307 t.have_signal_handler = true;
334 }308 }
...@@ -366,7 +340,7 @@ pub fn deinit(t: *Threaded) void {...@@ -366,7 +340,7 @@ pub fn deinit(t: *Threaded) void {
366 if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected();340 if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected();
367 }341 }
368 if (posix.Sigaction != void and t.have_signal_handler) {342 if (posix.Sigaction != void and t.have_signal_handler) {
369 if (!is_musl and have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null);343 if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null);
370 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);344 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
371 }345 }
372 t.* = undefined;346 t.* = undefined;
...@@ -397,15 +371,6 @@ fn worker(t: *Threaded) void {...@@ -397,15 +371,6 @@ fn worker(t: *Threaded) void {
397 while (true) {371 while (true) {
398 while (t.run_queue.popFirst()) |closure_node| {372 while (t.run_queue.popFirst()) |closure_node| {
399 t.mutex.unlock();373 t.mutex.unlock();
400
401 // Musl has an undocumented extension that makes pthread_cancel have the useful, desired
402 // behavior of causing the next syscall to return ECANCELED.
403 //
404 // The call to `requestCancel` and this can race, leading to
405 // ECANCELED being returned for a syscall in an unrelated task,
406 // which is why EINTR and ECANCELED are both handled with a check.
407 if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS);
408
409 const closure: *Closure = @fieldParentPtr("node", closure_node);374 const closure: *Closure = @fieldParentPtr("node", closure_node);
410 closure.start(closure, t);375 closure.start(closure, t);
411 t.mutex.lock();376 t.mutex.lock();
...@@ -3460,8 +3425,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {...@@ -3460,8 +3425,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
3460const sleep = switch (native_os) {3425const sleep = switch (native_os) {
3461 .windows => sleepWindows,3426 .windows => sleepWindows,
3462 .wasi => sleepWasi,3427 .wasi => sleepWasi,
3463 // Since we use musl's pthread_cancel, it's important that all the syscalls go through libc.3428 .linux => sleepLinux,
3464 .linux => if (is_musl) sleepPosix else sleepLinux,
3465 else => sleepPosix,3429 else => sleepPosix,
3466};3430};
34673431