edit: $/Dev10/feature/vs_langs01/Merlin/Main/Languages/Ruby/Libraries.LCA_RESTRICTED/Builtins/ThreadOps.cs;C695420 File: ThreadOps.cs =================================================================== --- $/Dev10/feature/vs_langs01/Merlin/Main/Languages/Ruby/Libraries.LCA_RESTRICTED/Builtins/ThreadOps.cs;C695420 (server) 1/4/2009 11:36 PM +++ Shelved Change: $/Dev10/feature/vs_langs01/Merlin/Main/Languages/Ruby/Libraries.LCA_RESTRICTED/Builtins/ThreadOps.cs;hangs @@ -159,6 +159,7 @@ internal Exception Exception { get; set; } internal object Result { get; set; } internal bool CreatedFromRuby { get; set; } + private bool IsSleeping { get; set; } internal bool AbortOnException { get { @@ -188,11 +189,18 @@ /// lock which can be signalled from another thread. /// internal void Sleep() { - _runSignal.WaitOne(); + try { + IsSleeping = true; + _runSignal.WaitOne(); + } finally { + IsSleeping = false; + } } internal void Run() { - _runSignal.Set(); + if (IsSleeping) { + _runSignal.Set(); + } } } @@ -490,6 +498,9 @@ } if ((state & ThreadState.WaitSleepJoin) == ThreadState.WaitSleepJoin) { + // We will report a thread to be sleeping more often than in CRuby. This is because any "lock" statement + // can potentially cause ThreadState.WaitSleepJoin. Also, "Thread.pass" does System.Threading.Thread.Sleep(0) + // which also briefly changes the state to ThreadState.WaitSleepJoin return RubyThreadStatus.Sleeping; } @@ -633,6 +644,7 @@ } catch (Exception e) { if (IsRubyThreadExit(e)) { Utils.Log(String.Format("Thread {0} exited.", info.Thread.ManagedThreadId), "THREAD"); + info.Result = false; } else { e = RubyOps.GetVisibleException(e); RubyExceptionData.ActiveExceptionHandled(e); =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/ironruby-tags/critical_tags.txt;C691193 File: critical_tags.txt =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/ironruby-tags/critical_tags.txt;C691193 (server) 1/6/2009 11:39 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/ironruby-tags/critical_tags.txt;hangs @@ -37,6 +37,10 @@ core\string\hex_tags.txt:3:critical:String#hex returns 0 on error core\string\oct_tags.txt:3:critical:String#oct takes an optional sign core\string\oct_tags.txt:3:critical:String#oct returns 0 on error +core\thread\critical_tags.txt:0:unstable:Thread.critical= does not change status of other existing threads +core\thread\critical_tags.txt:0:critical:Thread.critical= defers exit until Thread.pass +core\thread\critical_tags.txt:0:critical:Thread.critical= can be mismatched +core\thread\critical_tags.txt:0:critical:Thread.critical= schedules other threads on Thread.stop core\thread\raise_tags.txt:0:critical:Thread#raise on another thread re-raises active exception language\method_tags.txt:0:critical:Calling a method fails with both lambda and block argument library\mathn\rational\power2_tags.txt:0:critical:Rational#power2 when passed [Rational] returns Rational.new!(1, 1) when the passed argument is 0 =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/ironruby-tags/1.8/core/thread/critical_tags.txt;C695420 File: critical_tags.txt =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/ironruby-tags/1.8/core/thread/critical_tags.txt;C695420 (server) 1/6/2009 4:05 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/ironruby-tags/1.8/core/thread/critical_tags.txt;hangs @@ -1,4 +1,5 @@ -fails:Thread.critical defers exit -critical:Thread.critical defers exit until Thread.pass -fails:Thread.critical is not reset if the critical thread is killed -critical:Thread.critical can be mismatched \ No newline at end of file +unstable:Thread.critical= does not change status of other existing threads +fails:Thread.critical= defers exit +critical:Thread.critical= defers exit until Thread.pass +critical:Thread.critical= can be mismatched +critical:Thread.critical= schedules other threads on Thread.stop \ No newline at end of file =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/alive_spec.rb;C544557 File: alive_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/alive_spec.rb;C544557 (server) 1/7/2009 11:49 AM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/alive_spec.rb;hangs @@ -2,19 +2,33 @@ require File.dirname(__FILE__) + '/fixtures/classes' describe "Thread#alive?" do - it "returns true as long as the thread is alive" do - c = Channel.new - t = Thread.new { c.receive } - begin - t.alive?.should == true - ensure - c << nil - end + it "can check it's own status" do + ThreadSpecs.get_status_of_current_thread.alive?.should == true end - it "returns false when the thread is finished" do - t = Thread.new { } - t.join - t.alive?.should == false + it "describes a running thread" do + ThreadSpecs.get_status_of_running_thread.alive?.should == true end + + it "describes a sleeping thread" do + ThreadSpecs.get_status_of_sleeping_thread.alive?.should == true + end + + it "describes a completed thread" do + ThreadSpecs.get_status_of_completed_thread.alive?.should == false + end + + it "describes a killed thread" do + ThreadSpecs.get_status_of_killed_thread.alive?.should == false + end + + it "describes a thread with an uncaught exception" do + ThreadSpecs.get_status_of_thread_with_uncaught_exception.alive?.should == false + end + + compliant_on(:ruby) do + it "reports aborting on a killed thread" do + ThreadSpecs.get_status_of_aborting_thread.alive?.should == true + end + end end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/critical_spec.rb;C695420 File: critical_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/critical_spec.rb;C695420 (server) 12/31/2008 10:48 AM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/critical_spec.rb;hangs @@ -1,7 +1,7 @@ require File.dirname(__FILE__) + '/../../spec_helper' require File.dirname(__FILE__) + '/fixtures/classes' -describe "Thread.critical" do +describe "Thread.critical=" do it "should be sticky" do ThreadSpecs.critical_should_be_false() @@ -62,12 +62,12 @@ end it "schedules other threads on sleep" do - ThreadSpecs.critical_thread_yields_to_main_thread { sleep } + ThreadSpecs.critical_thread_yields_to_main_thread(true) { sleep } end it "schedules other threads on Thread.stop" do # Note that Thread.Stop resets Thread.critical, whereas sleep does not - ThreadSpecs.critical_thread_yields_to_main_thread(true) { Thread.stop } + ThreadSpecs.critical_thread_yields_to_main_thread(false, true) { Thread.stop } end it "defers exit" do @@ -84,14 +84,14 @@ ScratchPad.recorded.should == nil end - not_compliant_on(:ironruby) do # requires green threads - it "is not reset if the critical thread is killed" do - critical_thread = ThreadSpecs.create_and_kill_critical_thread(true) - Thread.pass while critical_thread.status != false - Thread.critical.should == true + not_compliant_on(:ironruby) do # requires green threads so that another thread can be scheduled when the critical thread is killed + it "is not reset if the critical thread is killed" do + critical_thread = ThreadSpecs.create_and_kill_critical_thread(true) + Thread.pass while critical_thread.status != false + Thread.critical.should == true - Thread.critical = false - ThreadSpecs.critical_should_be_false() + Thread.critical = false + ThreadSpecs.critical_should_be_false() + end end - end end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/inspect_spec.rb;C638190 File: inspect_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/inspect_spec.rb;C638190 (server) 1/7/2009 12:01 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/inspect_spec.rb;hangs @@ -3,39 +3,32 @@ describe "Thread#inspect" do it "can check it's own status" do - t = Thread.new { Thread.current.inspect } - t.value.should include('run') + ThreadSpecs.get_status_of_current_thread.inspect.should include('run') end - + + it "describes a running thread" do + ThreadSpecs.get_status_of_running_thread.inspect.should include('run') + end + it "describes a sleeping thread" do - c = Channel.new - t = Thread.new do - sleep - c << Thread.current.inspect - sleep - end - - Thread.pass until t.status == 'sleep' - t.inspect.should include('sleep') - t.run - c.receive.should include('run') - Thread.pass until t.status == 'sleep' - t.inspect.should include('sleep') - t.run - t.join - t.inspect.should include('dead') + ThreadSpecs.get_status_of_sleeping_thread.inspect.should include('sleep') end - compliant_on(:ruby) do + it "describes a completed thread" do + ThreadSpecs.get_status_of_completed_thread.inspect.should include('dead') + end + + it "describes a killed thread" do + ThreadSpecs.get_status_of_killed_thread.inspect.should include('dead') + end + + it "describes a thread with an uncaught exception" do + ThreadSpecs.get_status_of_thread_with_uncaught_exception.inspect.should include('dead') + end + + compliant_on(:ruby) do it "reports aborting on a killed thread" do - c = Channel.new - t = Thread.new { c << Thread.current.inspect; Thread.stop } - c.receive.should include('run') - t.inspect.should include('sleep') - Thread.critical = true - t.kill - t.inspect.should include('aborting') - Thread.critical = false + ThreadSpecs.get_status_of_aborting_thread.inspect.should include('aborting') end end end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/raise_spec.rb;C695420 File: raise_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/raise_spec.rb;C695420 (server) 12/30/2008 10:42 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/raise_spec.rb;hangs @@ -70,7 +70,9 @@ rescue RuntimeError => e $!.message.should == "Kill the thread" e.message.should == "Kill the thread" - e.backtrace.select {|b| b =~ /looping_method/ }.size().should == 1 + frame_count = e.backtrace.select {|b| b =~ /looping_method/ }.size() + # There will be two occurences if "loop" is yielding to the block + (frame_count == 1 or frame_count == 2).should == true end 102 end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/run_spec.rb;C544557 File: run_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/run_spec.rb;C544557 (server) 1/6/2009 10:41 AM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/run_spec.rb;hangs @@ -1,2 +1,9 @@ require File.dirname(__FILE__) + '/../../spec_helper' -require File.dirname(__FILE__) + '/fixtures/classes' \ No newline at end of file +require File.dirname(__FILE__) + '/fixtures/classes' + +require File.dirname(__FILE__) + '/shared/wakeup' + +describe "Thread#run" do + it_behaves_like :thread_wakeup, :run +end + =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/status_spec.rb;C638190 File: status_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/status_spec.rb;C638190 (server) 1/7/2009 12:02 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/status_spec.rb;hangs @@ -2,15 +2,33 @@ require File.dirname(__FILE__) + '/fixtures/classes' describe "Thread#status" do - it "reports threads as running and returns false on correct termination" do - t = Thread.new { Thread.current.status } - t.value.should == 'run' - t.status.should == false + it "can check it's own status" do + ThreadSpecs.get_status_of_current_thread.status.should == 'run' end - it "returns nil if thread terminates with exception" do - t = Thread.new { raise "death to the unbelievers" } - lambda { t.join }.should raise_error(StandardError) - t.status.should == nil + it "describes a running thread" do + ThreadSpecs.get_status_of_running_thread.status.should == 'run' end + + it "describes a sleeping thread" do + ThreadSpecs.get_status_of_sleeping_thread.status.should == 'sleep' + end + + it "describes a completed thread" do + ThreadSpecs.get_status_of_completed_thread.status.should == false + end + + it "describes a killed thread" do + ThreadSpecs.get_status_of_killed_thread.status.should == false + end + + it "describes a thread with an uncaught exception" do + ThreadSpecs.get_status_of_thread_with_uncaught_exception.status.should == nil + end + + compliant_on(:ruby) do + it "reports aborting on a killed thread" do + ThreadSpecs.get_status_of_aborting_thread.status.should == 'aborting' + end + end end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/stop_spec.rb;C544557 File: stop_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/stop_spec.rb;C544557 (server) 1/7/2009 12:24 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/stop_spec.rb;hangs @@ -20,12 +20,33 @@ end describe "Thread#stop?" do - it "reports if a thread has stopped due to sleeping" do - t = Thread.new { Thread.stop } - Thread.pass until t.status == 'sleep' - t.stop?.should == true - t.run - t.join - t.stop?.should == true + it "can check it's own status" do + ThreadSpecs.get_status_of_current_thread.stop?.should == false end + + it "describes a running thread" do + ThreadSpecs.get_status_of_running_thread.stop?.should == false + end + + it "describes a sleeping thread" do + ThreadSpecs.get_status_of_sleeping_thread.stop?.should == true + end + + it "describes a completed thread" do + ThreadSpecs.get_status_of_completed_thread.stop?.should == true + end + + it "describes a killed thread" do + ThreadSpecs.get_status_of_killed_thread.stop?.should == true + end + + it "describes a thread with an uncaught exception" do + ThreadSpecs.get_status_of_thread_with_uncaught_exception.stop?.should == true + end + + compliant_on(:ruby) do + it "reports aborting on a killed thread" do + ThreadSpecs.get_status_of_aborting_thread.stop?.should == false + end + end end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/value_spec.rb;C544557 File: value_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/value_spec.rb;C544557 (server) 1/7/2009 12:49 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/value_spec.rb;hangs @@ -5,4 +5,14 @@ it "returns the result of the block" do Thread.new { 3 }.value.should == 3 end + + it "re-raises error for an uncaught exception" do + t = Thread.new { raise "Hello" } + lambda { t.value }.should raise_error(RuntimeError, "Hello") + end + + it "is false for a killed thread" do + t = Thread.new { Thread.current.exit } + t.value.should == false + end end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/wakeup_spec.rb;C682742 File: wakeup_spec.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/wakeup_spec.rb;C682742 (server) 1/6/2009 10:15 AM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/wakeup_spec.rb;hangs @@ -1,38 +1,7 @@ require File.dirname(__FILE__) + '/../../spec_helper' require File.dirname(__FILE__) + '/fixtures/classes' +require File.dirname(__FILE__) + '/shared/wakeup' describe "Thread#wakeup" do - it "does not result in a deadlock" do - c = Channel.new - t1 = Thread.new do - loop do - c << t1 - c << t1 - Thread.stop - end - end - t2 = Thread.new do - loop do - c << t2 - Thread.stop - end - end - - count1 = 0 - count2 = 0 - while(count1 < 10) do - if c.size > 0 - case c.receive - when t1; count1+=1 - when t2; count2+=1 - end - end - t1.wakeup - t2.wakeup - end - count1.should > count2 - - t1.kill - t2.kill - end + it_behaves_like :thread_wakeup, :wakeup end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/fixtures/classes.rb;C695420 File: classes.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/fixtures/classes.rb;C695420 (server) 1/5/2009 2:10 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/fixtures/classes.rb;hangs @@ -6,6 +6,88 @@ end module ThreadSpecs + class Status + attr_reader :thread, :inspect, :status + def initialize(thread) + @thread = thread + @alive = thread.alive? + @inspect = thread.inspect + @status = thread.status + @stop = thread.stop? + end + + def alive? + @alive + end + + def stop? + @stop + end + end + + def self.get_status_of_current_thread + Thread.new { Status.new(Thread.current) }.value + end + + def self.get_status_of_running_thread + @@exit_loop = false + t = Thread.new do + loop do + if @@exit_loop then + break + end + end + end + status = Status.new t + @@exit_loop = true + Thread.pass + t.join + status + end + + def self.get_status_of_completed_thread + t = Thread.new { } + t.join + Status.new t + end + + def self.get_status_of_sleeping_thread + t = Thread.new { sleep } + Thread.pass until t.status == 'sleep' + status = Status.new t + t.run + t.join + status + end + + def self.get_status_of_aborting_thread + t = Thread.new { sleep } + begin + Thread.critical = true + t.kill + Status.new t + ensure + Thread.critical = false + end + end + + def self.get_status_of_killed_thread + t = Thread.new { sleep } + Thread.pass until t.status == 'sleep' + t.kill + t.join + Status.new t + end + + def self.get_status_of_thread_with_uncaught_exception + t = Thread.new { raise "error" } + begin + t.join + rescue RuntimeError + end + Status.new t + end + def self.critical_should_be_false() # Create another thread to verify that it can call Thread.critical= t = Thread.new do @@ -46,14 +128,18 @@ end end - def self.MainThread1(critical_thread, isThreadStop) - # Thread.stop reset Thread.critical. Also, with native threads, the Thread.Stop may not have executed yet + def self.MainThread1(critical_thread, isThreadSleep, isThreadStop) + # Thread.stop resets Thread.critical. Also, with native threads, the Thread.Stop may not have executed yet # since the main thread will race with the critical thread if not isThreadStop Thread.critical.should == true end critical_thread[:thread_specs] = 101 - critical_thread.wakeup + if isThreadSleep or isThreadStop + # Thread#wakeup calls are not queued up. So we need to ensure that the thread is sleeping before calling wakeup + Thread.pass while critical_thread.status != "sleep" + critical_thread.wakeup + end end def self.MainThread2(critical_thread) @@ -62,7 +148,7 @@ Thread.critical.should == false end - def self.critical_thread_yields_to_main_thread(isThreadStop=false) + def self.critical_thread_yields_to_main_thread(isThreadSleep=false, isThreadStop=false) @@after_first_sleep = false critical_thread = Thread.new do @@ -70,7 +156,7 @@ CriticalThread1() Thread.main.wakeup yield - Thread.pass while @@after_first_sleep != true # Implementations using native thread need to ensure that the next statement does not see the first sleep + Thread.pass while @@after_first_sleep != true # Need to ensure that the next statement does not see the first sleep itself Thread.pass while Thread.main.status != "sleep" CriticalThread2(isThreadStop) Thread.main.wakeup @@ -78,7 +164,7 @@ sleep @@after_first_sleep = true - MainThread1(critical_thread, isThreadStop) + MainThread1(critical_thread, isThreadSleep, isThreadStop) sleep MainThread2(critical_thread) end =================================================================== edit: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/shared/exit.rb;C695420 File: exit.rb =================================================================== --- $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/shared/exit.rb;C695420 (server) 1/5/2009 2:45 PM +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/shared/exit.rb;hangs @@ -6,22 +6,37 @@ ScratchPad.record :after_sleep end Thread.pass while sleeping_thread.status != "sleep" - sleeping_thread.kill + sleeping_thread.send(@method) sleeping_thread.join + sleeping_thread.value.should == false ScratchPad.recorded.should == nil end it "kills current thread" do ScratchPad.clear() thread = Thread.new do - Thread.current.kill + Thread.current.send(@method) ScratchPad.record :after_sleep end - Thread.pass while thread.status != false thread.join + thread.status.should == false + thread.value.should == false ScratchPad.recorded.should == nil end + it "runs ensure clause" do + ScratchPad.clear + thread = Thread.new do + begin + Thread.current.send(@method) + ensure + ScratchPad.record :in_ensure_clause + end + end + thread.join + ScratchPad.recorded.should == :in_ensure_clause + 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 =================================================================== add: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/shared/wakeup.rb File: wakeup.rb =================================================================== --- [no source file] +++ Shelved Change: $/Merlin_External/Languages/IronRuby/mspec/rubyspec/1.8/core/thread/shared/wakeup.rb;hangs @@ -1,0 +1,50 @@ +describe :thread_wakeup, :shared => true do + it "is not queued" do + exit_loop = false + after_sleep1 = false + after_sleep2 = false + t = Thread.new do + loop do + if exit_loop == true + break + end + end + + sleep + after_sleep1 = true + + sleep + after_sleep2 = true + end + + 10.times { t.send(@method); Thread.pass } # These will all get ignored because the thread is not sleeping yet + + exit_loop = true + + Thread.pass while t.status != "sleep" + after_sleep1.should == false # t should be blocked on the first sleep + t.send(@method) + + Thread.pass while after_sleep1 != true + Thread.pass while t.status != "sleep" + after_sleep2.should == false # t should be blocked on the second sleep + t.send(@method) + Thread.pass while after_sleep2 != true + + t.join + end + + it "does not result in a deadlock" do + c = Channel.new + t = Thread.new do + 1000.times {Thread.stop } + end + + while(t.status != false) do + t.send(@method) + Thread.pass + end + + 1.should == 1 # test succeeds if we reach here + end +end ===================================================================