diff --git a/examples/minimal.rb b/examples/minimal.rb index d637df7..742aaf0 100644 --- a/examples/minimal.rb +++ b/examples/minimal.rb @@ -60,8 +60,8 @@ def initialize(&block) @status = nil @pid = Process.fork do - Signal.trap(:INT) {raise Interrupt} - Signal.trap(:INT) {raise Terminate} + Signal.trap(:INT) {::Thread.current.raise(Interrupt)} + Signal.trap(:INT) {::Thread.current.raise(Terminate)} @channel.in.close diff --git a/lib/async/container/controller.rb b/lib/async/container/controller.rb index db4459e..2db6c50 100644 --- a/lib/async/container/controller.rb +++ b/lib/async/container/controller.rb @@ -176,16 +176,17 @@ def reload # Enter the controller run loop, trapping `SIGINT` and `SIGTERM`. def run # 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 - raise Interrupt + ::Thread.current.raise(Interrupt) end terminate_action = Signal.trap(:TERM) do - raise Terminate + ::Thread.current.raise(Terminate) end hangup_action = Signal.trap(:HUP) do - raise Hangup + ::Thread.current.raise(Hangup) end self.start diff --git a/lib/async/container/generic.rb b/lib/async/container/generic.rb index 6147298..05a4ec9 100644 --- a/lib/async/container/generic.rb +++ b/lib/async/container/generic.rb @@ -49,6 +49,8 @@ def initialize(**options) @keyed = {} end + attr :group + attr :state # A human readable representation of the container. diff --git a/lib/async/container/process.rb b/lib/async/container/process.rb index 7cd5358..aa24b6f 100644 --- a/lib/async/container/process.rb +++ b/lib/async/container/process.rb @@ -66,8 +66,9 @@ def exec(*arguments, ready: true, **options) def self.fork(**options) self.new(**options) do |process| ::Process.fork do - Signal.trap(:INT) {raise Interrupt} - Signal.trap(:TERM) {raise Terminate} + # We use `Thread.current.raise(...)` so that exceptions are filtered through `Thread.handle_interrupt` correctly. + Signal.trap(:INT) {::Thread.current.raise(Interrupt)} + Signal.trap(:TERM) {::Thread.current.raise(Terminate)} begin yield Instance.for(process) diff --git a/test/async/container/forked.rb b/test/async/container/forked.rb index 42adf4b..f4ed5d4 100644 --- a/test/async/container/forked.rb +++ b/test/async/container/forked.rb @@ -39,6 +39,33 @@ expect(container.statistics.restarts).to be == 2 end + it "can handle interrupts" do + finished = IO.pipe + interrupted = IO.pipe + + container.spawn(restart: true) do |instance| + Thread.handle_interrupt(Interrupt => :never) do + instance.ready! + + finished.first.gets + rescue ::Interrupt + interrupted.last.puts "incorrectly interrupted" + end + rescue ::Interrupt + interrupted.last.puts "correctly interrupted" + end + + container.wait_until_ready + + container.group.interrupt + sleep(0.001) + finished.last.puts "finished" + + expect(interrupted.first.gets).to be == "correctly interrupted\n" + + container.stop + end + it "should be multiprocess" do expect(subject).to be(:multiprocess?) end