CrackedRuby logo

CrackedRuby

Signal Lists

Ruby's Signal.list method provides access to system signal names and their numeric identifiers for cross-platform process communication.

Core Modules Signal Module
3.8.2

Overview

Ruby's Signal.list method returns a hash mapping signal names to their corresponding numeric values on the current operating system. This method serves as the foundation for signal-based inter-process communication, allowing Ruby programs to discover which signals are available and reference them by name or number.

The Signal module wraps operating system signal functionality, providing a Ruby interface to Unix-like signal handling mechanisms. Signal handling enables processes to communicate asynchronously, respond to system events, and coordinate shutdown procedures.

# Get all available signals on the system
signals = Signal.list
puts signals["TERM"]  # => 15
puts signals["INT"]   # => 2
puts signals["USR1"]  # => 10

Signal lists vary between operating systems. Linux systems typically include POSIX-standard signals plus additional platform-specific signals, while other Unix-like systems may have different signal sets. Ruby normalizes signal names by removing the "SIG" prefix - SIGTERM becomes "TERM" in the returned hash.

The method returns a frozen hash containing string keys (signal names) and integer values (signal numbers). Signal numbers are system-specific but follow general conventions - TERM is typically 15, INT is 2, and KILL is 9 across most platforms.

# Check for specific signals before using them
available_signals = Signal.list

if available_signals.key?("USR1")
  Signal.trap("USR1") { puts "Received USR1 signal" }
else
  puts "USR1 signal not available on this system"
end

Ruby's signal handling operates through deferred execution - when a signal arrives, Ruby registers the event but delays callback execution until it's safe for the interpreter. This prevents signal handlers from interrupting critical Ruby VM operations while maintaining responsiveness to external signals.

Basic Usage

The Signal.list method requires no arguments and returns immediately with the complete signal mapping for the current platform. The returned hash contains all signals supported by the operating system, including both standard POSIX signals and platform-specific extensions.

# Basic signal list retrieval
all_signals = Signal.list
puts "System supports #{all_signals.size} signals"

# Common signals present on most systems
puts "TERM signal number: #{all_signals['TERM']}"
puts "INT signal number: #{all_signals['INT']}"  
puts "KILL signal number: #{all_signals['KILL']}"

Signal names in the returned hash omit the traditional "SIG" prefix used in C and shell commands. Ruby normalizes signal names to their core identifiers - SIGTERM becomes "TERM", SIGINT becomes "INT", and so forth.

# Iterate through all available signals
Signal.list.each do |name, number|
  puts "Signal #{name} has number #{number}"
end

# Filter signals by pattern
user_signals = Signal.list.select { |name, _| name.start_with?("USR") }
puts "User-defined signals: #{user_signals}"
# => {"USR1"=>10, "USR2"=>12}

The signal list serves as a discovery mechanism for platform-specific signal availability. Applications can query the list before attempting to trap signals, avoiding runtime errors on systems that don't support specific signals.

# Safe signal registration with availability checking
def setup_signal_handlers
  signals = Signal.list
  
  # Standard termination signals
  Signal.trap("TERM") { graceful_shutdown } if signals.key?("TERM")
  Signal.trap("INT") { graceful_shutdown } if signals.key?("INT")
  
  # User-defined signals for runtime control
  if signals.key?("USR1")
    Signal.trap("USR1") { toggle_debug_mode }
  end
  
  if signals.key?("USR2")  
    Signal.trap("USR2") { reload_configuration }
  end
end

Signal numbers from Signal.list can be used interchangeably with signal names in other Signal module methods. Both Signal.trap("TERM", handler) and Signal.trap(15, handler) accomplish the same registration, assuming TERM maps to 15 on the current system.

# Using signal numbers directly
term_number = Signal.list["TERM"]
Signal.trap(term_number) { puts "Termination requested" }

# Equivalent to using the string name
Signal.trap("TERM") { puts "Termination requested" }

The EXIT signal (number 0) appears in every signal list but represents a special case - it triggers during normal program termination rather than in response to external signals. Applications can trap EXIT to perform cleanup operations regardless of how the program ends.

Error Handling & Debugging

Signal list operations rarely fail since Signal.list performs read-only system queries, but signal-related debugging often requires understanding platform differences and signal availability patterns. Different operating systems support varying signal sets, and applications must handle these variations gracefully.

Platform variations create the primary source of signal-related errors. Code that assumes specific signals exist will fail on systems lacking those signals. Always verify signal presence before attempting to trap or send signals.

# Robust signal setup with error handling
def configure_signals_safely
  available = Signal.list
  
  # Check for required signals
  required_signals = ["TERM", "INT"]
  missing_signals = required_signals - available.keys
  
  if missing_signals.any?
    raise "Required signals missing: #{missing_signals.join(', ')}"
  end
  
  # Configure handlers with fallback options
  begin
    Signal.trap("TERM") { controlled_shutdown }
  rescue ArgumentError => e
    warn "Failed to trap TERM: #{e.message}"
    # Fall back to EXIT trap
    Signal.trap("EXIT") { controlled_shutdown }
  end
