strand 0.1.0 → 0.2.0.rc0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|