Running code at shutdown via at_exit
In Ruby, we can register blocks of code to be run when the program exits using the at_exit method. This is useful for performing cleanup tasks, printing a goodbye message, or even somewhat unintuitively running the actual program.
at_exit do
puts "Goodbye!"
end
puts "Hello, world!"
# Output:
# Hello, world!
# Goodbye!
You can register multiple at_exit blocks, and they will be executed in reverse order of registration:
at_exit do
puts "first goodbye!"
at_exit do
puts "nested goodbye!"
end
end
at_exit do
puts "last goodbye!"
end
puts "Hello, world!"
# Output:
# Hello, world!
# last goodbye!
# first goodbye!
# nested goodbye!
Let's look at an example of using at_exit to clean up temporary files created during a program's execution:
require "net/http"
require "json"
NUMBER_OF_POSTS = ENV.fetch("NUMBER_OF_POSTS", 10).to_i
DOWNLOADS_DIR = "tmp-downloads"
at_exit do
puts
puts "Cleaning up temporary files..."
require "fileutils"
FileUtils.rm_rf(DOWNLOADS_DIR)
end
Dir.mkdir(DOWNLOADS_DIR) unless Dir.exist?(DOWNLOADS_DIR)
(1..NUMBER_OF_POSTS).each do |i|
puts "Downloading post #{i}..."
response = Net::HTTP.get("jsonplaceholder.typicode.com", "/posts/#{i + 1}")
File.write("#{DOWNLOADS_DIR}/post_#{i + 1}.json", response)
sleep 0.1 # Simulate a longer download time
end
posts_word_tally = Dir.glob("#{DOWNLOADS_DIR}/*.json").each_with_object({}) do |path, tally|
post = JSON.load_file(path)
post.fetch("body").split.tally(tally)
end
puts "Top ten words across all #{NUMBER_OF_POSTS} posts:"
posts_word_tally.sort_by { |_, count| -count }.first(10).each do |word, count|
puts "#{word}: #{count}"
end
Now, if the program is interrupted (e.g. via Ctrl+C), the at_exit block will still run, ensuring that the temporary files are cleaned up, with an output like:
Downloading post 1...
Downloading post 2...
Downloading post 3...
^C
Cleaning up temporary files...
scratchpad.rb:19:in 'Kernel#sleep': Interrupt
from scratchpad.rb:19:in 'block in <main>'
from scratchpad.rb:16:in 'Range#each'
from scratchpad.rb:16:in '<main>'
If the program is allowed to fully execute, it will finally print:
Top ten words across all 10 posts:
aut: 9
qui: 9
et: 8
ut: 8
quis: 7
voluptatem: 5
molestiae: 5
sed: 5
autem: 4
ea: 4
Cleaning up temporary files...
Either way, we don't have to worry about leaving temporary files around!
Another rather unintuitive use case for at_exit is to execute a program's main logic through it. For example, minitest uses it to run tests after they have been defined. The mini web framework sinatra uses it to start its web server after all routes have been initialized.
History
at_exit shipped in Ruby 1.2 in 1998.
Ruby 1.4 in 1999 made the runner handle exceptions from exit handlers, so one failing handler would not simply stop shutdown processing.
Ruby 1.8.1 in 2003 tightened the API by rejecting at_exit calls without a block.
Since then the API has stayed stable.