Skip to content

Instantly share code, notes, and snippets.

@mgreenly
Created February 22, 2020 22:11
Show Gist options
  • Save mgreenly/49a0f7f30af3f3481f039b29ad6a52ab to your computer and use it in GitHub Desktop.
Save mgreenly/49a0f7f30af3f3481f039b29ad6a52ab to your computer and use it in GitHub Desktop.
#
# The key thing to understand about this is that an `ensure` section is not guarnteed to run!
#
# If code executing is in the main thread of the process and a Interrupt, caused by a SIGINT or SIGTERM, occurs
# `ensure` blocks in that thread will not be executed. This is because Interrupt is an asynchrnous event and
# Ruby has no way to return to the point in execution it left when the signal happened.
#
# But, because Interrupts are only processed in the main thread of a process, moving all the actual code of an
# application into a child thread fixes this problem.
#
class App
def run(runner)
until runner.exit?
# The code inside this block will not be interrupted by SIGTERM or SIGINT
# because they are only raised in the main thread. Instead we can decide
# how to shutdown gracefully when runner.exit? is true.
end
end
end
module Cli
class << self
def run(app) # Here we create thread to run application in, because it's in a
@exit = false # thread when it runs it will not be Interrupted by SIGINT or SIGTERM.
thread = Thread.new do
app.run(self) # We inject the `self` context into the app so the app can call `exit?` on it.
end
thread.join # Wait for hte app to exit
rescue Interrupt
@exit = true # If we rescue an Interrupt in the main thread we set the exit flag
thread.join # then wait for the app to shutdown gracefully.
end
def exit?
@exit
end
end
end
Cli.run(App.new)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment