The pidfd: File Descriptors for Processes and the PID Reuse Race It Kills

2026-08-16

Before Linux 5.3, referring to a process meant holding its PID — a small integer the kernel is free to recycle the instant the process is reaped. This creates a race that has crashed production supervisors for decades: you read a PID from a config or a pidfile, decide to send it SIGTERM, and in the microseconds between decision and kill(pid, SIGTERM), the original process exits, gets reaped, and the PID is handed to an unrelated program you just killed by accident.

A pidfd is a file descriptor that refers to a specific process. Because it's an fd, the kernel pins the reference — the PID slot cannot be reused while any pidfd points to that process. You get one three ways:

What you can do with it:

Real-world example: systemd's cgroup-empty notification used to combine PID scanning with signals, and had a documented race where restarting a service could occasionally SIGKILL a freshly-spawned unrelated process that had inherited the recycled PID. systemd 245 (2020) switched to pidfds, and this class of bug disappeared. Container runtimes (runc, crun) did the same migration for the same reason.

Rule of thumb for the race window: on a busy 64-bit Linux system with pid_max = 4194304 and, say, 50 process creations per second, a specific PID's expected reuse time is pid_max / rate ≈ 23 hours. Sounds huge — but on a short-lived-worker system doing 5000 forks/second, it collapses to ~14 minutes. Any long-lived supervisor holding a stale PID longer than that has already lost.

The one gotcha: pidfds are not inherited across fork() by default the way you might expect for parent-child semantics, and passing one over an AF_UNIX socket with SCM_RIGHTS gives the receiver full signaling power — treat them as capabilities.

Key Takeaway: A pidfd pins a process reference in the kernel, closing the PID-reuse race that made kill(pid, ...) fundamentally unsafe for any process you didn't just fork yourself.

All newsletters