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
This commit is contained in:
Andrew Kelley 2025-10-28 18:45:53 -07:00
parent b863f2548b
commit 05b28409e7
4 changed files with 38 additions and 37 deletions

View file

@ -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");
}

View file

@ -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;
},
};

View file

@ -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 {}

View file

@ -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.
///