diff --git a/example-app/README.md b/example-app/README.md index a365c5a..2b16df8 100644 --- a/example-app/README.md +++ b/example-app/README.md @@ -7,3 +7,14 @@ To run: bundle bundle exec clock ``` + +To test invocation of existing signal handlers, put this code at the very top of exe/clock: + +```ruby +%w[INT TERM].each do |signal| + Signal.trap(signal) do + puts "This is a well-behaving #{signal} handler from outside of ruby-clock" + exit + end +end +``` diff --git a/lib/ruby-clock.rb b/lib/ruby-clock.rb index d7ea7b1..e5ac92d 100644 --- a/lib/ruby-clock.rb +++ b/lib/ruby-clock.rb @@ -24,22 +24,37 @@ def wait_seconds ENV['RUBY_CLOCK_SHUTDOWN_WAIT_SECONDS']&.to_i || 29 end - def shutdown - puts "Shutting down ruby-clock. Waiting #{wait_seconds} seconds for jobs to finish..." - schedule.shutdown(wait: wait_seconds) - puts "...done 🐈️ 👋" + # shutdown is done async in a Thread because signal handlers should + # not have mutex locks created within them, which I believe can happen in + # the rufus shutdown process + def shutdown(old_handler=nil) + Thread.new do + sleep 0.1 # wait for trap block to exit + puts "Shutting down ruby-clock. Waiting #{wait_seconds} seconds for jobs to finish..." + schedule.shutdown(wait: wait_seconds) + puts "...done 🐈️ 👋" + if old_handler + puts "handing off shutdown to another signal handler..." + old_handler.call + else + exit + end + end end def listen_to_signals signals = %w[INT TERM] signals.each do |signal| old_handler = Signal.trap(signal) do - shutdown if old_handler.respond_to?(:call) - old_handler.call + shutdown(old_handler) else - exit + shutdown end + + # keep this line here at the end, to serve as some degree of demonstration that + # the handler exited before shutdown begins + puts("received #{signal}") && STDOUT.flush end end puts "RUBY_CLOCK_SHUTDOWN_WAIT_SECONDS is set to #{wait_seconds}"