strand 0.1.0 → 0.2.0.rc0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +3 -0
- data/CHANGELOG +13 -0
- data/Gemfile +3 -15
- data/LICENSE.txt +1 -0
- data/README.rdoc +12 -19
- data/Rakefile +10 -57
- data/lib/strand.rb +151 -126
- data/lib/strand/atc.rb +1 -1
- data/lib/strand/em/condition_variable.rb +57 -0
- data/lib/strand/em/mutex.rb +63 -0
- data/lib/strand/em/queue.rb +84 -0
- data/lib/strand/em/thread.rb +305 -0
- data/lib/strand/monitor.rb +193 -0
- data/lib/strand/version.rb +3 -0
- data/spec/spec_helper.rb +9 -5
- data/spec/strand/alive.rb +62 -0
- data/spec/strand/condition_variable.rb +10 -0
- data/spec/strand/condition_variable/broadcast.rb +61 -0
- data/spec/strand/condition_variable/signal.rb +62 -0
- data/spec/strand/condition_variable/wait.rb +20 -0
- data/spec/strand/current.rb +15 -0
- data/spec/strand/exit.rb +148 -0
- data/spec/strand/join.rb +60 -0
- data/spec/strand/local_storage.rb +98 -0
- data/spec/strand/mutex.rb +244 -0
- data/spec/strand/pass.rb +9 -0
- data/spec/strand/queue.rb +124 -0
- data/spec/strand/raise.rb +142 -0
- data/spec/strand/run.rb +5 -0
- data/spec/strand/shared.rb +14 -0
- data/spec/strand/sleep.rb +51 -0
- data/spec/strand/status.rb +44 -0
- data/spec/strand/stop.rb +58 -0
- data/spec/strand/strand.rb +32 -0
- data/spec/strand/value.rb +39 -0
- data/spec/strand/wakeup.rb +60 -0
- data/spec/strand_spec.rb +51 -0
- data/spec/support/fixtures.rb +305 -0
- data/spec/support/scratch.rb +17 -0
- data/spec/thread_spec.rb +20 -0
- data/strand.gemspec +23 -0
- metadata +72 -58
- data/Gemfile.lock +0 -40
- data/lib/strand/condition_variable.rb +0 -78
- data/spec/condition_variable_spec.rb +0 -82
- data/test/helper.rb +0 -30
- data/test/test_strand.rb +0 -121
data/spec/strand/exit.rb
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
|
2
|
+
shared_examples_for "Strand#exit" do
|
3
|
+
|
4
|
+
context :exit do
|
5
|
+
it "kills sleeping strand" do
|
6
|
+
sleeping_strand = Strand.new do
|
7
|
+
Strand.sleep
|
8
|
+
ScratchPad.record :after_sleep
|
9
|
+
end
|
10
|
+
sleeping_strand.exit
|
11
|
+
sleeping_strand.join
|
12
|
+
ScratchPad.recorded.should == nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it "kills current strand" do
|
16
|
+
strand = Strand.new do
|
17
|
+
Strand.current.kill
|
18
|
+
ScratchPad.record :after_sleep
|
19
|
+
end
|
20
|
+
strand.join
|
21
|
+
ScratchPad.recorded.should == nil
|
22
|
+
end
|
23
|
+
|
24
|
+
it "runs ensure clause" do
|
25
|
+
strand = StrandSpecs.dying_strand_ensures(:kill) { ScratchPad.record :in_ensure_clause }
|
26
|
+
strand.join
|
27
|
+
ScratchPad.recorded.should == :in_ensure_clause
|
28
|
+
end
|
29
|
+
|
30
|
+
quarantine! do
|
31
|
+
# >1.9.2 has this as undefined
|
32
|
+
it "runs nested ensure clauses" do
|
33
|
+
ScratchPad.record []
|
34
|
+
outer = Strand.new do
|
35
|
+
begin
|
36
|
+
inner = Strand.new do
|
37
|
+
begin
|
38
|
+
Strand.sleep
|
39
|
+
ensure
|
40
|
+
ScratchPad << :inner_ensure_clause
|
41
|
+
end
|
42
|
+
end
|
43
|
+
Strand.sleep
|
44
|
+
ensure
|
45
|
+
ScratchPad << :outer_ensure_clause
|
46
|
+
Strand.pass while inner.status and inner.status != "sleep"
|
47
|
+
# exit is private for thread, but not for em::thread
|
48
|
+
inner.terminate
|
49
|
+
inner.join
|
50
|
+
end
|
51
|
+
end
|
52
|
+
outer.terminate
|
53
|
+
outer.join
|
54
|
+
ScratchPad.recorded.should include(:inner_ensure_clause)
|
55
|
+
ScratchPad.recorded.should include(:outer_ensure_clause)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
it "does not set $!" do
|
59
|
+
strand = StrandSpecs.dying_strand_ensures(:kill) { ScratchPad.record $! }
|
60
|
+
strand.join
|
61
|
+
ScratchPad.recorded.should == nil
|
62
|
+
end
|
63
|
+
|
64
|
+
it "cannot be rescued" do
|
65
|
+
strand = Strand.new do
|
66
|
+
begin
|
67
|
+
Strand.current.kill
|
68
|
+
rescue Exception
|
69
|
+
ScratchPad.record :in_rescue
|
70
|
+
end
|
71
|
+
ScratchPad.record :end_of_strand_block
|
72
|
+
end
|
73
|
+
|
74
|
+
strand.join
|
75
|
+
ScratchPad.recorded.should == nil
|
76
|
+
end
|
77
|
+
|
78
|
+
it "killing dying running does nothing" do
|
79
|
+
# Not applicable for Strands (there can be no "running" status)
|
80
|
+
end
|
81
|
+
|
82
|
+
quarantine! do
|
83
|
+
|
84
|
+
it "propogates inner exception to Strand.join if there is an outer ensure clause" do
|
85
|
+
strand = StrandSpecs.dying_strand_with_outer_ensure(:kill) { }
|
86
|
+
lambda { strand.join }.should raise_error(RuntimeError, "In dying strand")
|
87
|
+
end
|
88
|
+
|
89
|
+
it "runs all outer ensure clauses even if inner ensure clause raises exception" do
|
90
|
+
strand = StrandSpecs.join_dying_strand_with_outer_ensure(:kill) { ScratchPad.record :in_outer_ensure_clause }
|
91
|
+
ScratchPad.recorded.should == :in_outer_ensure_clause
|
92
|
+
end
|
93
|
+
|
94
|
+
it "sets $! in outer ensure clause if inner ensure clause raises exception" do
|
95
|
+
strand = StrandSpecs.join_dying_strand_with_outer_ensure(:kill) { ScratchPad.record $! }
|
96
|
+
ScratchPad.recorded.to_s.should == "In dying strand"
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
it "can be rescued by outer rescue clause when inner ensure clause raises exception" do
|
101
|
+
strand = Strand.new do
|
102
|
+
begin
|
103
|
+
begin
|
104
|
+
Strand.current.send(:kill)
|
105
|
+
ensure
|
106
|
+
raise "In dying strand"
|
107
|
+
end
|
108
|
+
rescue Exception
|
109
|
+
ScratchPad.record $!
|
110
|
+
end
|
111
|
+
:end_of_strand_block
|
112
|
+
end
|
113
|
+
|
114
|
+
strand.value.should == :end_of_strand_block
|
115
|
+
ScratchPad.recorded.to_s.should == "In dying strand"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "is deferred if ensure clause does Strand.stop" do
|
119
|
+
StrandSpecs.wakeup_dying_sleeping_strand(:kill) { Strand.stop; ScratchPad.record :after_sleep }
|
120
|
+
ScratchPad.recorded.should == :after_sleep
|
121
|
+
end
|
122
|
+
|
123
|
+
# Hangs on 1.8.6.114 OS X, possibly also on Linux
|
124
|
+
# FIX: There is no such thing as not_compliant_on(:ruby)!!!
|
125
|
+
quarantine! do
|
126
|
+
not_compliant_on(:ruby) do # Doing a sleep in the ensure block hangs the process
|
127
|
+
it "is deferred if ensure clause sleeps" do
|
128
|
+
StrandSpecs.wakeup_dying_sleeping_strand(:kill) { sleep; ScratchPad.record :after_sleep }
|
129
|
+
ScratchPad.recorded.should == :after_sleep
|
130
|
+
end
|
131
|
+
end
|
132
|
+
|
133
|
+
# This case occurred in JRuby where native strands are used to provide
|
134
|
+
# the same behavior as MRI green strands. Key to this issue was the fact
|
135
|
+
# that the strand which called #exit in its block was also being explicitly
|
136
|
+
# sent #join from outside the strand. The 100.times provides a certain
|
137
|
+
# probability that the deadlock will occur. It was sufficient to reliably
|
138
|
+
# reproduce the deadlock in JRuby.
|
139
|
+
it "does not deadlock when called from within the strand while being joined from without" do
|
140
|
+
100.times do
|
141
|
+
t = Strand.new { Strand.stop; Strand.current.send(:kill) }
|
142
|
+
t.wakeup.should == t
|
143
|
+
t.join.should == t
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
data/spec/strand/join.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
shared_examples_for "Strand#join" do
|
2
|
+
|
3
|
+
context :join do
|
4
|
+
it "returns the strand when it is finished" do
|
5
|
+
t = Strand.new {}
|
6
|
+
t.join.should equal(t)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns the strand when it is finished when given a timeout" do
|
10
|
+
t = Strand.new {}
|
11
|
+
t.join
|
12
|
+
t.join(0).should equal(t)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "returns nil if it is not finished when given a timeout" do
|
16
|
+
c = Strand::Queue.new
|
17
|
+
t = Strand.new { c.shift }
|
18
|
+
begin
|
19
|
+
t.join(0).should == nil
|
20
|
+
ensure
|
21
|
+
c << true
|
22
|
+
end
|
23
|
+
t.join.should == t
|
24
|
+
end
|
25
|
+
|
26
|
+
it "accepts a floating point timeout length" do
|
27
|
+
c = Strand::Queue.new
|
28
|
+
t = Strand.new { c.shift }
|
29
|
+
begin
|
30
|
+
t.join(0.01).should == nil
|
31
|
+
ensure
|
32
|
+
c << true
|
33
|
+
end
|
34
|
+
t.join.should == t
|
35
|
+
end
|
36
|
+
|
37
|
+
it "raises any exceptions encountered in the strand body" do
|
38
|
+
t = Strand.new { raise NotImplementedError.new("Just kidding") }
|
39
|
+
lambda { t.join }.should raise_error(NotImplementedError)
|
40
|
+
end
|
41
|
+
|
42
|
+
it "returns the dead strand" do
|
43
|
+
t = Strand.new { Strand.current.kill }
|
44
|
+
t.join.should equal(t)
|
45
|
+
end
|
46
|
+
|
47
|
+
quarantine! do
|
48
|
+
# This was pre 1.9 behaviour
|
49
|
+
it "returns the dead strand even if an uncaught exception is thrown from ensure block" do
|
50
|
+
t = StrandSpecs.dying_strand_ensures { raise "In dying strand" }
|
51
|
+
t.join.should equal(t)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
it "raises any uncaught exception encountered in ensure block" do
|
56
|
+
t = StrandSpecs.dying_strand_ensures { raise NotImplementedError.new("Just kidding") }
|
57
|
+
lambda { t.join }.should raise_error(NotImplementedError)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,98 @@
|
|
1
|
+
|
2
|
+
shared_examples_for "strand local storage" do
|
3
|
+
|
4
|
+
context "[]" do
|
5
|
+
it "gives access to strand local values" do
|
6
|
+
th = Strand.new do
|
7
|
+
Strand.current[:value] = 5
|
8
|
+
end
|
9
|
+
th.join
|
10
|
+
th[:value].should == 5
|
11
|
+
Strand.current[:value].should == nil
|
12
|
+
end
|
13
|
+
|
14
|
+
it "is not shared across strands" do
|
15
|
+
t1 = Strand.new do
|
16
|
+
Strand.current[:value] = 1
|
17
|
+
end
|
18
|
+
t2 = Strand.new do
|
19
|
+
Strand.current[:value] = 2
|
20
|
+
end
|
21
|
+
[t1,t2].each {|x| x.join}
|
22
|
+
t1[:value].should == 1
|
23
|
+
t2[:value].should == 2
|
24
|
+
end
|
25
|
+
|
26
|
+
it "is accessable using strings or symbols" do
|
27
|
+
t1 = Strand.new do
|
28
|
+
Strand.current[:value] = 1
|
29
|
+
end
|
30
|
+
t2 = Strand.new do
|
31
|
+
Strand.current["value"] = 2
|
32
|
+
end
|
33
|
+
[t1,t2].each {|x| x.join}
|
34
|
+
t1[:value].should == 1
|
35
|
+
t1["value"].should == 1
|
36
|
+
t2[:value].should == 2
|
37
|
+
t2["value"].should == 2
|
38
|
+
end
|
39
|
+
|
40
|
+
it "raises exceptions on the wrong type of keys" do
|
41
|
+
lambda { Strand.current[nil] }.should raise_error(TypeError)
|
42
|
+
lambda { Strand.current[5] }.should raise_error(TypeError)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
context "[]=" do
|
47
|
+
it "raises exceptions on the wrong type of keys" do
|
48
|
+
lambda { Strand.current[nil] = true }.should raise_error(TypeError)
|
49
|
+
lambda { Strand.current[5] = true }.should raise_error(TypeError)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context "keys" do
|
54
|
+
it "returns an array of the names of the thread-local variables as symbols" do
|
55
|
+
th = Strand.new do
|
56
|
+
Strand.current["cat"] = 'woof'
|
57
|
+
Strand.current[:cat] = 'meow'
|
58
|
+
Strand.current[:dog] = 'woof'
|
59
|
+
end
|
60
|
+
th.join
|
61
|
+
th.keys.sort_by {|x| x.to_s}.should == [:cat,:dog]
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
context "key?" do
|
66
|
+
before :each do
|
67
|
+
@th = Strand.new do
|
68
|
+
Strand.current[:oliver] = "a"
|
69
|
+
end
|
70
|
+
@th.join
|
71
|
+
end
|
72
|
+
|
73
|
+
it "tests for existance of strand local variables using symbols or strings" do
|
74
|
+
@th.key?(:oliver).should == true
|
75
|
+
@th.key?("oliver").should == true
|
76
|
+
@th.key?(:stanley).should == false
|
77
|
+
@th.key?(:stanley.to_s).should == false
|
78
|
+
end
|
79
|
+
|
80
|
+
quarantine! do
|
81
|
+
ruby_version_is ""..."1.9" do
|
82
|
+
it "raises exceptions on the wrong type of keys" do
|
83
|
+
lambda { Strand.current.key? nil }.should raise_error(TypeError)
|
84
|
+
lambda { Strand.current.key? 5 }.should raise_error(ArgumentError)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
end
|
89
|
+
|
90
|
+
# Ruby spec says 1.9 should raise TypeError
|
91
|
+
it "raises exceptions on the wrong type of keys" do
|
92
|
+
lambda { Strand.current.key? nil }.should raise_error(TypeError)
|
93
|
+
lambda { Strand.current.key? 5 }.should raise_error(TypeError)
|
94
|
+
end
|
95
|
+
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
@@ -0,0 +1,244 @@
|
|
1
|
+
|
2
|
+
# This spec derived from rubyspec for mutex
|
3
|
+
shared_examples "a mutex" do
|
4
|
+
|
5
|
+
context :lock do
|
6
|
+
|
7
|
+
it "returns self" do
|
8
|
+
m = Strand::Mutex.new
|
9
|
+
m.lock.should == m
|
10
|
+
m.unlock
|
11
|
+
end
|
12
|
+
|
13
|
+
it "waits if the lock is not available" do
|
14
|
+
m = Strand::Mutex.new
|
15
|
+
|
16
|
+
status = nil
|
17
|
+
m.lock
|
18
|
+
|
19
|
+
s = Strand.new do
|
20
|
+
m.lock
|
21
|
+
status = :after_lock
|
22
|
+
end
|
23
|
+
|
24
|
+
status.should be_nil
|
25
|
+
m.unlock
|
26
|
+
s.join
|
27
|
+
status.should == :after_lock
|
28
|
+
end
|
29
|
+
|
30
|
+
#GG there's no test for this in rubyspec
|
31
|
+
# but there is a test below for locked?
|
32
|
+
it "acquires a lock previously held by a dead Fiber" do
|
33
|
+
m = Strand::Mutex.new
|
34
|
+
|
35
|
+
m.lock
|
36
|
+
|
37
|
+
# s1 acquires the lock but does not release it before
|
38
|
+
# it dies, something needs to resume s2
|
39
|
+
# we
|
40
|
+
s1 = Strand.new { m.lock; Strand.pass }
|
41
|
+
s2 = Strand.new { m.lock; }
|
42
|
+
|
43
|
+
m.unlock
|
44
|
+
s2.join
|
45
|
+
end
|
46
|
+
|
47
|
+
it "acquires a lock previously held by a killed Fiber" do
|
48
|
+
m = Strand::Mutex.new
|
49
|
+
m.lock
|
50
|
+
|
51
|
+
s1 = Strand.new { m.lock; Strand.sleep }
|
52
|
+
s2 = Strand.new { m.lock; }
|
53
|
+
|
54
|
+
m.unlock
|
55
|
+
s1.kill
|
56
|
+
s2.join
|
57
|
+
end
|
58
|
+
|
59
|
+
it "acquires a lock in a queue behind a killed Fiber" do
|
60
|
+
|
61
|
+
m = Strand::Mutex.new
|
62
|
+
m.lock
|
63
|
+
s1 = Strand.new { m.lock }
|
64
|
+
s2 = Strand.new { m.lock }
|
65
|
+
s3 = Strand.new { m.lock }
|
66
|
+
|
67
|
+
s2.kill
|
68
|
+
m.unlock
|
69
|
+
s3.join
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
context :unlock do
|
75
|
+
it "raises StrandError unless Mutex is locked" do
|
76
|
+
mutex = Strand::Mutex.new
|
77
|
+
lambda { mutex.unlock }.should raise_error(strand_exception)
|
78
|
+
end
|
79
|
+
|
80
|
+
it "raises StrandError unless thread owns Mutex" do
|
81
|
+
mutex = Strand::Mutex.new
|
82
|
+
wait = Strand::Mutex.new
|
83
|
+
wait.lock
|
84
|
+
|
85
|
+
s = Strand.new do
|
86
|
+
mutex.lock
|
87
|
+
wait.lock
|
88
|
+
end
|
89
|
+
|
90
|
+
lambda { mutex.unlock }.should raise_error(strand_exception)
|
91
|
+
|
92
|
+
wait.unlock
|
93
|
+
s.join
|
94
|
+
end
|
95
|
+
|
96
|
+
it "raises StrandError if previously locking thread is gone" do
|
97
|
+
mutex = Strand::Mutex.new
|
98
|
+
s = Strand.new do
|
99
|
+
mutex.lock
|
100
|
+
end
|
101
|
+
|
102
|
+
s.join
|
103
|
+
#TODO This doesn't make sense, because it would raise error
|
104
|
+
# as per above test
|
105
|
+
lambda { mutex.unlock }.should raise_error(strand_exception)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
context :locked do
|
110
|
+
it "returns true if locked" do
|
111
|
+
m = Strand::Mutex.new
|
112
|
+
m.lock
|
113
|
+
m.locked?.should be_true
|
114
|
+
end
|
115
|
+
|
116
|
+
it "returns false if unlocked" do
|
117
|
+
m = Strand::Mutex.new
|
118
|
+
m.locked?.should be_false
|
119
|
+
end
|
120
|
+
|
121
|
+
it "returns the status of the lock" do
|
122
|
+
|
123
|
+
m1 = Strand::Mutex.new
|
124
|
+
m2 = Strand::Mutex.new
|
125
|
+
|
126
|
+
m2.lock # hold s with only m1 locked
|
127
|
+
|
128
|
+
s = Strand.new do
|
129
|
+
m1.lock
|
130
|
+
m2.lock
|
131
|
+
end
|
132
|
+
Strand.pass while s.status and s.status != "sleep"
|
133
|
+
m1.locked?.should be_true
|
134
|
+
m2.unlock # release s
|
135
|
+
s.join
|
136
|
+
#TODO GG implies that I should be able to get m1
|
137
|
+
# but there is no test for this case!
|
138
|
+
m1.locked?.should be_false
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
context :try_lock do
|
143
|
+
it "locks the mutex if it can" do
|
144
|
+
m = Strand::Mutex.new
|
145
|
+
m.try_lock
|
146
|
+
|
147
|
+
m.locked?.should be_true
|
148
|
+
lambda { m.try_lock.should be_false }.should_not raise_error(strand_exception)
|
149
|
+
end
|
150
|
+
|
151
|
+
it "returns false if lock can not be aquired immediately" do
|
152
|
+
m1 = Strand::Mutex.new
|
153
|
+
m2 = Strand::Mutex.new
|
154
|
+
|
155
|
+
m2.lock
|
156
|
+
s = Strand.new do
|
157
|
+
m1.lock
|
158
|
+
m2.lock
|
159
|
+
end
|
160
|
+
Strand.pass while s.status and s.status != "sleep"
|
161
|
+
# s owns m1 so try_lock should return false
|
162
|
+
m1.try_lock.should be_false
|
163
|
+
m2.unlock
|
164
|
+
s.join
|
165
|
+
# once th is finished m1 should be released
|
166
|
+
m1.try_lock.should be_true
|
167
|
+
end
|
168
|
+
end
|
169
|
+
context :synchronize do
|
170
|
+
it "wraps the lock/unlock pair in an ensure" do
|
171
|
+
m1 = Strand::Mutex.new
|
172
|
+
m2 = Strand::Mutex.new
|
173
|
+
m2.lock
|
174
|
+
|
175
|
+
s = Strand.new do
|
176
|
+
lambda do
|
177
|
+
m1.synchronize do
|
178
|
+
m2.lock
|
179
|
+
raise Exception
|
180
|
+
end
|
181
|
+
end.should raise_error(Exception)
|
182
|
+
end
|
183
|
+
|
184
|
+
Strand.pass while s.status and s.status != "sleep"
|
185
|
+
m1.locked?.should be_true
|
186
|
+
m2.unlock
|
187
|
+
s.join
|
188
|
+
m1.locked?.should be_false
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
context :sleep do
|
193
|
+
it "raises StrandError if not locked by the current thread" do
|
194
|
+
m = Strand::Mutex.new
|
195
|
+
lambda { m.sleep }.should raise_error(strand_exception)
|
196
|
+
end
|
197
|
+
|
198
|
+
it "pauses execution for approximately the duration requested" do
|
199
|
+
m = Strand::Mutex.new
|
200
|
+
m.lock
|
201
|
+
duration = 0.1
|
202
|
+
start = Time.now
|
203
|
+
m.sleep duration
|
204
|
+
(Time.now - start).should be_within(0.1).of(duration)
|
205
|
+
end
|
206
|
+
|
207
|
+
it "unlocks the mutex while sleeping" do
|
208
|
+
m = Strand::Mutex.new
|
209
|
+
s = Strand.new { m.lock; m.sleep }
|
210
|
+
Strand.pass while s.status and s.status != "sleep"
|
211
|
+
m.locked?.should be_false
|
212
|
+
s.run
|
213
|
+
s.join
|
214
|
+
end
|
215
|
+
|
216
|
+
it "relocks the mutex when woken" do
|
217
|
+
m = Strand::Mutex.new
|
218
|
+
m.lock
|
219
|
+
m.sleep(0.01)
|
220
|
+
m.locked?.should be_true
|
221
|
+
end
|
222
|
+
|
223
|
+
it "relocks the mutex when woken by an exception being raised" do
|
224
|
+
m = Strand::Mutex.new
|
225
|
+
s = Strand.new do
|
226
|
+
m.lock
|
227
|
+
begin
|
228
|
+
m.sleep
|
229
|
+
rescue Exception
|
230
|
+
m.locked?
|
231
|
+
end
|
232
|
+
end
|
233
|
+
Strand.pass while s.status and s.status != "sleep"
|
234
|
+
s.raise(Exception)
|
235
|
+
s.value.should be_true
|
236
|
+
end
|
237
|
+
|
238
|
+
it "returns the rounded number of seconds asleep" do
|
239
|
+
m = Strand::Mutex.new
|
240
|
+
m.lock
|
241
|
+
m.sleep(0.01).should be_kind_of(Integer)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|