Skip to content

Commit

Permalink
More robust signal handling.
Browse files Browse the repository at this point in the history
  • Loading branch information
ioquatix committed Dec 22, 2024
1 parent 5ad7b89 commit 269a5e8
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 30 deletions.
61 changes: 35 additions & 26 deletions lib/async/container/controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -187,47 +187,56 @@ def reload
def run
@notify&.status!("Initializing...")

with_signal_handlers do
self.start

while @container&.running?
begin
@container.wait
rescue SignalException => exception
if handler = @signals[exception.signo]
begin
handler.call
rescue SetupError => error
Console.error(self) {error}
end
else
raise
end
end
end
rescue Interrupt
self.stop
rescue Terminate
self.stop(false)
ensure
self.stop(false)
end
end

private def with_signal_handlers
# I thought this was the default... but it doesn't always raise an exception unless you do this explicitly.
# We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly.

interrupt_action = Signal.trap(:INT) do
# $stderr.puts "Received INT signal, terminating...", caller
# We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly.
# $stderr.puts "Received Interrupt signal, terminating...", caller
::Thread.current.raise(Interrupt)
end

terminate_action = Signal.trap(:TERM) do
# $stderr.puts "Received TERM signal, terminating...", caller
# $stderr.puts "Received Terminate signal, terminating...", caller
::Thread.current.raise(Terminate)
end

hangup_action = Signal.trap(:HUP) do
# $stderr.puts "Received HUP signal, restarting...", caller
# $stderr.puts "Received Hangup signal, restarting...", caller
::Thread.current.raise(Hangup)
end

self.start

while @container&.running?
begin
@container.wait
rescue SignalException => exception
if handler = @signals[exception.signo]
begin
handler.call
rescue SetupError => error
Console.error(self) {error}
end
else
raise
end
end
::Thread.handle_interrupt(SignalException => :on_blocking) do
yield
end
rescue Interrupt
self.stop
rescue Terminate
self.stop(false)
ensure
self.stop(false)

# Restore the interrupt handler:
Signal.trap(:INT, interrupt_action)
Signal.trap(:TERM, terminate_action)
Expand Down
14 changes: 11 additions & 3 deletions lib/async/container/group.rb
Original file line number Diff line number Diff line change
Expand Up @@ -142,11 +142,19 @@ def wait_for_children(duration = nil)

if !@running.empty?
# Maybe consider using a proper event loop here:
if ready = self.select(duration)
ready.each do |io|
@running[io].resume
end
end
end
end

def select(duration)
::Thread.handle_interrupt(SignalException => :immediate) do
readable, _, _ = ::IO.select(@running.keys, nil, nil, duration)

readable&.each do |io|
@running[io].resume
end
return readable
end
end

Expand Down
2 changes: 1 addition & 1 deletion lib/async/container/process.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def self.fork(**options)
Signal.trap(:INT) {::Thread.current.raise(Interrupt)}
Signal.trap(:TERM) {::Thread.current.raise(Terminate)}

begin
::Thread.handle_interrupt(SignalException => :immediate) do
yield Instance.for(process)
rescue Interrupt
# Graceful exit.
Expand Down

0 comments on commit 269a5e8

Please sign in to comment.