cloudflare/pingora · critical
failed to register SIGUSR1 listener
Error message
failed to register SIGUSR1 listener
What it means
In the daemonization parent, pingora registers a SIGUSR1 listener via tokio::signal::unix::signal(SignalKind::user_defined1()) to learn when the daemonized grandchild is ready. signal() returns Err when the signal cannot be registered — sandbox/syscall restrictions, incompatible signal state, or exhausted signal-listener capacity — and this expect turns that into an immediate panic, aborting the parent during daemonization.
Source
Thrown at pingora-core/src/server/daemon.rs:280
///
/// Uses a local tokio runtime with [`tokio::signal::unix`] to listen for `SIGUSR1` instead of
/// raw signal handlers and polling loops. The daemon's PID is checked periodically via the pid
/// file — if the process exits before signaling, the parent aborts.
///
/// Exits the process directly:
/// - exit code 0 if `SIGUSR1` is received (daemon is ready).
/// - exit code 1 if `timeout` elapses (daemon took too long).
/// - exit code 1 if the pid file exists and the process is no longer running.
fn wait_for_ready_or_exit(pid_file: &str, timeout: Duration) {
let rt = build_parent_runtime();
let pid_file = pid_file.to_owned();
rt.block_on(async move {
use tokio::signal::unix::{signal, SignalKind};
use tokio::time::{interval, timeout as tokio_timeout};
let mut sigusr1 =
signal(SignalKind::user_defined1()).expect("failed to register SIGUSR1 listener");
let mut liveness_check = interval(LIVENESS_CHECK_INTERVAL);
let mut daemon_pid: Option<libc::pid_t> = None;
let result = tokio_timeout(timeout, async {
loop {
tokio::select! {
_ = sigusr1.recv() => {
info!("Daemon signaled readiness, parent exiting");
return;
}
_ = liveness_check.tick() => {
if daemon_pid.is_none() {
daemon_pid = try_read_pid_file(&pid_file);
}
if let Some(pid) = daemon_pid {
if !process_is_running(pid) {
error!(View on GitHub (pinned to 0046038bd4)
Solutions
- Run the service foreground (skip daemon mode) under systemd/supervisord, which handles readiness and signals for you
- Adjust the container/sandbox security policy so sigaction for SIGUSR1 is permitted
- Audit the embedding process for other SIGUSR1 users (libraries, custom handlers) and reconfigure them
- If a plain unprivileged daemonized run reproduces it, report to pingora — this expect could be a graceful error instead
Defensive patterns
Strategy: validation
Validate before calling
// Probe SIGUSR1 registration before choosing daemon mode
fn sigusr1_available() -> bool {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.map(|rt| rt.block_on(async {
tokio::signal::unix::signal(
tokio::signal::unix::SignalKind::user_defined1(),
)
.is_ok()
}))
.unwrap_or(false)
} Prevention
- Run foreground under an init system instead of pingora daemon mode where possible
- Verify the container security profile permits sigaction for SIGUSR1
- Never install your own SIGUSR1 handlers in the same process as pingora daemonization
When it happens
Trigger: Running the daemonize path (wait_for_ready_or_exit) where tokio's SIGUSR1 registration fails: a container/seccomp sandbox blocking sigaction, an embedding host with conflicting SIGUSR1 handlers or masked signals, or a runtime environment without Unix signal support.
Common situations: Daemonizing inside Docker/gVisor/WASI-like sandboxes; pingora embedded in another process that installed its own SIGUSR1 handler or broke signal state; minimal containers with unusual signal semantics.
Related errors
- failed to build tokio runtime for parent signal wait
- failed to build work-stealing Tokio runtime
- failed to build no-steal Tokio runtime worker
- non-pathname unix sockets not supported as peer
- Tried to listen with no addr specified
AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16).
Data as JSON: /api/errors/0c2d39b4342e6b81.
Report an issue: GitHub.