cloudflare/pingora · critical
failed to build tokio runtime for parent signal wait
Error message
failed to build tokio runtime for parent signal wait
What it means
During daemonization, pingora's short-lived parent process builds a single-threaded (current_thread) tokio runtime to wait for the daemon's readiness signal. This expect fires when tokio's runtime builder returns Err — in practice an OS-level resource problem: creating the IO/timer drivers (epoll/eventfd) or runtime resources failed under restrictive limits or sandboxes, not application logic.
Source
Thrown at pingora-core/src/server/daemon.rs:258
"Waiting up to {:?} for daemon to signal readiness via SIGUSR1",
timeout
);
wait_for_ready_or_exit(&conf.pid_file, timeout);
}
process::exit(0);
}
/// Build a single-threaded tokio runtime for the parent's signal wait loop.
///
/// The parent process is short-lived and only needs to wait for a signal and check the pid file.
/// A current-thread runtime avoids spawning worker threads in a process that is about to exit.
fn build_parent_runtime() -> tokio::runtime::Runtime {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.expect("failed to build tokio runtime for parent signal wait")
}
/// Wait for the daemon grandchild to send `SIGUSR1`, up to `timeout`.
///
/// 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};View on GitHub (pinned to 0046038bd4)
Solutions
- Check and raise limits: ulimit -n, ulimit -u, and the cgroup pids limit for the process
- Run the same binary foreground (--daemon off / no daemon) to confirm it works outside the restricted environment
- Inspect seccomp/sandbox policy for syscalls tokio needs (epoll_create1, eventfd, timerfd_create) and allow them
- Check lsof / /proc/<pid>/fd for fd leaks before the daemon step
Defensive patterns
Strategy: validation
Validate before calling
// Pre-flight in the target environment: can we even build a runtime?
fn tokio_runtime_buildable() -> bool {
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.is_ok()
} Prevention
- Set sane RLIMIT_NOFILE/RLIMIT_NPROC in the supervisor or container spec
- Include a startup smoke test that builds a tokio runtime inside your production image
- Prefer foreground execution under systemd over pingora daemon mode in restricted sandboxes
When it happens
Trigger: Running the daemonize path (wait_for_ready_or_exit -> build_parent_runtime) where Builder::new_current_thread().enable_all().build() fails: epoll/eventfd/timerfd creation blocked, or the process is out of file descriptors / memory for runtime resources.
Common situations: Containers with very low ulimit -n or cgroup pids.max; seccomp profiles (Docker default+strictening, gVisor, nsjail) blocking epoll_create1/eventfd; fd leaks accumulated before daemonization.
Related errors
- failed to build work-stealing Tokio runtime
- failed to build no-steal Tokio runtime worker
- failed to build offload runtime
- failed to register SIGUSR1 listener
- non-pathname unix sockets not supported as peer
AI-assisted analysis of cloudflare/pingora@0046038bd4 (2026-08-16).
Data as JSON: /api/errors/875c300fde62d0e4.
Report an issue: GitHub.