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
@@ -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
|
data/spec/strand_spec.rb
ADDED
@@ -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
|