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.
Files changed (47) hide show
  1. data/.gitignore +3 -0
  2. data/CHANGELOG +13 -0
  3. data/Gemfile +3 -15
  4. data/LICENSE.txt +1 -0
  5. data/README.rdoc +12 -19
  6. data/Rakefile +10 -57
  7. data/lib/strand.rb +151 -126
  8. data/lib/strand/atc.rb +1 -1
  9. data/lib/strand/em/condition_variable.rb +57 -0
  10. data/lib/strand/em/mutex.rb +63 -0
  11. data/lib/strand/em/queue.rb +84 -0
  12. data/lib/strand/em/thread.rb +305 -0
  13. data/lib/strand/monitor.rb +193 -0
  14. data/lib/strand/version.rb +3 -0
  15. data/spec/spec_helper.rb +9 -5
  16. data/spec/strand/alive.rb +62 -0
  17. data/spec/strand/condition_variable.rb +10 -0
  18. data/spec/strand/condition_variable/broadcast.rb +61 -0
  19. data/spec/strand/condition_variable/signal.rb +62 -0
  20. data/spec/strand/condition_variable/wait.rb +20 -0
  21. data/spec/strand/current.rb +15 -0
  22. data/spec/strand/exit.rb +148 -0
  23. data/spec/strand/join.rb +60 -0
  24. data/spec/strand/local_storage.rb +98 -0
  25. data/spec/strand/mutex.rb +244 -0
  26. data/spec/strand/pass.rb +9 -0
  27. data/spec/strand/queue.rb +124 -0
  28. data/spec/strand/raise.rb +142 -0
  29. data/spec/strand/run.rb +5 -0
  30. data/spec/strand/shared.rb +14 -0
  31. data/spec/strand/sleep.rb +51 -0
  32. data/spec/strand/status.rb +44 -0
  33. data/spec/strand/stop.rb +58 -0
  34. data/spec/strand/strand.rb +32 -0
  35. data/spec/strand/value.rb +39 -0
  36. data/spec/strand/wakeup.rb +60 -0
  37. data/spec/strand_spec.rb +51 -0
  38. data/spec/support/fixtures.rb +305 -0
  39. data/spec/support/scratch.rb +17 -0
  40. data/spec/thread_spec.rb +20 -0
  41. data/strand.gemspec +23 -0
  42. metadata +72 -58
  43. data/Gemfile.lock +0 -40
  44. data/lib/strand/condition_variable.rb +0 -78
  45. data/spec/condition_variable_spec.rb +0 -82
  46. data/test/helper.rb +0 -30
  47. data/test/test_strand.rb +0 -121
@@ -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
@@ -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