Created
February 22, 2020 22:11
-
-
Save mgreenly/49a0f7f30af3f3481f039b29ad6a52ab to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# | |
# 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