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,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
@@ -0,0 +1,3 @@
1
+ module Strand
2
+ VERSION = "0.2.0.rc0"
3
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,11 +1,11 @@
1
- $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
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
- # Requires supporting files with custom matchers and macros, etc,
7
- # in ./support/ and its subdirectories.
8
- Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
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