end

Signal number conflicts rarely occur within a single platform, but cross-platform code may encounter different numbering schemes. Some embedded or specialized systems use non-standard signal numbers, making string-based signal names more portable than numeric references.

# Debug signal configuration issues
def debug_signal_environment
  signals = Signal.list
  
  puts "Platform: #{RUBY_PLATFORM}"
  puts "Available signals: #{signals.size}"
  
  # Check for common signals and their numbers
  common_signals = ["HUP", "INT", "QUIT", "TERM", "KILL", "USR1", "USR2"]
  common_signals.each do |sig|
    if signals.key?(sig)
      puts "#{sig}: #{signals[sig]}"
    else
      puts "#{sig}: NOT AVAILABLE"
    end
  end
  
  # Look for platform-specific signals
  platform_specific = signals.keys - common_signals
  puts "Platform-specific signals: #{platform_specific.join(', ')}"
end

Signal handlers themselves can fail during execution, but these failures occur separately from signal list operations. Handler exceptions don't prevent signal delivery but may leave processes in inconsistent states.

# Signal handler with error containment
Signal.trap("USR1") do
  begin
    complex_handler_logic
  rescue StandardError => e
    # Log error without crashing the process
    warn "Signal handler error: #{e.message}"
    File.open("signal_errors.log", "a") do |f|
      f.puts "#{Time.now}: USR1 handler failed: #{e.message}"
    end
  end
end

Some signals cannot be trapped or ignored by design. SIGKILL (number 9) and SIGSTOP (typically 19) bypass process signal handling entirely, making them unsuitable for trapping. Attempting to trap these signals raises ArgumentError.

# Handle non-trappable signals appropriately
def setup_signal_traps
  signals = Signal.list
  non_trappable = ["KILL", "STOP"]
  
  signals.each do |name, number|
    next if non_trappable.include?(name)
    
    begin
      Signal.trap(name) { puts "Received #{name}" }
    rescue ArgumentError => e
      puts "Cannot trap #{name}: #{e.message}"
    end
  end
end

Production Patterns

Production Ruby applications rely heavily on signal handling for graceful shutdowns, configuration reloading, and operational control. Web servers like Puma, Unicorn, and Passenger use signals extensively for worker process management and zero-downtime deployments.

Signal lists enable applications to implement portable signal handling across different deployment platforms. Production systems often run on various operating systems, and signal availability checking prevents deployment failures on platforms with different signal support.

# Production-ready signal configuration
class SignalManager
  def initialize
    @available_signals = Signal.list
    @shutdown_callbacks = []
    @reload_callbacks = []
    setup_handlers
  end
  
  def on_shutdown(&block)
    @shutdown_callbacks << block
  end
  
  def on_reload(&block) 
    @reload_callbacks << block
  end
  
  private
  
  def setup_handlers
    # Graceful shutdown signals
    ["TERM", "INT"].each do |sig|
      next unless @available_signals.key?(sig)
      Signal.trap(sig) { execute_shutdown }
    end
    
    # Configuration reload signal
    if @available_signals.key?("HUP")
      Signal.trap("HUP") { execute_reload }
    end
    
    # Custom operational signals
    setup_operational_signals
  end
  
  def setup_operational_signals
    # Worker scaling signals (common in web servers)
    Signal.trap("TTIN") { scale_workers_up } if @available_signals.key?("TTIN")
    Signal.trap("TTOU") { scale_workers_down } if @available_signals.key?("TTOU")
    
    # Status and debugging signals
    Signal.trap("USR1") { dump_status } if @available_signals.key?("USR1")
    Signal.trap("USR2") { toggle_debug } if @available_signals.key?("USR2")
  end
  
  def execute_shutdown
    @shutdown_callbacks.each(&:call)
    exit 0
  end
  
  def execute_reload
    @reload_callbacks.each(&:call)
  end
end

# Usage in web application
signal_manager = SignalManager.new

signal_manager.on_shutdown do
  # Close database connections
  ActiveRecord::Base.connection_pool.disconnect!
  # Stop background workers
  Sidekiq.stop!
  # Log shutdown
  Rails.logger.info "Application shutting down gracefully"
end

signal_manager.on_reload do
  # Reload application configuration
  Rails.application.reload_routes! if defined?(Rails)
  # Clear caches
  Rails.cache.clear
end

Container orchestration platforms like Docker and Kubernetes send specific signals during container lifecycle events. Applications must handle these signals appropriately to integrate smoothly with container management systems.

