Commit 05b28409e7

Andrew Kelley <andrew@ziglang.org>
2025-10-29 02:45:53
std.Io.Threaded: install and cleanup signal handlers
rather than in start code. delete std.options.keep_sig_io and std.options.keep_sig_pipe
1 parent b863f25
Changed files (4)
lib/std/Io/Threaded.zig
@@ -26,8 +26,13 @@ threads: std.ArrayListUnmanaged(std.Thread),
 stack_size: usize,
 cpu_count: std.Thread.CpuCountError!usize,
 concurrent_count: usize,
+
 wsa: if (is_windows) Wsa else struct {} = .{},
 
+have_signal_handler: bool,
+old_sig_io: if (have_sig_io) posix.Sigaction else void,
+old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void,
+
 threadlocal var current_closure: ?*Closure = null;
 
 const max_iovecs_len = 8;
@@ -104,23 +109,46 @@ pub fn init(
         .stack_size = std.Thread.SpawnConfig.default_stack_size,
         .cpu_count = std.Thread.getCpuCount(),
         .concurrent_count = 0,
+        .old_sig_io = undefined,
+        .old_sig_pipe = undefined,
+        .have_signal_handler = false,
     };
+
     if (t.cpu_count) |n| {
         t.threads.ensureTotalCapacityPrecise(gpa, n - 1) catch {};
     } else |_| {}
+
+    if (posix.Sigaction != void) {
+        // This causes sending `posix.SIG.IO` to thread to interrupt blocking
+        // syscalls, returning `posix.E.INTR`.
+        const act: posix.Sigaction = .{
+            .handler = .{ .handler = doNothingSignalHandler },
+            .mask = posix.sigemptyset(),
+            .flags = 0,
+        };
+        if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io);
+        if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe);
+        t.have_signal_handler = true;
+    }
+
     return t;
 }
 
 /// Statically initialize such that calls to `Io.VTable.concurrent` will fail
 /// with `error.ConcurrencyUnavailable`.
 ///
-/// When initialized this way, `deinit` is safe, but unnecessary to call.
+/// When initialized this way:
+/// * cancel requests have no effect.
+/// * `deinit` is safe, but unnecessary to call.
 pub const init_single_threaded: Threaded = .{
     .allocator = .failing,
     .threads = .empty,
     .stack_size = std.Thread.SpawnConfig.default_stack_size,
     .cpu_count = 1,
     .concurrent_count = 0,
+    .old_sig_io = undefined,
+    .old_sig_pipe = undefined,
+    .have_signal_handler = false,
 };
 
 pub fn deinit(t: *Threaded) void {
@@ -130,6 +158,10 @@ pub fn deinit(t: *Threaded) void {
     if (is_windows and t.wsa.status == .initialized) {
         if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected();
     }
+    if (posix.Sigaction != void and t.have_signal_handler) {
+        if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null);
+        if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null);
+    }
     t.* = undefined;
 }
 
@@ -338,6 +370,8 @@ const have_preadv = switch (native_os) {
     .windows, .haiku, .serenity => false, // ๐Ÿ’ฉ๐Ÿ’ฉ๐Ÿ’ฉ
     else => true,
 };
+const have_sig_io = posix.SIG != void and @hasField(posix.SIG, "IO");
+const have_sig_pipe = posix.SIG != void and @hasField(posix.SIG, "PIPE");
 
 const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat;
 const fstat_sym = if (posix.lfs64_abi) posix.system.fstat64 else posix.system.fstat;
@@ -6115,6 +6149,8 @@ fn initializeWsa(t: *Threaded) error{NetworkDown}!void {
     return error.NetworkDown;
 }
 
+fn doNothingSignalHandler(_: posix.SIG) callconv(.c) void {}
+
 test {
     _ = @import("Threaded/test.zig");
 }
lib/std/posix.zig
@@ -55,6 +55,7 @@ else switch (native_os) {
         pub const mode_t = u0;
         pub const ino_t = void;
         pub const IFNAMESIZE = {};
+        pub const SIG = void;
     },
 };
 
lib/std/start.zig
@@ -651,7 +651,6 @@ inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [][*:0]u8) u8 {
     std.os.argv = argv[0..argc];
     std.os.environ = envp;
 
-    maybeIgnoreSignals();
     std.debug.maybeEnableSegfaultHandler();
 
     return callMain();
@@ -756,23 +755,3 @@ pub fn call_wWinMain() std.os.windows.INT {
     // second parameter hPrevInstance, MSDN: "This parameter is always NULL"
     return root.wWinMain(hInstance, null, lpCmdLine, nCmdShow);
 }
-
-fn maybeIgnoreSignals() void {
-    const posix = std.posix;
-    if (posix.Sigaction == void) return;
-    const act: posix.Sigaction = .{
-        // Set handler to a noop function instead of `IGN` to prevent
-        // leaking signal disposition to a child process.
-        .handler = .{ .handler = noopSigHandler },
-        .mask = posix.sigemptyset(),
-        .flags = 0,
-    };
-
-    if (@hasField(posix.SIG, "IO") and !std.options.keep_sig_io)
-        posix.sigaction(.IO, &act, null);
-
-    if (@hasField(posix.SIG, "PIPE") and !std.options.keep_sig_pipe)
-        posix.sigaction(.PIPE, &act, null);
-}
-
-fn noopSigHandler(_: std.posix.SIG) callconv(.c) void {}
lib/std/std.zig
@@ -144,21 +144,6 @@ pub const Options = struct {
 
     crypto_fork_safety: bool = true,
 
-    keep_sig_io: bool = false,
-
-    /// By default Zig disables SIGPIPE by setting a "no-op" handler for it.  Set this option
-    /// to `true` to prevent that.
-    ///
-    /// Note that we use a "no-op" handler instead of SIG_IGN because it will not be inherited by
-    /// any child process.
-    ///
-    /// SIGPIPE is triggered when a process attempts to write to a broken pipe. By default, SIGPIPE
-    /// will terminate the process instead of exiting.  It doesn't trigger the panic handler so in many
-    /// cases it's unclear why the process was terminated.  By capturing SIGPIPE instead, functions that
-    /// write to broken pipes will return the EPIPE error (error.BrokenPipe) and the program can handle
-    /// it like any other error.
-    keep_sig_pipe: bool = false,
-
     /// By default, std.http.Client will support HTTPS connections.  Set this option to `true` to
     /// disable TLS support.
     ///