diff options
Diffstat (limited to 'spec/ruby/core/thread/handle_interrupt_spec.rb')
| -rw-r--r-- | spec/ruby/core/thread/handle_interrupt_spec.rb | 125 |
1 files changed, 125 insertions, 0 deletions
diff --git a/spec/ruby/core/thread/handle_interrupt_spec.rb b/spec/ruby/core/thread/handle_interrupt_spec.rb new file mode 100644 index 0000000000..aa03d4c66d --- /dev/null +++ b/spec/ruby/core/thread/handle_interrupt_spec.rb @@ -0,0 +1,125 @@ +require_relative '../../spec_helper' + +describe "Thread.handle_interrupt" do + def make_handle_interrupt_thread(interrupt_config, blocking = true) + interrupt_class = Class.new(RuntimeError) + + ScratchPad.record [] + + in_handle_interrupt = Queue.new + can_continue = Queue.new + + thread = Thread.new do + begin + Thread.handle_interrupt(interrupt_config) do + begin + in_handle_interrupt << true + if blocking + Thread.pass # Make it clearer the other thread needs to wait for this one to be in #pop + can_continue.pop + else + begin + can_continue.pop(true) + rescue ThreadError + Thread.pass + retry + end + end + rescue interrupt_class + ScratchPad << :interrupted + end + end + rescue interrupt_class + ScratchPad << :deferred + end + end + + in_handle_interrupt.pop + if blocking + # Ensure the thread is inside Thread#pop, as if thread.raise is done before it would be deferred + Thread.pass until thread.stop? + end + thread.raise interrupt_class, "interrupt" + can_continue << true + thread.join + + ScratchPad.recorded + end + + before :each do + Thread.pending_interrupt?.should == false # sanity check + end + + it "with :never defers interrupts until exiting the handle_interrupt block" do + make_handle_interrupt_thread(RuntimeError => :never).should == [:deferred] + end + + it "with :on_blocking defers interrupts until the next blocking call" do + make_handle_interrupt_thread(RuntimeError => :on_blocking).should == [:interrupted] + make_handle_interrupt_thread({ RuntimeError => :on_blocking }, false).should == [:deferred] + end + + it "with :immediate handles interrupts immediately" do + make_handle_interrupt_thread(RuntimeError => :immediate).should == [:interrupted] + end + + it "with :immediate immediately runs pending interrupts, before the block" do + Thread.handle_interrupt(RuntimeError => :never) do + current = Thread.current + Thread.new { + current.raise "interrupt immediate" + }.join + + Thread.pending_interrupt?.should == true + -> { + Thread.handle_interrupt(RuntimeError => :immediate) { + flunk "not reached" + } + }.should.raise(RuntimeError, "interrupt immediate") + Thread.pending_interrupt?.should == false + end + end + + it "also works with suspended Fibers and does not duplicate interrupts" do + fiber = Fiber.new { Fiber.yield } + fiber.resume + + Thread.handle_interrupt(RuntimeError => :never) do + current = Thread.current + Thread.new { + current.raise "interrupt with fibers" + }.join + + Thread.pending_interrupt?.should == true + -> { + Thread.handle_interrupt(RuntimeError => :immediate) { + flunk "not reached" + } + }.should.raise(RuntimeError, "interrupt with fibers") + Thread.pending_interrupt?.should == false + end + + fiber.resume + end + + it "runs pending interrupts at the end of the block, even if there was an exception raised in the block" do + executed = false + -> { + Thread.handle_interrupt(RuntimeError => :never) do + current = Thread.current + Thread.new { + current.raise "interrupt exception" + }.join + + Thread.pending_interrupt?.should == true + executed = true + raise "regular exception" + end + }.should.raise(RuntimeError, "interrupt exception") + executed.should == true + end + + it "supports multiple pairs in the Hash" do + make_handle_interrupt_thread(ArgumentError => :never, RuntimeError => :never).should == [:deferred] + end +end |