# Container-aware signal handling
class ContainerSignalHandler
  def initialize
    @signals = Signal.list
    @running = true
    setup_container_signals
  end
  
  def wait_for_shutdown
    while @running
      sleep 1
    end
  end
  
  private
  
  def setup_container_signals
    # Docker sends SIGTERM for graceful shutdown
    if @signals.key?("TERM")
      Signal.trap("TERM") do
        puts "Received SIGTERM, shutting down..."
        @running = false
        cleanup_resources
      end
    end
    
    # Handle immediate shutdown requests
    if @signals.key?("INT")
      Signal.trap("INT") do
        puts "Received SIGINT, immediate shutdown"
        @running = false
      end
    end
    
    # Some orchestrators use SIGUSR1 for custom operations
    if @signals.key?("USR1")
      Signal.trap("USR1") { dump_metrics }
    end
  end
  
  def cleanup_resources
    # Close file handles
    # Finish processing current requests
    # Save state to persistent storage
  end
  
  def dump_metrics
    # Output application metrics for monitoring
    puts "Memory usage: #{`ps -o rss= -p #{Process.pid}`.strip} KB"
    puts "Open files: #{`lsof -p #{Process.pid} | wc -l`.strip}"
  end
end

Multi-process applications use signals for coordination between parent and child processes. Web servers spawn worker processes and use signals to manage worker lifecycle, load balancing, and configuration updates.

# Multi-process signal coordination
class WorkerManager
  def initialize(worker_count: 4)
    @workers = {}
    @worker_count = worker_count
    @signals = Signal.list
    setup_master_signals
  end
  
  def start_workers
    @worker_count.times do |i|
      spawn_worker(i)
    end
    
    # Monitor worker processes
    monitor_loop
  end
  
  private
  
  def spawn_worker(id)
    pid = fork do
      setup_worker_signals
      worker_main_loop
    end
    
    @workers[pid] = { id: id, started_at: Time.now }
  end
  
  def setup_master_signals
    # Graceful shutdown of all workers
    Signal.trap("TERM") { shutdown_all_workers }
    
    # Restart all workers
    Signal.trap("HUP") { restart_all_workers } if @signals.key?("HUP")
    
    # Increment worker count
    Signal.trap("TTIN") { spawn_worker(@workers.size) } if @signals.key?("TTIN")
    
    # Decrement worker count  
    Signal.trap("TTOU") { terminate_oldest_worker } if @signals.key?("TTOU")
  end
  
  def setup_worker_signals
    # Workers handle shutdown independently
    Signal.trap("TERM") { exit 0 }
    Signal.trap("INT") { exit 0 }
  end
  
  def shutdown_all_workers
    @workers.keys.each do |pid|
      Process.kill("TERM", pid) rescue nil
    end
    
    # Wait for workers to exit
    @workers.keys.each do |pid|
      Process.wait(pid) rescue nil
    end
    
    exit 0
  end
end

Reference

Signal Module Methods

Method Parameters Returns Description
Signal.list None Hash<String, Integer> Returns hash of signal names to numbers
Signal.trap(signal, command) signal (String/Integer), command (String/Proc) Previous handler Installs signal handler
Signal.signame(signo) signo (Integer) String or nil Converts signal number to name

Common Signal Names and Numbers

Signal Name Typical Number Purpose Trappable
"EXIT" 0 Program termination Yes
"HUP" 1 Hangup/reload configuration Yes
"INT" 2 Interrupt (Ctrl+C) Yes
"QUIT" 3 Quit with core dump Yes
"KILL" 9 Force termination No
"TERM" 15 Termination request Yes
"STOP" 19 Stop process No
"USR1" 10 User-defined signal 1 Yes
"USR2" 12 User-defined signal 2 Yes
"CHLD" 17 Child process status change Yes

Platform-Specific Signals

Signal Name Linux macOS Purpose
"WINCH" 28 28 Window size change
"PIPE" 13 13 Broken pipe
"ALRM" 14 14 Timer expiration
"PROF" 27 27 Profiling timer
"VTALRM" 26 26 Virtual timer

Signal List Properties

Property Value Description
Return Type Hash<String, Integer> Frozen hash mapping names to numbers
Key Format String without "SIG" prefix "TERM" instead of "SIGTERM"
Value Range 0-31 (typically) Platform-dependent signal numbers
Mutability Frozen Cannot modify returned hash
Thread Safety Safe Read-only operation

Error Conditions

Condition Exception Handling Strategy
Signal not available ArgumentError in trap Check Signal.list first
Non-trappable signal ArgumentError in trap Use platform signal documentation
Invalid signal number ArgumentError in signame Validate against signal list
Reserved signal ArgumentError in trap Cannot trap KILL, STOP

Usage Patterns

Pattern Code Example Use Case
Signal Discovery Signal.list.key?("USR1") Check signal availability
Cross-Platform signals = Signal.list; signals["TERM"] Portable signal references
Bulk Registration `Signal.list.each { name, _
Number Conversion Signal.signame(Signal.list["TERM"]) Convert between formats

Production Configuration Matrix

Application Type Required Signals Optional Signals Configuration Pattern
Web Server TERM, INT, HUP TTIN, TTOU, USR1, USR2 Graceful shutdown + worker scaling
Background Worker TERM, INT USR1, USR2 Job processing control
CLI Applic