describe :thread_exit, shared: true do before :each do ScratchPad.clear end # This spec randomly kills mspec worker like: https://ci.appveyor.com/project/ruby/ruby/builds/19390874/job/wv1bsm8skd4e1pxl # TODO: Investigate the cause or at least print helpful logs, and remove this `platform_is_not` guard. platform_is_not :mingw do it "kills sleeping thread" do sleeping_thread = Thread.new do sleep ScratchPad.record :after_sleep end Thread.pass while sleeping_thread.status and sleeping_thread.status != "sleep" sleeping_thread.send(@method) sleeping_thread.join ScratchPad.recorded.should == nil end it "kills current thread" do thread = Thread.new do Thread.current.send(@method) ScratchPad.record :after_sleep end thread.join ScratchPad.recorded.should == nil end it "runs ensure clause" do thread = ThreadSpecs.dying_thread_ensures(@method) { ScratchPad.record :in_ensure_clause } thread.join ScratchPad.recorded.should == :in_ensure_clause end it "runs nested ensure clauses" do ScratchPad.record [] @outer = Thread.new do begin @inner = Thread.new do begin sleep ensure ScratchPad << :inner_ensure_clause end end sleep ensure ScratchPad << :outer_ensure_clause @inner.send(@method) @inner.join end end Thread.pass while @outer.status and @outer.status != "sleep" Thread.pass until @inner Thread.pass while @inner.status and @inner.status != "sleep" @outer.send(@method) @outer.join ScratchPad.recorded.should include(:inner_ensure_clause) ScratchPad.recorded.should include(:outer_ensure_clause) end it "does not set $!" do thread = ThreadSpecs.dying_thread_ensures(@method) { ScratchPad.record $! } thread.join ScratchPad.recorded.should == nil end it "does not reset $!" do ScratchPad.record [] exc = RuntimeError.new("foo") thread = Thread.new do begin raise exc ensure ScratchPad << $! begin Thread.current.send(@method) ensure ScratchPad << $! end end end thread.join ScratchPad.recorded.should == [exc, exc] end it "cannot be rescued" do thread = Thread.new do begin Thread.current.send(@method) rescue Exception ScratchPad.record :in_rescue end ScratchPad.record :end_of_thread_block end thread.join ScratchPad.recorded.should == nil end it "kills the entire thread when a fiber is active" do t = Thread.new do Fiber.new do sleep end.resume ScratchPad.record :fiber_resumed end Thread.pass while t.status and t.status != "sleep" t.send(@method) t.join ScratchPad.recorded.should == nil end it "kills other fibers of that thread without running their ensure clauses" do t = Thread.new do f = Fiber.new do ScratchPad.record :fiber_resumed begin Fiber.yield ensure ScratchPad.record :fiber_ensure end end f.resume sleep end Thread.pass until t.stop? t.send(@method) t.join ScratchPad.recorded.should == :fiber_resumed end # This spec is a mess. It fails randomly, it hangs on MRI, it needs to be removed quarantine! do it "killing dying running does nothing" do in_ensure_clause = false exit_loop = true t = ThreadSpecs.dying_thread_ensures do in_ensure_clause = true loop { if exit_loop then break end } ScratchPad.record :after_stop end Thread.pass until in_ensure_clause == true 10.times { t.send(@method); Thread.pass } exit_loop = true t.join ScratchPad.recorded.should == :after_stop end end quarantine! do it "propagates inner exception to Thread.join if there is an outer ensure clause" do thread = ThreadSpecs.dying_thread_with_outer_ensure(@method) { } -> { thread.join }.should raise_error(RuntimeError, "In dying thread") end it "runs all outer ensure clauses even if inner ensure clause raises exception" do ThreadSpecs.join_dying_thread_with_outer_ensure(@method) { ScratchPad.record :in_outer_ensure_clause } ScratchPad.recorded.should == :in_outer_ensure_clause end it "sets $! in outer ensure clause if inner ensure clause raises exception" do ThreadSpecs.join_dying_thread_with_outer_ensure(@method) { ScratchPad.record $! } ScratchPad.recorded.to_s.should == "In dying thread" end end it "can be rescued by outer rescue clause when inner ensure clause raises exception" do thread = Thread.new do begin begin Thread.current.send(@method) ensure raise "In dying thread" end rescue Exception ScratchPad.record $! end :end_of_thread_block end thread.value.should == :end_of_thread_block ScratchPad.recorded.to_s.should == "In dying thread" end it "is deferred if ensure clause does Thread.stop" do ThreadSpecs.wakeup_dying_sleeping_thread(@method) { Thread.stop; ScratchPad.record :after_sleep } ScratchPad.recorded.should == :after_sleep end # Hangs on 1.8.6.114 OS X, possibly also on Linux quarantine! do it "is deferred if ensure clause sleeps" do ThreadSpecs.wakeup_dying_sleeping_thread(@method) { sleep; ScratchPad.record :after_sleep } ScratchPad.recorded.should == :after_sleep end end # This case occurred in JRuby where native threads are used to provide # the same behavior as MRI green threads. Key to this issue was the fact # that the thread which called #exit in its block was also being explicitly # sent #join from outside the thread. The 100.times provides a certain # probability that the deadlock will occur. It was sufficient to reliably # reproduce the deadlock in JRuby. it "does not deadlock when called from within the thread while being joined from without" do 100.times do t = Thread.new { Thread.stop; Thread.current.send(@method) } Thread.pass while t.status and t.status != "sleep" t.wakeup.should == t t.join.should == t end end end # platform_is_not :mingw end