Project

pidfd

0.0
No release in over 3 years
Provides race-free process management using Linux pidfd (process file descriptors) for safer signal delivery and process monitoring
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
 Dependencies

Runtime

>= 1.15
 Project Readme

Pidfd

Gem Version Build Status

A Ruby wrapper for Linux pidfd (Process File Descriptor) system calls, providing safer process management with guaranteed process identity.

What is pidfd?

Process file descriptors (pidfd) were introduced in Linux 5.3 (2019) to solve fundamental problems with traditional PID-based process management:

The Problem with PIDs

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

The pidfd Solution

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

When to Use This Gem

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)

Installation

Add this line to your application's Gemfile:

gem 'pidfd'

And then execute:

bundle install

Or install it yourself as:

gem install pidfd

Requirements

  • Linux kernel 5.3 or newer
  • Ruby 3.2 or newer
  • FFI gem

Usage

Basic Process Management

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

Monitoring Non-Child Processes

# 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

Safe Signal Delivery

# 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

Integration with Event Loops

# 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
end

Platform Support

Checking Support

if Pidfd.supported?
  puts "pidfd is supported on this system"
else
  puts "pidfd is not available (wrong OS or kernel version)"
end

Fallback Strategy

def 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
end

Configuration

Custom Syscall Numbers

Different 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 headers

Error Handling

begin
  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

Performance Considerations

  • 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

Security

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)

Further Reading

Author

Based on the pidfd implementation in Karafka by Maciej Mensfeld.

Acknowledgments

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