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,193 @@
|
|
1
|
+
|
2
|
+
module Strand
|
3
|
+
|
4
|
+
# See MRI Ruby's MonitorMixin
|
5
|
+
# This file (strand/monitor.rb) is derived from MRI Ruby 1.9.3 monitor.rb
|
6
|
+
#
|
7
|
+
# Copyright (C) 2001 Shugo Maeda <shugo@ruby-lang.org>
|
8
|
+
#
|
9
|
+
module MonitorMixin
|
10
|
+
|
11
|
+
class ConditionVariable
|
12
|
+
|
13
|
+
#
|
14
|
+
# Releases the lock held in the associated monitor and waits; reacquires the lock on wakeup.
|
15
|
+
#
|
16
|
+
# If +timeout+ is given, this method returns after +timeout+ seconds passed,
|
17
|
+
# even if no other thread doesn't signal.
|
18
|
+
#
|
19
|
+
def wait(timeout = nil)
|
20
|
+
@monitor.__send__(:mon_check_owner)
|
21
|
+
count = @monitor.__send__(:mon_exit_for_cond)
|
22
|
+
begin
|
23
|
+
@cond.wait(@monitor.instance_variable_get("@mon_mutex"), timeout)
|
24
|
+
return true
|
25
|
+
ensure
|
26
|
+
@monitor.__send__(:mon_enter_for_cond, count)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
#
|
31
|
+
# Calls wait repeatedly while the given block yields a truthy value.
|
32
|
+
#
|
33
|
+
def wait_while
|
34
|
+
while yield
|
35
|
+
wait
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Calls wait repeatedly until the given block yields a truthy value.
|
41
|
+
#
|
42
|
+
def wait_until
|
43
|
+
until yield
|
44
|
+
wait
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
#
|
49
|
+
# Wakes up the first thread in line waiting for this lock.
|
50
|
+
#
|
51
|
+
def signal
|
52
|
+
@monitor.__send__(:mon_check_owner)
|
53
|
+
@cond.signal
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# Wakes up all threads waiting for this lock.
|
58
|
+
#
|
59
|
+
def broadcast
|
60
|
+
@monitor.__send__(:mon_check_owner)
|
61
|
+
@cond.broadcast
|
62
|
+
end
|
63
|
+
|
64
|
+
private
|
65
|
+
|
66
|
+
def initialize(monitor)
|
67
|
+
@monitor = monitor
|
68
|
+
@cond = ConditionVariable.new
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
def self.extend_object(obj)
|
73
|
+
super(obj)
|
74
|
+
obj.__send__(:mon_initialize)
|
75
|
+
end
|
76
|
+
|
77
|
+
#
|
78
|
+
# Attempts to enter exclusive section. Returns +false+ if lock fails.
|
79
|
+
#
|
80
|
+
def mon_try_enter
|
81
|
+
if @mon_owner != Strand.current
|
82
|
+
unless @mon_mutex.try_lock
|
83
|
+
return false
|
84
|
+
end
|
85
|
+
@mon_owner = Strand.current
|
86
|
+
end
|
87
|
+
@mon_count += 1
|
88
|
+
return true
|
89
|
+
end
|
90
|
+
# For backward compatibility
|
91
|
+
alias try_mon_enter mon_try_enter
|
92
|
+
|
93
|
+
#
|
94
|
+
# Enters exclusive section.
|
95
|
+
#
|
96
|
+
def mon_enter
|
97
|
+
if @mon_owner != Strand.current
|
98
|
+
@mon_mutex.lock
|
99
|
+
@mon_owner = Strand.current
|
100
|
+
end
|
101
|
+
@mon_count += 1
|
102
|
+
end
|
103
|
+
|
104
|
+
#
|
105
|
+
# Leaves exclusive section.
|
106
|
+
#
|
107
|
+
def mon_exit
|
108
|
+
mon_check_owner
|
109
|
+
@mon_count -=1
|
110
|
+
if @mon_count == 0
|
111
|
+
@mon_owner = nil
|
112
|
+
@mon_mutex.unlock
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
#
|
117
|
+
# Enters exclusive section and executes the block. Leaves the exclusive
|
118
|
+
# section automatically when the block exits. See example under
|
119
|
+
# +MonitorMixin+.
|
120
|
+
#
|
121
|
+
def mon_synchronize
|
122
|
+
mon_enter
|
123
|
+
begin
|
124
|
+
yield
|
125
|
+
ensure
|
126
|
+
mon_exit
|
127
|
+
end
|
128
|
+
end
|
129
|
+
alias synchronize mon_synchronize
|
130
|
+
|
131
|
+
#
|
132
|
+
# Creates a new MonitorMixin::ConditionVariable associated with the
|
133
|
+
# receiver.
|
134
|
+
#
|
135
|
+
def new_cond
|
136
|
+
return ConditionVariable.new(self)
|
137
|
+
end
|
138
|
+
|
139
|
+
private
|
140
|
+
|
141
|
+
# Use <tt>extend MonitorMixin</tt> or <tt>include MonitorMixin</tt> instead
|
142
|
+
# of this constructor. Have look at the examples above to understand how to
|
143
|
+
# use this module.
|
144
|
+
def initialize(*args)
|
145
|
+
super
|
146
|
+
mon_initialize
|
147
|
+
end
|
148
|
+
|
149
|
+
# Initializes the MonitorMixin after being included in a class or when an
|
150
|
+
# object has been extended with the MonitorMixin
|
151
|
+
def mon_initialize
|
152
|
+
@mon_owner = nil
|
153
|
+
@mon_count = 0
|
154
|
+
@mon_mutex = Mutex.new
|
155
|
+
end
|
156
|
+
|
157
|
+
def mon_check_owner
|
158
|
+
if @mon_owner != Strand.current
|
159
|
+
raise Strand.delegate_class(ThreadError,FiberError), "current thread not owner"
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def mon_enter_for_cond(count)
|
164
|
+
@mon_owner = Strand.current
|
165
|
+
@mon_count = count
|
166
|
+
end
|
167
|
+
|
168
|
+
def mon_exit_for_cond
|
169
|
+
count = @mon_count
|
170
|
+
@mon_owner = nil
|
171
|
+
@mon_count = 0
|
172
|
+
return count
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
# Use the Monitor class when you want to have a lock object for blocks with
|
177
|
+
# mutual exclusion.
|
178
|
+
#
|
179
|
+
# require 'monitor'
|
180
|
+
#
|
181
|
+
# lock = Monitor.new
|
182
|
+
# lock.synchronize do
|
183
|
+
# # exclusive access
|
184
|
+
# end
|
185
|
+
#
|
186
|
+
class Monitor
|
187
|
+
include MonitorMixin
|
188
|
+
alias try_enter try_mon_enter
|
189
|
+
alias enter mon_enter
|
190
|
+
alias exit mon_exit
|
191
|
+
end
|
192
|
+
|
193
|
+
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,11 +1,11 @@
|
|
1
|
-
|
2
|
-
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
1
|
+
require 'eventmachine'
|
3
2
|
require 'strand'
|
3
|
+
require 'bundler'
|
4
4
|
Bundler.require(:development)
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
6
|
+
require 'support/scratch'
|
7
|
+
require 'support/fixtures'
|
8
|
+
|
9
9
|
|
10
10
|
RSpec.configure do |config|
|
11
11
|
config.mock_with :rr
|
@@ -15,3 +15,7 @@ require "strand/atc"
|
|
15
15
|
Atc = Strand::Atc
|
16
16
|
|
17
17
|
require "em-spec/rspec"
|
18
|
+
|
19
|
+
def quarantine!(&specs)
|
20
|
+
#Nothing
|
21
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
|
2
|
+
shared_examples_for "Strand#alive?" do
|
3
|
+
|
4
|
+
context "alive?" do
|
5
|
+
it "can check it's own status" do
|
6
|
+
StrandSpecs.status_of_current_strand.alive?.should == true
|
7
|
+
end
|
8
|
+
|
9
|
+
it "describes a sleeping strand" do
|
10
|
+
StrandSpecs.status_of_sleeping_strand.alive?.should == true
|
11
|
+
end
|
12
|
+
|
13
|
+
it "describes a blocked strand" do
|
14
|
+
StrandSpecs.status_of_blocked_strand.alive?.should == true
|
15
|
+
end
|
16
|
+
|
17
|
+
it "describes a completed strand" do
|
18
|
+
StrandSpecs.status_of_completed_strand.alive?.should == false
|
19
|
+
end
|
20
|
+
|
21
|
+
it "describes a killed strand" do
|
22
|
+
StrandSpecs.status_of_killed_strand.alive?.should == false
|
23
|
+
end
|
24
|
+
|
25
|
+
it "describes a strand with an uncaught exception" do
|
26
|
+
StrandSpecs.status_of_strand_with_uncaught_exception.alive?.should == false
|
27
|
+
end
|
28
|
+
|
29
|
+
it "describes a dying running strand" do
|
30
|
+
StrandSpecs.status_of_dying_running_strand.alive?.should == true
|
31
|
+
end
|
32
|
+
|
33
|
+
it "describes a dying sleeping strand" do
|
34
|
+
StrandSpecs.status_of_dying_sleeping_strand.alive?.should == true
|
35
|
+
end
|
36
|
+
|
37
|
+
# No such thing as a "running" strand
|
38
|
+
quarantine!() do
|
39
|
+
it "describes a running strand" do
|
40
|
+
StrandSpecs.status_of_running_strand.alive?.should == true
|
41
|
+
end
|
42
|
+
|
43
|
+
it "return true for a killed but still running strand" do
|
44
|
+
exit = false
|
45
|
+
t = Strand.new do
|
46
|
+
begin
|
47
|
+
sleep
|
48
|
+
ensure
|
49
|
+
true while !exit # spin until told to exit
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
StrandSpecs.spin_until_sleeping(t)
|
54
|
+
|
55
|
+
t.kill
|
56
|
+
t.alive?.should == true
|
57
|
+
exit = true
|
58
|
+
t.join
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
require 'strand/condition_variable/broadcast'
|
2
|
+
require 'strand/condition_variable/signal'
|
3
|
+
require 'strand/condition_variable/wait'
|
4
|
+
|
5
|
+
shared_examples_for "a condition variable" do
|
6
|
+
include_examples "ConditionVariable#signal"
|
7
|
+
include_examples "ConditionVariable#wait"
|
8
|
+
include_examples "ConditionVariable#broadcast"
|
9
|
+
end
|
10
|
+
|
@@ -0,0 +1,61 @@
|
|
1
|
+
|
2
|
+
shared_examples_for "ConditionVariable#broadcast" do
|
3
|
+
|
4
|
+
context "#broadcast" do
|
5
|
+
it "returns self if nothing to broadcast to" do
|
6
|
+
cv = Strand::ConditionVariable.new
|
7
|
+
cv.broadcast.should == cv
|
8
|
+
end
|
9
|
+
|
10
|
+
it "returns self if something is waiting for a broadcast" do
|
11
|
+
m = Strand::Mutex.new
|
12
|
+
cv = Strand::ConditionVariable.new
|
13
|
+
th = Strand.new do
|
14
|
+
m.synchronize do
|
15
|
+
cv.wait(m)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
Strand.pass while th.status and th.status != 'sleep'
|
20
|
+
|
21
|
+
m.synchronize { cv.broadcast }.should == cv
|
22
|
+
|
23
|
+
th.join
|
24
|
+
end
|
25
|
+
|
26
|
+
it "releases all strands waiting in line for this resource" do
|
27
|
+
m = Strand::Mutex.new
|
28
|
+
cv = Strand::ConditionVariable.new
|
29
|
+
strands = []
|
30
|
+
r1 = []
|
31
|
+
r2 = []
|
32
|
+
|
33
|
+
# large number to attempt to cause race conditions
|
34
|
+
10.times do |i|
|
35
|
+
strands << Strand.new(i) do |tid|
|
36
|
+
m.synchronize do
|
37
|
+
r1 << tid
|
38
|
+
cv.wait(m)
|
39
|
+
r2 << tid
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# wait for all strands to acquire the mutex the first time
|
45
|
+
Strand.pass until m.synchronize { r1.size == strands.size }
|
46
|
+
# wait until all strands are sleeping (ie waiting)
|
47
|
+
Strand.pass until strands.all? {|th| th.status == "sleep" }
|
48
|
+
r2.should be_empty
|
49
|
+
m.synchronize do
|
50
|
+
cv.broadcast
|
51
|
+
end
|
52
|
+
|
53
|
+
strands.each {|t| t.join }
|
54
|
+
|
55
|
+
# ensure that all strands that enter cv.wait are released
|
56
|
+
r2.sort.should == r1.sort
|
57
|
+
# note that order is not specified as broadcast results in a race
|
58
|
+
# condition on regaining the lock m
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
shared_examples_for "ConditionVariable#signal" do
|
2
|
+
context :signal do
|
3
|
+
it "returns self if nothing to signal" do
|
4
|
+
cv = Strand::ConditionVariable.new
|
5
|
+
cv.signal.should == cv
|
6
|
+
end
|
7
|
+
|
8
|
+
it "returns self if something is waiting for a signal" do
|
9
|
+
m = Strand::Mutex.new
|
10
|
+
cv = Strand::ConditionVariable.new
|
11
|
+
th = Strand.new do
|
12
|
+
m.synchronize do
|
13
|
+
cv.wait(m)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
# ensures that th grabs m before current strand
|
18
|
+
Strand.pass while th.status and th.status != "sleep"
|
19
|
+
|
20
|
+
m.synchronize { cv.signal }.should == cv
|
21
|
+
|
22
|
+
th.join
|
23
|
+
end
|
24
|
+
|
25
|
+
it "releases the first strand waiting in line for this resource" do
|
26
|
+
m = Strand::Mutex.new
|
27
|
+
cv = Strand::ConditionVariable.new
|
28
|
+
strands = []
|
29
|
+
r1 = []
|
30
|
+
r2 = []
|
31
|
+
|
32
|
+
# large number to attempt to cause race conditions
|
33
|
+
10.times do |i|
|
34
|
+
strands << Strand.new(i) do |tid|
|
35
|
+
m.synchronize do
|
36
|
+
r1 << tid
|
37
|
+
cv.wait(m)
|
38
|
+
r2 << tid
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
# wait for all strands to acquire the mutex the first time
|
44
|
+
Strand.pass until m.synchronize { r1.size == strands.size }
|
45
|
+
# wait until all strands are sleeping (ie waiting)
|
46
|
+
Strand.pass until strands.all? {|th| th.status == "sleep" || !thread.status }
|
47
|
+
r2.should be_empty
|
48
|
+
10.times do |i|
|
49
|
+
m.synchronize do
|
50
|
+
cv.signal
|
51
|
+
end
|
52
|
+
Strand.pass until r2.size == i+1
|
53
|
+
end
|
54
|
+
|
55
|
+
strands.each {|t| t.join }
|
56
|
+
|
57
|
+
# ensure that all the strands that went into the cv.wait are
|
58
|
+
# released in the same order
|
59
|
+
r2.should == r1
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
shared_examples_for "ConditionVariable#wait" do
|
2
|
+
context :wait do
|
3
|
+
it "returns self" do
|
4
|
+
m = Strand::Mutex.new
|
5
|
+
cv = Strand::ConditionVariable.new
|
6
|
+
|
7
|
+
th = Strand.new do
|
8
|
+
m.synchronize do
|
9
|
+
cv.wait(m).should == cv
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# ensures that th grabs m before current thread
|
14
|
+
Strand.pass while th.status and th.status != "sleep"
|
15
|
+
|
16
|
+
m.synchronize { cv.signal }
|
17
|
+
th.join
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
shared_examples_for "Strand#current" do
|
3
|
+
context "current" do
|
4
|
+
it "returns a strand" do
|
5
|
+
current = Strand.current
|
6
|
+
current.should be_kind_of(strand_type)
|
7
|
+
end
|
8
|
+
|
9
|
+
it "returns the current strand" do
|
10
|
+
t = Strand.new { Strand.current }
|
11
|
+
t.value.should equal(t)
|
12
|
+
Strand.current.should_not equal(t.value)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|