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