diff options
Diffstat (limited to 'spec/ruby/core/mutex')
| -rw-r--r-- | spec/ruby/core/mutex/lock_spec.rb | 92 | ||||
| -rw-r--r-- | spec/ruby/core/mutex/locked_spec.rb | 36 | ||||
| -rw-r--r-- | spec/ruby/core/mutex/owned_spec.rb | 53 | ||||
| -rw-r--r-- | spec/ruby/core/mutex/sleep_spec.rb | 111 | ||||
| -rw-r--r-- | spec/ruby/core/mutex/synchronize_spec.rb | 66 | ||||
| -rw-r--r-- | spec/ruby/core/mutex/try_lock_spec.rb | 32 | ||||
| -rw-r--r-- | spec/ruby/core/mutex/unlock_spec.rb | 38 |
7 files changed, 428 insertions, 0 deletions
diff --git a/spec/ruby/core/mutex/lock_spec.rb b/spec/ruby/core/mutex/lock_spec.rb new file mode 100644 index 0000000000..4fee29091a --- /dev/null +++ b/spec/ruby/core/mutex/lock_spec.rb @@ -0,0 +1,92 @@ +require_relative '../../spec_helper' + +describe "Mutex#lock" do + it "returns self" do + m = Mutex.new + m.lock.should == m + m.unlock + end + + it "blocks the caller if already locked" do + m = Mutex.new + m.lock + -> { m.lock }.should block_caller + end + + it "does not block the caller if not locked" do + m = Mutex.new + -> { m.lock }.should_not block_caller + end + + # Unable to find a specific ticket but behavior change may be + # related to this ML thread. + it "raises a deadlock ThreadError when used recursively" do + m = Mutex.new + m.lock + -> { + m.lock + }.should.raise(ThreadError, /deadlock/) + end + + it "raises a deadlock ThreadError when multiple fibers from the same thread try to lock" do + m = Mutex.new + + m.lock + f0 = Fiber.new do + m.lock + end + -> { f0.resume }.should.raise(ThreadError, /deadlock/) + + m.unlock + f1 = Fiber.new do + m.lock + Fiber.yield + end + f2 = Fiber.new do + m.lock + end + f1.resume + -> { f2.resume }.should.raise(ThreadError, /deadlock/) + end + + it "does not raise deadlock if a fiber's attempt to lock was interrupted" do + lock = Mutex.new + main = Thread.current + + t2 = nil + t1 = Thread.new do + loop do + # interrupt fiber below looping on synchronize + sleep 0.01 + t2.raise if t2 + end + end + + # loop ten times to try to handle the interrupt during synchronize + t2 = Thread.new do + 10.times do + Fiber.new do + begin + loop { lock.synchronize {} } + rescue RuntimeError + end + end.resume + + Fiber.new do + -> do + lock.synchronize {} + end.should_not.raise(ThreadError) + end.resume + rescue RuntimeError + retry + end + end + t2.join + ensure + t1.kill rescue nil + t2.kill rescue nil + + t1.join + t2.join + end +end diff --git a/spec/ruby/core/mutex/locked_spec.rb b/spec/ruby/core/mutex/locked_spec.rb new file mode 100644 index 0000000000..1818cdb4f3 --- /dev/null +++ b/spec/ruby/core/mutex/locked_spec.rb @@ -0,0 +1,36 @@ +require_relative '../../spec_helper' + +describe "Mutex#locked?" do + it "returns true if locked" do + m = Mutex.new + m.lock + m.locked?.should == true + end + + it "returns false if unlocked" do + m = Mutex.new + m.locked?.should == false + end + + it "returns the status of the lock" do + m1 = Mutex.new + m2 = Mutex.new + + m2.lock # hold th with only m1 locked + m1_locked = false + + th = Thread.new do + m1.lock + m1_locked = true + m2.lock + end + + Thread.pass until m1_locked + + m1.locked?.should == true + m2.unlock # release th + th.join + # A Thread releases its locks upon termination + m1.locked?.should == false + end +end diff --git a/spec/ruby/core/mutex/owned_spec.rb b/spec/ruby/core/mutex/owned_spec.rb new file mode 100644 index 0000000000..ea7d5faf1c --- /dev/null +++ b/spec/ruby/core/mutex/owned_spec.rb @@ -0,0 +1,53 @@ +require_relative '../../spec_helper' + +describe "Mutex#owned?" do + describe "when unlocked" do + it "returns false" do + m = Mutex.new + m.owned?.should == false + end + end + + describe "when locked by the current thread" do + it "returns true" do + m = Mutex.new + m.lock + m.owned?.should == true + end + end + + describe "when locked by another thread" do + before :each do + @checked = false + end + + after :each do + @checked = true + @th.join + end + + it "returns false" do + m = Mutex.new + locked = false + + @th = Thread.new do + m.lock + locked = true + Thread.pass until @checked + end + + Thread.pass until locked + m.owned?.should == false + end + end + + it "is held per Fiber" do + m = Mutex.new + m.lock + + Fiber.new do + m.locked?.should == true + m.owned?.should == false + end.resume + end +end diff --git a/spec/ruby/core/mutex/sleep_spec.rb b/spec/ruby/core/mutex/sleep_spec.rb new file mode 100644 index 0000000000..71b089d251 --- /dev/null +++ b/spec/ruby/core/mutex/sleep_spec.rb @@ -0,0 +1,111 @@ +require_relative '../../spec_helper' + +describe "Mutex#sleep" do + describe "when not locked by the current thread" do + it "raises a ThreadError" do + m = Mutex.new + -> { m.sleep }.should.raise(ThreadError) + end + + it "raises an ArgumentError if passed a negative duration" do + m = Mutex.new + -> { m.sleep(-0.1) }.should.raise(ArgumentError) + -> { m.sleep(-1) }.should.raise(ArgumentError) + end + end + + it "raises an ArgumentError if passed a negative duration" do + m = Mutex.new + m.lock + -> { m.sleep(-0.1) }.should.raise(ArgumentError) + -> { m.sleep(-1) }.should.raise(ArgumentError) + end + + it "pauses execution for approximately the duration requested" do + m = Mutex.new + m.lock + duration = 0.001 + start = Process.clock_gettime(Process::CLOCK_MONOTONIC) + m.sleep duration + now = Process.clock_gettime(Process::CLOCK_MONOTONIC) + (now - start).should >= 0 + (now - start).should < (duration + TIME_TOLERANCE) + end + + it "unlocks the mutex while sleeping" do + m = Mutex.new + locked = false + th = Thread.new { m.lock; locked = true; m.sleep } + Thread.pass until locked + Thread.pass until th.stop? + m.locked?.should == false + th.run + th.join + end + + it "relocks the mutex when woken" do + m = Mutex.new + m.lock + m.sleep(0.001) + m.locked?.should == true + end + + it "relocks the mutex when woken by an exception being raised" do + m = Mutex.new + locked = false + th = Thread.new do + m.lock + locked = true + begin + m.sleep + rescue Exception + m.locked? + end + end + Thread.pass until locked + Thread.pass until th.stop? + th.raise(Exception) + th.value.should == true + end + + it "returns the rounded number of seconds asleep" do + m = Mutex.new + locked = false + th = Thread.start do + m.lock + locked = true + m.sleep + end + Thread.pass until locked + Thread.pass until th.stop? + th.wakeup + th.value.should.is_a?(Integer) + end + + it "accepts nil as a sleep duration" do + m = Mutex.new + -> { + m.lock + m.sleep(nil) + }.should block_caller + end + + it "wakes up when requesting sleep times near or equal to zero" do + times = [] + val = 1 + + # power of two divisor so we eventually get near zero + loop do + val = val / 16.0 + times << val + break if val == 0.0 + end + + m = Mutex.new + m.lock + times.each do |time| + # just testing that sleep completes + -> {m.sleep(time)}.should_not.raise + end + end +end diff --git a/spec/ruby/core/mutex/synchronize_spec.rb b/spec/ruby/core/mutex/synchronize_spec.rb new file mode 100644 index 0000000000..823f29a634 --- /dev/null +++ b/spec/ruby/core/mutex/synchronize_spec.rb @@ -0,0 +1,66 @@ +require_relative '../../spec_helper' + +describe "Mutex#synchronize" do + it "wraps the lock/unlock pair in an ensure" do + m1 = Mutex.new + m2 = Mutex.new + m2.lock + synchronized = false + + th = Thread.new do + -> do + m1.synchronize do + synchronized = true + m2.lock + raise Exception + end + end.should.raise(Exception) + end + + Thread.pass until synchronized + + m1.locked?.should == true + m2.unlock + th.join + m1.locked?.should == false + end + + it "blocks the caller if already locked" do + m = Mutex.new + m.lock + -> { m.synchronize { } }.should block_caller + end + + it "does not block the caller if not locked" do + m = Mutex.new + -> { m.synchronize { } }.should_not block_caller + end + + it "blocks the caller if another thread is also in the synchronize block" do + m = Mutex.new + q1 = Queue.new + q2 = Queue.new + + t = Thread.new { + m.synchronize { + q1.push :ready + q2.pop + } + } + + q1.pop.should == :ready + + -> { m.synchronize { } }.should block_caller + + q2.push :done + t.join + end + + it "is not recursive" do + m = Mutex.new + + m.synchronize do + -> { m.synchronize { } }.should.raise(ThreadError) + end + end +end diff --git a/spec/ruby/core/mutex/try_lock_spec.rb b/spec/ruby/core/mutex/try_lock_spec.rb new file mode 100644 index 0000000000..1da0735d6a --- /dev/null +++ b/spec/ruby/core/mutex/try_lock_spec.rb @@ -0,0 +1,32 @@ +require_relative '../../spec_helper' + +describe "Mutex#try_lock" do + describe "when unlocked" do + it "returns true" do + m = Mutex.new + m.try_lock.should == true + end + + it "locks the mutex" do + m = Mutex.new + m.try_lock + m.locked?.should == true + end + end + + describe "when locked by the current thread" do + it "returns false" do + m = Mutex.new + m.lock + m.try_lock.should == false + end + end + + describe "when locked by another thread" do + it "returns false" do + m = Mutex.new + m.lock + Thread.new { m.try_lock }.value.should == false + end + end +end diff --git a/spec/ruby/core/mutex/unlock_spec.rb b/spec/ruby/core/mutex/unlock_spec.rb new file mode 100644 index 0000000000..ed493cf84a --- /dev/null +++ b/spec/ruby/core/mutex/unlock_spec.rb @@ -0,0 +1,38 @@ +require_relative '../../spec_helper' + +describe "Mutex#unlock" do + it "raises ThreadError unless Mutex is locked" do + mutex = Mutex.new + -> { mutex.unlock }.should.raise(ThreadError) + end + + it "raises ThreadError unless thread owns Mutex" do + mutex = Mutex.new + wait = Mutex.new + wait.lock + th = Thread.new do + mutex.lock + wait.lock + end + + # avoid race on mutex.lock + Thread.pass until mutex.locked? + Thread.pass until th.stop? + + -> { mutex.unlock }.should.raise(ThreadError) + + wait.unlock + th.join + end + + it "raises ThreadError if previously locking thread is gone" do + mutex = Mutex.new + th = Thread.new do + mutex.lock + end + + th.join + + -> { mutex.unlock }.should.raise(ThreadError) + end +end |
