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 |