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();
33const builtin = @import("builtin");
44const native_os = builtin.os.tag;
55const is_windows = native_os == .windows;
6const is_musl = native_os == .linux and builtin.link_libc and builtin.abi.isMusl();
76const windows = std.os.windows;
87const ws2_32 = std.os.windows.ws2_32;
98const is_debug = builtin.mode == .Debug;
......@@ -63,12 +62,7 @@ pid: Pid = .unknown,
6362/// Unfortunately, trying again until the cancellation request is acknowledged
6463/// has been observed to be relatively slow, and usually strong cancellation
6564/// guarantees are not needed, so this defaults to off.
66///
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,
65robust_cancel: RobustCancel = .disabled,
7266
7367wsa: if (is_windows) Wsa else struct {} = .{},
7468
......@@ -76,9 +70,7 @@ have_signal_handler: bool,
7670old_sig_io: if (have_sig_io) posix.Sigaction else void,
7771old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
7872
79pub const RobustCancel = if (is_musl) enum {
80 enabled,
81} else if (std.Thread.use_pthreads or native_os == .linux) enum {
73pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum {
8274 enabled,
8375 disabled,
8476} else enum {
......@@ -113,15 +105,10 @@ const Thread = struct {
113105 .acknowledged,
114106 .acq_rel,
115107 .acquire,
116 ) orelse {
117 if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS);
118 return error.Canceled;
119 }) {
108 ) orelse return error.Canceled) {
120109 .requested => unreachable,
121110 .acknowledged => unreachable,
122 .none, _ => {
123 if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS);
124 },
111 .none, _ => {},
125112 }
126113 }
127114
......@@ -139,7 +126,6 @@ const Thread = struct {
139126 .none => unreachable,
140127 .requested => {
141128 @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release);
142 if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS);
143129 return error.Canceled;
144130 },
145131 .acknowledged => return,
......@@ -149,7 +135,7 @@ const Thread = struct {
149135
150136 fn endSyscall(thread: *Thread) void {
151137 const closure = thread.current_closure orelse return;
152 const prev = @cmpxchgStrong(
138 _ = @cmpxchgStrong(
153139 CancelStatus,
154140 &closure.cancel_status,
155141 .fromSignaleeId(thread.signal_id),
......@@ -157,11 +143,6 @@ const Thread = struct {
157143 .acq_rel,
158144 .acquire,
159145 ) 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 }
165146 }
166147
167148 fn currentSignalId() SignaleeId {
......@@ -233,13 +214,6 @@ const Closure = struct {
233214 .none, .acknowledged, .requested => return,
234215 .signal_id => |signal_id| signal_id,
235216 };
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
243217 // The task will enter a blocking syscall before checking for cancellation again.
244218 // We can send a signal to interrupt the syscall, but if it arrives before
245219 // the syscall instruction, it will be missed. Therefore, this code tries
......@@ -328,7 +302,7 @@ pub fn init(
328302 .mask = posix.sigemptyset(),
329303 .flags = 0,
330304 };
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);
332306 if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe);
333307 t.have_signal_handler = true;
334308 }
......@@ -366,7 +340,7 @@ pub fn deinit(t: *Threaded) void {
366340 if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected();
367341 }
368342 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);
370344 if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
371345 }
372346 t.* = undefined;
......@@ -397,15 +371,6 @@ fn worker(t: *Threaded) void {
397371 while (true) {
398372 while (t.run_queue.popFirst()) |closure_node| {
399373 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
409374 const closure: *Closure = @fieldParentPtr("node", closure_node);
410375 closure.start(closure, t);
411376 t.mutex.lock();
......@@ -3460,8 +3425,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp {
34603425const sleep = switch (native_os) {
34613426 .windows => sleepWindows,
34623427 .wasi => sleepWasi,
3463 // Since we use musl's pthread_cancel, it's important that all the syscalls go through libc.
3464 .linux => if (is_musl) sleepPosix else sleepLinux,
3428 .linux => sleepLinux,
34653429 else => sleepPosix,
34663430};
34673431