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,32 @@
1
+ require 'strand/join'
2
+ require 'strand/exit'
3
+ require 'strand/wakeup'
4
+ require 'strand/status'
5
+ require 'strand/stop'
6
+ require 'strand/raise'
7
+ require 'strand/current'
8
+ require 'strand/alive'
9
+ require 'strand/pass'
10
+ require 'strand/value'
11
+ require 'strand/sleep'
12
+
13
+ shared_examples "a strand" do
14
+
15
+ it "is the expected type of strand" do
16
+ Strand.delegate_class(::Thread).should == strand_type
17
+ end
18
+
19
+ include_examples "Strand#current"
20
+ include_examples "Strand#status"
21
+ include_examples "Strand#exit"
22
+ include_examples "Strand#join"
23
+ include_examples "Strand#wakeup"
24
+ include_examples "Strand#stop"
25
+ include_examples "Strand#raise"
26
+ include_examples "Strand#alive?"
27
+ include_examples "Strand#pass"
28
+ include_examples "Strand#value"
29
+ include_examples "Strand#sleep"
30
+
31
+ it "should have specs for Strand#list"
32
+ end
@@ -0,0 +1,39 @@
1
+
2
+ shared_examples_for "Strand#value" do
3
+ describe "value" do
4
+ it "returns the result of the block" do
5
+ Thread.new { 3 }.value.should == 3
6
+ end
7
+
8
+ it "re-raises error for an uncaught exception" do
9
+ t = Thread.new { raise "Hello" }
10
+ lambda { t.value }.should raise_error(RuntimeError, "Hello")
11
+ end
12
+
13
+ quarantine!() do
14
+ # varying results for killed threads and exceptions
15
+ ruby_version_is "" ... "1.9" do
16
+ it "is false for a killed thread" do
17
+ t = Thread.new { Thread.current.exit }
18
+ t.value.should == false
19
+ end
20
+ end
21
+
22
+ ruby_version_is "1.9" do
23
+ it "is nil for a killed thread" do
24
+ t = Thread.new { Thread.current.exit }
25
+ t.value.should == nil
26
+ end
27
+ end
28
+
29
+ ruby_version_is "" ... "1.9" do
30
+ not_compliant_on :rubinius do
31
+ it "is false for an uncaught exception thrown from a dying thread" do
32
+ t = ThreadSpecs.dying_thread_ensures { 1/0 }
33
+ t.value.should == false
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,60 @@
1
+ shared_examples_for "Strand#wakeup" do
2
+
3
+ context :wakeup do
4
+ it "can interrupt Strand#sleep" do
5
+ exit_loop = false
6
+ after_sleep1 = false
7
+ after_sleep2 = false
8
+
9
+ t = Strand.new do
10
+
11
+ # For Threads, this is an infinite running loop
12
+ # but for EM::Thread this part of the test cannot
13
+ # be simulated
14
+ #while true
15
+ # break if exit_loop == true
16
+ #end unless
17
+
18
+ Strand.sleep
19
+ after_sleep1 = true
20
+
21
+ Strand.sleep
22
+ after_sleep2 = true
23
+ end
24
+
25
+ 10.times { Strand.sleep(0.1) if t.status and t.status != "sleep" }
26
+ after_sleep1.should == false # t should be blocked on the first sleep
27
+ t.send(:wakeup)
28
+
29
+ 10.times { Strand.sleep(0.1) if t.status and t.status != "sleep" }
30
+ after_sleep2.should == false # t should be blocked on the second sleep
31
+ t.send(:wakeup)
32
+
33
+ t.join
34
+ end
35
+
36
+ it "does not result in a deadlock" do
37
+ t = Strand.new do
38
+ 10.times { Strand.stop }
39
+ end
40
+
41
+ while(t.status != false) do
42
+ begin
43
+ t.send(:wakeup)
44
+ rescue FiberError,ThreadError
45
+ # The strand might die right after.
46
+ t.status.should == false
47
+ end
48
+ end
49
+
50
+ 1.should == 1 # test succeeds if we reach here
51
+ end
52
+
53
+ it "raises a StrandError when trying to wake up a dead strand" do
54
+ expected_error = Strand.event_machine? ? FiberError : ThreadError
55
+ t = Strand.new { 1 }
56
+ t.join
57
+ lambda { t.wakeup }.should raise_error(expected_error)
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,51 @@
1
+ require 'spec_helper'
2
+ require 'strand/shared'
3
+
4
+ include EM::SpecHelper
5
+
6
+ describe Strand::EM::Thread do
7
+
8
+ let(:strand_type) { Strand::EM::Thread }
9
+ let(:strand_exception) { FiberError }
10
+ around(:each) do |example|
11
+ ScratchPad.clear
12
+ em do
13
+ example.run
14
+ done
15
+ end
16
+ end
17
+
18
+ it "should run examples within EventMachine" do
19
+ EM.reactor_running?.should be_true
20
+ Strand.event_machine?.should be_true
21
+ end
22
+
23
+ include_examples Strand
24
+
25
+ it "should pass resume and yield arguments like fiber" do
26
+ em do
27
+ s = Strand.new() do
28
+ # These yield args get swalled by the resume that
29
+ # automatically starts the Strand
30
+ Strand.yield(:y1).should == [ :r2,"r2" ]
31
+
32
+ #These yeild args should be visible by our resume call below
33
+ Strand.yield(:y2,"y2").should == [ "r3",:r3]
34
+ :the_end
35
+ end
36
+
37
+ s.resume(:r2,"r2").should == [ :y2, "y2" ]
38
+
39
+ # This is the end of the strand because there are no more yields
40
+ # should be the value of the block.
41
+ # Most apps should be calling value() here, rather than resume
42
+ s.resume("r3",:r3).should == :the_end
43
+
44
+ # the Strand is dead now, but should still have captured the value
45
+ s.value.should == :the_end
46
+ done
47
+ end
48
+ end
49
+
50
+ it "needs some tests to describe how raw fibers can be treated as strands"
51
+ end
@@ -0,0 +1,305 @@
1
+
2
+ # Channel is just a queue with receive aliased to shift.
3
+ # if thread specs refer to these just replace with Queue
4
+ #
5
+ module StrandSpecs
6
+
7
+ module SubStrand
8
+ def self.new(*args)
9
+ subclass = Strand.event_machine? ? EMSubStrand : SubThread
10
+ subclass.new(*args)
11
+ end
12
+ end
13
+
14
+ class EMSubStrand < Strand::EM::Thread
15
+ def initialize(*args)
16
+ super { args.first << 1 }
17
+ end
18
+ end
19
+
20
+ class SubThread < ::Thread
21
+ def initialize(*args)
22
+ super { args.first << 1 }
23
+ end
24
+ end
25
+
26
+ class Status
27
+ attr_reader :strand, :inspect, :status
28
+ def initialize(strand)
29
+ @strand = strand
30
+ @alive = strand.alive?
31
+ @inspect = strand.inspect
32
+ @status = strand.status
33
+ @stop = strand.stop?
34
+ end
35
+
36
+ def alive?
37
+ @alive
38
+ end
39
+
40
+ def stop?
41
+ @stop
42
+ end
43
+ end
44
+
45
+ # TODO: In the great Thread spec rewrite, abstract this
46
+ class << self
47
+ attr_accessor :state
48
+ end
49
+
50
+ def self.clear_state
51
+ @state = nil
52
+ end
53
+
54
+ # GG Not really necessary, if a strand is executing by
55
+ # definition all other strands are dead or sleeping
56
+ def self.spin_until_sleeping(t)
57
+ Strand.pass while t.status and t.status != "sleep"
58
+ end
59
+
60
+ def self.sleeping_strand
61
+ Strand.new do
62
+ begin
63
+ Strand.sleep
64
+ rescue Object => e
65
+ ScratchPad.record e
66
+ end
67
+ end
68
+ end
69
+
70
+ #GG: Only the current strand can be running, so you can never
71
+ # find a running strand from outside the strand
72
+ def self.running_strand
73
+ Strand.new do
74
+ begin
75
+ StrandSpecs.state = :running
76
+ loop { Strand.pass }
77
+ ScratchPad.record :woken
78
+ rescue Object => e
79
+ ScratchPad.record e
80
+ end
81
+ end
82
+ end
83
+
84
+ def self.completed_strand
85
+ Strand.new {}
86
+ end
87
+
88
+ def self.status_of_current_strand
89
+ Strand.new { Status.new(Strand.current) }.value
90
+ end
91
+
92
+ #GG: can't check a running strand from outside the strand
93
+ def self.status_of_running_strand
94
+ t = running_strand
95
+ Strand.pass while t.status and t.status != "run"
96
+ status = Status.new t
97
+ t.kill
98
+ t.join
99
+ status
100
+ end
101
+
102
+ def self.status_of_completed_strand
103
+ t = completed_strand
104
+ t.join
105
+ Status.new t
106
+ end
107
+
108
+ def self.status_of_sleeping_strand
109
+ t = sleeping_strand
110
+ Strand.pass while t.status and t.status != 'sleep'
111
+ status = Status.new t
112
+ t.run
113
+ t.join
114
+ status
115
+ end
116
+
117
+ def self.status_of_blocked_strand
118
+ m = Strand::Mutex.new
119
+ m.lock
120
+ t = Strand.new { m.lock }
121
+ Strand.pass while t.status and t.status != 'sleep'
122
+ status = Status.new t
123
+ m.unlock
124
+ t.join
125
+ status
126
+ end
127
+
128
+ def self.status_of_aborting_strand
129
+ end
130
+
131
+ def self.status_of_killed_strand
132
+ t = Strand.new { Strand.sleep }
133
+ t.kill
134
+ t.join
135
+ Status.new t
136
+ end
137
+
138
+ def self.status_of_strand_with_uncaught_exception
139
+ t = Strand.new { raise "error" }
140
+ begin
141
+ t.join
142
+ rescue RuntimeError
143
+ end
144
+ Status.new t
145
+ end
146
+
147
+ def self.status_of_dying_running_strand
148
+ status = nil
149
+ t = dying_strand_ensures { status = Status.new Strand.current }
150
+ t.join
151
+ status
152
+ end
153
+
154
+ def self.status_of_dying_sleeping_strand
155
+ t = dying_strand_ensures { Strand.stop; }
156
+ Strand.pass while t.status and t.status != 'sleep'
157
+ status = Status.new t
158
+ t.wakeup
159
+ t.join
160
+ status
161
+ end
162
+
163
+ def self.dying_strand_ensures(kill_method_name=:kill)
164
+ t = Strand.new do
165
+ begin
166
+ Strand.current.send(kill_method_name)
167
+ ensure
168
+ yield
169
+ end
170
+ end
171
+ end
172
+
173
+ def self.dying_strand_with_outer_ensure(kill_method_name=:kill)
174
+ t = Strand.new do
175
+ begin
176
+ begin
177
+ Strand.current.send(kill_method_name)
178
+ ensure
179
+ raise "In dying strand"
180
+ end
181
+ ensure
182
+ yield
183
+ end
184
+ end
185
+ end
186
+
187
+ def self.join_dying_strand_with_outer_ensure(kill_method_name=:kill)
188
+ t = dying_strand_with_outer_ensure(kill_method_name) { yield }
189
+ lambda { t.join }.should raise_error(RuntimeError, "In dying strand")
190
+ return t
191
+ end
192
+
193
+ def self.wakeup_dying_sleeping_strand(kill_method_name=:kill)
194
+ t = StrandSpecs.dying_strand_ensures(kill_method_name) { yield }
195
+ Strand.pass while t.status and t.status != 'sleep'
196
+ t.wakeup
197
+ t.join
198
+ end
199
+
200
+ def self.critical_is_reset
201
+ # Create another strand to verify that it can call Strand.critical=
202
+ t = Strand.new do
203
+ initial_critical = Strand.critical
204
+ Strand.critical = true
205
+ Strand.critical = false
206
+ initial_critical == false && Strand.critical == false
207
+ end
208
+ v = t.value
209
+ t.join
210
+ v
211
+ end
212
+
213
+ def self.counter
214
+ @@counter
215
+ end
216
+
217
+ def self.counter= c
218
+ @@counter = c
219
+ end
220
+
221
+ def self.increment_counter(incr)
222
+ incr.times do
223
+ begin
224
+ Strand.critical = true
225
+ @@counter += 1
226
+ ensure
227
+ Strand.critical = false
228
+ end
229
+ end
230
+ end
231
+
232
+ def self.critical_strand1()
233
+ Strand.critical = true
234
+ Strand.current.key?(:strand_specs).should == false
235
+ end
236
+
237
+ def self.critical_strand2(isStrandStop)
238
+ Strand.current[:strand_specs].should == 101
239
+ Strand.critical.should == !isStrandStop
240
+ if not isStrandStop
241
+ Strand.critical = false
242
+ end
243
+ end
244
+
245
+ def self.main_strand1(critical_strand, isStrandSleep, isStrandStop)
246
+ # Strand.stop resets Strand.critical. Also, with native strands, the Strand.Stop may not have executed yet
247
+ # since the main strand will race with the critical strand
248
+ if not isStrandStop
249
+ Strand.critical.should == true
250
+ end
251
+ critical_strand[:strand_specs] = 101
252
+ if isStrandSleep or isStrandStop
253
+ # Strand#wakeup calls are not queued up. So we need to ensure that the strand is sleeping before calling wakeup
254
+ Strand.pass while critical_strand.status and critical_strand.status != "sleep"
255
+ critical_strand.wakeup
256
+ end
257
+ end
258
+
259
+ def self.main_strand2(critical_strand)
260
+ Strand.pass # The join below seems to cause a deadlock with CRuby unless Strand.pass is called first
261
+ critical_strand.join
262
+ Strand.critical.should == false
263
+ end
264
+
265
+ def self.critical_strand_yields_to_main_strand(isStrandSleep=false, isStrandStop=false)
266
+ @@after_first_sleep = false
267
+
268
+ critical_strand = Strand.new do
269
+ Strand.pass while Strand.main.status and Strand.main.status != "sleep"
270
+ critical_strand1()
271
+ Strand.main.wakeup
272
+ yield
273
+ Strand.pass while @@after_first_sleep != true # Need to ensure that the next statement does not see the first sleep itself
274
+ Strand.pass while Strand.main.status and Strand.main.status != "sleep"
275
+ critical_strand2(isStrandStop)
276
+ Strand.main.wakeup
277
+ end
278
+
279
+ sleep 5
280
+ @@after_first_sleep = true
281
+ main_strand1(critical_strand, isStrandSleep, isStrandStop)
282
+ sleep 5
283
+ main_strand2(critical_strand)
284
+ end
285
+
286
+ def self.create_critical_strand()
287
+ critical_strand = Strand.new do
288
+ Strand.critical = true
289
+ yield
290
+ Strand.critical = false
291
+ end
292
+ return critical_strand
293
+ end
294
+
295
+ def self.create_and_kill_critical_strand(passAfterKill=false)
296
+ critical_strand = StrandSpecs.create_critical_strand do
297
+ Strand.current.kill
298
+ if passAfterKill
299
+ Strand.pass
300
+ end
301
+ ScratchPad.record("status=" + Strand.current.status)
302
+ end
303
+ end
304
+
305
+ end