Kernel#exit!
exit!(status = false) The exit! method terminates the Ruby process immediately without running any cleanup code. Unlike exit, it does not run at_exit handlers or ensure finalizers.
Basic Usage
# Exit with default code 1
exit!
# Exit with specific code
exit!(0) # Success
exit!(1) # Error
exit!(2) # Another error code
The status argument follows the same convention as exit: 0 means success, any non-zero value signals an error. The default status is 1 (general error), which differs from exit’s default of 0 (success). This difference is deliberate — exit! assumes an abnormal termination when no status is given.
How exit! differs from exit
# exit - runs at_exit handlers
at_exit { puts "Cleaning up..." }
exit
# Output: Cleaning up...
# exit! - immediate termination
at_exit { puts "Cleaning up..." }
exit!
# No output - exits immediately
The at_exit example makes the trade-off visible: exit prints the cleanup message before the process ends, while exit! terminates without any output. The code inside at_exit blocks, END blocks, and object finalizers is all skipped. Choose exit! only when skipping those hooks is the desired behavior.
Practical Examples
Fork Safety
# In parent process after fork
if fork.nil?
# Child process
# Exit immediately without running parent's at_exit
exit!
end
The fork pattern is the most common legitimate use of exit!. After fork, the child process inherits the parent’s at_exit handlers, which might close database connections or delete temporary files that the parent still needs. Calling exit! in the child avoids running those inherited handlers, keeping the parent’s cleanup intact.
Timeout Handling
# Kill process after timeout
begin
Timeout.timeout(5) do
long_running_task
end
rescue Timeout::Error
# Force exit without cleanup
exit!(1)
end
When a timeout fires, the code is already in an exceptional path. Using exit! here avoids running at_exit handlers that might themselves block or take too long — if the goal is to stop the process as quickly as possible, exit! delivers that guarantee without waiting for cleanup.
Signal Handling
trap(:KILL) { exit! } # Cannot trap KILL anyway
trap(:TERM) { exit!(1) } # Force TERM to exit immediately
Signal handlers run at unpredictable times, so cleanup code inside a handler can be risky — it might interrupt I/O or leave shared state inconsistent. exit! avoids that risk by stopping the process immediately, making it the safer choice for signal handlers where the only goal is to terminate.
Testing
# Exit child in tests without running other code
def run_test_case(test)
fork do
begin
test.run
rescue => e
puts "Test failed: #{e}"
exit!(1)
end
end
end
In the testing example, the child process runs a single test case and exits with exit! to report success or failure. The parent process can then Process.wait for the child and inspect the exit status without worrying about shared state or at_exit handlers from the test framework leaking into the child.
Exit Codes
exit!(0) # Success
exit!(1) # General error
exit!(2) # Misuse of command
exit!(126) # Command not executable
exit!(127) # Command not found
exit!(128) # Invalid exit argument
These codes follow the Unix convention where 0-127 are application-defined and 128+ are reserved for signal-related exits. In practice, most Ruby programs stick to 0 (success), 1 (general error), and occasionally 2 (usage error) — the other codes are rarely needed outside system-level scripts.
When to use exit!
- After fork to avoid double at_exit runs
- When you need guaranteed immediate termination
- In signal handlers where cleanup isn’t desired
- Performance-critical code where cleanup overhead matters
Each of these cases shares the same reasoning: the process needs to stop right away, and any cleanup code that would normally run is either unwanted, already handled by the parent, or impossible to run safely. The decision to use exit! should be explicit and rare.
When NOT to use exit!
- Normal application termination (use
exit) - When you need cleanup code to run
- In daemons that need to flush buffers
- When at_exit handlers contain important cleanup
Normal application shutdown should always use exit so that at_exit handlers, finalizers, and buffered I/O flush properly. Skipping cleanup can leave log files truncated, temporary files on disk, or database connections open — problems that are easy to miss in development but cause real issues in production.
Forcing an immediate stop
exit! is reserved for cases where the process should stop right away and no cleanup should run. That makes it useful in forked child processes, hard timeout paths, or a few signal-handling situations where the program should leave as quickly as possible. Because it skips at_exit handlers and all finalizers, the code should only use it when that trade-off is deliberate and well understood. The method does one thing and does it immediately — there is no negotiation, no cleanup, and no second chance.
The method is powerful, but the surrounding code should make the choice obvious. If the caller expects cleanup, exit! can be surprising. If the caller expects an immediate stop, though, the method says exactly that and keeps the shutdown path short.
That makes exit! a very specific tool rather than a general answer. It is best when the process really should disappear right away and no further Ruby code should run. In that role it is clear and decisive, but it should stay rare so the rest of the application can rely on normal cleanup paths.
Because it skips the usual cleanup hooks, the method should usually appear only where the process is already in an abnormal path or a child process is about to end. That keeps the code honest about the cost of the shortcut. If the program still needs to tidy up state — flushing file buffers, closing network connections, or removing lock files — exit is the safer and more descriptive choice.