A Ruby wrapper for Linux pidfd (Process File Descriptor) system calls, providing safer process management with guaranteed process identity.
Process file descriptors (pidfd) were introduced in Linux 5.3 (2019) to solve fundamental problems with traditional PID-based process management:
Traditional Unix PIDs have a critical flaw: PID reuse. When a process dies, its PID can be immediately reassigned to a new, unrelated process. This creates race conditions where:
- You might send signals to the wrong process
- Process state checks become unreliable
- Security vulnerabilities can arise from PID confusion
Pidfds provide a stable reference to a process that remains valid even if the PID is reused. Key benefits:
- Race-free signal delivery - Signals always go to the intended process
- Reliable process monitoring - Know definitively when a process exits
- Thread-safe operations - No TOCTTOU races
- Pollable file descriptors - Integrate with event loops efficiently
Use pidfd when you need:
- Reliable process management in production environments
- Non-child process monitoring without being the parent
- High-security applications where PID confusion is unacceptable
- Systems with high process churn where PID reuse is common
- Libraries operating in uncontrolled environments
Don't use pidfd for:
- macOS or Windows (Linux-only feature)
- Kernels older than Linux 5.3
- Simple parent-child relationships (traditional wait works fine)
Add this line to your application's Gemfile:
gem 'pidfd'And then execute:
bundle installOr install it yourself as:
gem install pidfd- Linux kernel 5.3 or newer
- Ruby 3.2 or newer
- FFI gem
require 'pidfd'
# Create a pidfd for a process
process = fork { sleep 10 }
pidfd = Pidfd.new(process)
# Check if process is alive
pidfd.alive? # => true
# Send a signal safely
pidfd.signal('TERM') # => true (signal sent)
# Clean up zombie process after it exits
pidfd.cleanup# Monitor any process by PID (requires appropriate permissions)
nginx_pid = File.read('/var/run/nginx.pid').to_i
pidfd = Pidfd.new(nginx_pid)
# Check status without race conditions
if pidfd.alive?
puts "Nginx is running"
else
puts "Nginx has stopped"
pidfd.cleanup
end# Traditional approach (UNSAFE - race condition)
Process.kill('TERM', pid) # Might hit wrong process if PID was reused!
# Pidfd approach (SAFE)
pidfd = Pidfd.new(pid)
pidfd.signal('TERM') # Guaranteed to hit the right process or fail safely# Pidfd provides a pollable file descriptor
pidfd = Pidfd.new(child_pid)
# Use with IO.select for non-blocking monitoring
loop do
if pidfd.alive?
# Process still running
sleep 0.1
else
# Process exited
pidfd.cleanup
break
end
endif Pidfd.supported?
puts "pidfd is supported on this system"
else
puts "pidfd is not available (wrong OS or kernel version)"
enddef safe_process_check(pid)
if Pidfd.supported?
pidfd = Pidfd.new(pid)
result = pidfd.alive?
pidfd.cleanup unless result
result
else
# Fallback to traditional (less safe) approach
Process.kill(0, pid)
true
rescue Errno::ESRCH
false
end
endDifferent architectures may use different syscall numbers. You can configure them:
# Default values for x86_64
Pidfd.pidfd_open_syscall = 434
Pidfd.pidfd_signal_syscall = 424
# For other architectures, consult your system headersbegin
pidfd = Pidfd.new(pid)
rescue Pidfd::Errors::PidfdOpenFailedError => e
# Process doesn't exist or permission denied
puts "Could not open pidfd: #{e.message}"
end
begin
pidfd.signal('TERM')
rescue Pidfd::Errors::PidfdSignalFailedError => e
# Signal delivery failed
puts "Could not send signal: #{e.message}"
end- Efficient: Pidfd operations are kernel-level, very fast
- Low overhead: Minimal memory usage per pidfd
- Scalable: Handles thousands of processes efficiently
- Non-blocking: Supports async operation patterns
Pidfd provides significant security improvements:
- Prevents signal delivery to wrong processes
- Eliminates PID confusion attacks
- Provides capability-based process references
- Works with Linux security modules (SELinux, AppArmor)
Based on the pidfd implementation in Karafka by Maciej Mensfeld.
Special thanks to:
- The Linux kernel team for implementing pidfd
- KJ Tsanaktsidis for Ruby core contributions discussions
- The Karafka community for battle-testing this implementation