Understanding SIGPIPE Signal and Preventing Service Crashes in Go‑Rust Integration
This article explains how the Linux SIGPIPE signal is generated during broken TCP connections, why the default kernel handling can terminate Go services that call Rust code via cgo, and provides practical code examples to ignore SIGPIPE and avoid silent process exits.
During a recent gray‑release, a Go service that had part of its logic rewritten in Rust crashed unexpectedly due to a SIGPIPE signal triggered by a broken network connection. The article walks through the background of the fault, the kernel mechanisms that generate SIGPIPE, the default signal handling path, and how to properly handle the signal at the application level.
Fault Background
The service was partially refactored to Rust and communicated with Go via cgo. When a dependent process was hot‑upgraded, the TCP connection broke, causing the service to receive SIGPIPE and exit without a core dump. After extensive debugging, the root cause was identified as the unhandled SIGPIPE signal.
1. How SIGPIPE Happens
When a TCP socket is closed unexpectedly (e.g., due to network errors or peer restart), subsequent write or send calls on the socket cause the kernel to generate a SIGPIPE signal for the current process.
Key kernel code (simplified):
//file:net/core/stream.c
ssize_t do_tcp_sendpages(struct sock *sk, struct page *page, int offset,
size_t size, int flags) {
...
err = -EPIPE;
if (sk->sk_err || (sk->sk_shutdown & SEND_SHUTDOWN))
goto out_err;
out_err:
return sk_stream_error(sk, flags, err);
}The sk_stream_error function sends SIGPIPE when the error is -EPIPE and the MSG_NOSIGNAL flag is not set.
int sk_stream_error(struct sock *sk, int flags, int err) {
...
if (err == -EPIPE && !(flags & MSG_NOSIGNAL))
send_sig(SIGPIPE, current, 0);
return err;
}2. Kernel SIGPIPE Handling Flow
When returning from kernel to user space, the kernel checks for pending signals via do_signal , which calls get_signal and then handle_signal if a user‑defined handler exists.
//file:arch/x86/kernel/signal.c
static void do_signal(struct pt_regs *regs) {
struct ksignal ksig;
...
if (get_signal(&ksig)) {
handle_signal(&ksig, regs);
return;
}
...
}get_signal performs three main steps:
Dequeues a pending signal.
Checks whether the process has a custom handler (ignores if SIG_IGN ).
If no handler, falls back to the kernel's default behavior.
The default behavior for SIGPIPE is to call do_group_exit , which terminates all threads of the process without generating a core dump.
//file:kernel/signal.c
if (sig_kernel_coredump(signr)) {
do_coredump(&ksig->info);
} else {
do_group_exit(ksig->info.si_signo); // kills process, no core dump
}3. Application‑Level Mitigation
To prevent the crash, the service must set the SIGPIPE disposition to SIG_IGN (ignore) before any network writes. In Rust, this can be done with the following code (using the nix crate or similar):
// Set SIGPIPE handler to ignore
let ignore_action = SigAction::new(
SigHandler::SigIgn,
signal::SaFlags::empty(),
SigSet::empty(),
);
unsafe {
signal::sigaction(Signal::SIGPIPE, &ignore_action)
.expect("Failed to set SIGPIPE handler to ignore");
}With this handler in place, the kernel will skip the default termination path, and the write call will simply return EPIPE , allowing the application to handle the error gracefully.
Summary
The crash occurs because:
The dependent service’s hot upgrade breaks the TCP connection.
The Go‑Rust service continues to write to the broken socket.
The kernel detects the broken pipe and sends SIGPIPE.
The service has no SIGPIPE handler, so the kernel’s default action ( do_group_exit ) terminates the process without a core dump.
Go’s runtime automatically ignores SIGPIPE on non‑standard file descriptors, but this does not apply to cgo‑invoked Rust code running on non‑Go threads. By explicitly ignoring SIGPIPE in the Rust side, the issue is fully resolved.
IT Services Circle
Delivering cutting-edge internet insights and practical learning resources. We're a passionate and principled IT media platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.