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

  1. Run the service foreground (skip daemon mode) under systemd/supervisord, which handles readiness and signals for you
  2. Adjust the container/sandbox security policy so sigaction for SIGUSR1 is permitted
  3. Audit the embedding process for other SIGUSR1 users (libraries, custom handlers) and reconfigure them
  4. 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

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


AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16). Data as JSON: /api/errors/0c2d39b4342e6b81. Report an issue: GitHub.