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