summaryrefslogtreecommitdiff
path: root/spec/ruby/core/mutex
diff options
context:
space:
mode:
Diffstat (limited to 'spec/ruby/core/mutex')
-rw-r--r--spec/ruby/core/mutex/lock_spec.rb92
-rw-r--r--spec/ruby/core/mutex/locked_spec.rb36
-rw-r--r--spec/ruby/core/mutex/owned_spec.rb53
-rw-r--r--spec/ruby/core/mutex/sleep_spec.rb111
-rw-r--r--spec/ruby/core/mutex/synchronize_spec.rb66
-rw-r--r--spec/ruby/core/mutex/try_lock_spec.rb32
-rw-r--r--spec/ruby/core/mutex/unlock_spec.rb38
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