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.
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