strand 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
6
+ README.rdoc
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/Gemfile ADDED
@@ -0,0 +1,16 @@
1
+ source "http://rubygems.org"
2
+ # Add dependencies required to use your gem here.
3
+ # Example:
4
+ # gem "activesupport", ">= 2.3.5"
5
+
6
+ # Add dependencies to develop your gem here.
7
+ # Include everything needed to run rake, tests, features, etc.
8
+ group :development do
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "em-spec"
11
+ gem "eventmachine", "~> 0.12.0"
12
+ gem "jeweler", "~> 1.6.4"
13
+ gem "rcov", ">= 0"
14
+ gem "rr"
15
+ gem "rspec"
16
+ end
@@ -0,0 +1,40 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ bacon (1.1.0)
5
+ diff-lcs (1.1.2)
6
+ em-spec (0.2.5)
7
+ bacon
8
+ eventmachine
9
+ rspec (~> 2.6.0)
10
+ test-unit
11
+ eventmachine (0.12.10)
12
+ git (1.2.5)
13
+ jeweler (1.6.4)
14
+ bundler (~> 1.0)
15
+ git (>= 1.2.5)
16
+ rake
17
+ rake (0.9.2)
18
+ rcov (0.9.9)
19
+ rr (1.0.3)
20
+ rspec (2.6.0)
21
+ rspec-core (~> 2.6.0)
22
+ rspec-expectations (~> 2.6.0)
23
+ rspec-mocks (~> 2.6.0)
24
+ rspec-core (2.6.4)
25
+ rspec-expectations (2.6.0)
26
+ diff-lcs (~> 1.1.2)
27
+ rspec-mocks (2.6.0)
28
+ test-unit (2.3.2)
29
+
30
+ PLATFORMS
31
+ ruby
32
+
33
+ DEPENDENCIES
34
+ bundler (~> 1.0.0)
35
+ em-spec
36
+ eventmachine (~> 0.12.0)
37
+ jeweler (~> 1.6.4)
38
+ rcov
39
+ rr
40
+ rspec
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Christopher J. Bottaro
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,173 @@
1
+ = strand
2
+
3
+ Strand is a class that wraps Fibers and gives them Thread-like behavior by using EventMachine.
4
+
5
+ Strand::ConditionVariable provides functionality for Fibers similar to Ruby's built in ConditionVariable for Threads.
6
+
7
+ == Like Threads
8
+
9
+ Strand has an interface similar to Thread.
10
+
11
+ thread = Thread.new{ 1+2 }
12
+ thread.join
13
+ thread.value # => 3
14
+
15
+ strand = Strand.new{ 1+2 }
16
+ strand.join
17
+ strand.value # => 3
18
+
19
+ == Fiber/EventMachine aware sleep
20
+
21
+ Calling sleep usually results in EventMachine's reactor being blocked, but Strand defines an EM+Fiber safe sleep.
22
+
23
+ Consider mimicking the following threaded code:
24
+
25
+ t1 = Thread.new{ sleep(1) }
26
+ t2 = Thread.new{ sleep(1) }
27
+ t1.join
28
+ t2.join
29
+ # Roughly 1 second will have passed.
30
+
31
+ Assuming that EventMachine is running:
32
+
33
+ s1 = Strand.new{ Strand.sleep(1) }
34
+ s2 = Strand.new{ Strand.sleep(1) }
35
+ s1.join
36
+ s2.join
37
+ # Roughly 1 second will have passed.
38
+
39
+ == Thread local, Fiber local and Strand local variables
40
+
41
+ Thread local storage is the same as Fiber local storage in Ruby.
42
+
43
+ Thread.current[:name] = "callie"
44
+ Fiber.current[:name] # => "callie"
45
+ Fiber.current[:name] = "coco"
46
+ Thread.current[:name] # => "coco"
47
+
48
+ Strand provides its own storage.
49
+
50
+ Thread.current[:name] = "callie"
51
+ Strand.current[:name] = "coco"
52
+ Thread.current[:name] # => "callie"
53
+ Strand.current[:name] # => "coco"
54
+
55
+ == Strand.list
56
+
57
+ Ruby provides a way to get a list of all living Threads:
58
+
59
+ Thread.new{ sleep }
60
+ Thread.list # => [#<Thread:0x007f9343869da8 run>, #<Thread:0x007f934405eec8 sleep>]
61
+
62
+ There is no equivalent for finding all living Fibers.
63
+
64
+ There is a way to find all living Strands though:
65
+
66
+ Strand.new do
67
+ Strand.new{ Strand.yield }
68
+ Strand.list # => [#<Strand:0x70358087608460 run, #<Strand:0x70358087608280 yielded]
69
+ end
70
+
71
+ == Strand.pass
72
+
73
+ Consider the following threaded code:
74
+
75
+ Thread.new do
76
+ puts 1
77
+ Thread.pass
78
+ puts 2
79
+ Thread.pass
80
+ puts 3
81
+ end
82
+ Thread.new do
83
+ puts 1
84
+ Thread.pass
85
+ puts 2
86
+ Thread.pass
87
+ puts 3
88
+ end
89
+
90
+ Or similarly:
91
+
92
+ Thread.new do
93
+ puts 1
94
+ sleep(0.01)
95
+ puts 2
96
+ sleep(0.01)
97
+ puts 3
98
+ end
99
+ Thread.new do
100
+ puts 1
101
+ sleep(0.01)
102
+ puts 2
103
+ sleep(0.01)
104
+ puts 3
105
+ end
106
+
107
+ How would you do that with fibers?
108
+
109
+ Fiber.new do
110
+ puts 1
111
+ Fiber.yield
112
+ puts 2
113
+ Fiber.yield
114
+ puts 3
115
+ end.resume
116
+ Fiber.new do
117
+ puts 1
118
+ Fiber.yield
119
+ puts 2
120
+ Fiber.yield
121
+ puts 3
122
+ end.resume
123
+
124
+ That doesn't work. The fibers are yielding, but nothing is resuming them. The output is just:
125
+
126
+ 1
127
+ 1
128
+
129
+ Enter <tt>Strand.pass</tt>. It yields the fiber, but tells EventMachine to resume it on the next tick.
130
+
131
+ Strand.new do
132
+ puts 1
133
+ Strand.pass
134
+ puts 2
135
+ Strand.pass
136
+ puts 3
137
+ end
138
+ Strand.new do
139
+ puts 1
140
+ Strand.pass
141
+ puts 2
142
+ Strand.pass
143
+ puts 3
144
+ end
145
+
146
+ The output is:
147
+
148
+ 1
149
+ 1
150
+ 2
151
+ 2
152
+ 3
153
+ 3
154
+
155
+ == RDoc
156
+
157
+ {\http://doc.stochasticbytes.com/strand/index.html}[http://doc.stochasticbytes.com/strand/index.html]
158
+
159
+ == Contributing to strand
160
+
161
+ * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
162
+ * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
163
+ * Fork the project
164
+ * Start a feature/bugfix branch
165
+ * Commit and push until you are happy with your contribution
166
+ * Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
167
+ * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
168
+
169
+ == Copyright
170
+
171
+ Copyright (c) 2011 Christopher J. Bottaro. See LICENSE.txt for
172
+ further details.
173
+
@@ -0,0 +1,64 @@
1
+ # encoding: utf-8
2
+
3
+ require 'rubygems'
4
+ require 'bundler'
5
+ begin
6
+ Bundler.setup(:default, :development)
7
+ rescue Bundler::BundlerError => e
8
+ $stderr.puts e.message
9
+ $stderr.puts "Run `bundle install` to install missing gems"
10
+ exit e.status_code
11
+ end
12
+ require 'rake'
13
+
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
17
+ gem.name = "strand"
18
+ gem.homepage = "http://github.com/cjbottaro/strand"
19
+ gem.license = "MIT"
20
+ gem.summary = %Q{Make fibers behave like threads using EventMachine.}
21
+ gem.description = %Q{Get thread-like behavior from fibers using EventMachine.}
22
+ gem.email = "cjbottaro@alumni.cs.utexas.edu"
23
+ gem.authors = ["Christopher J. Bottaro"]
24
+ # dependencies defined in Gemfile
25
+ end
26
+ Jeweler::RubygemsDotOrgTasks.new
27
+
28
+ require 'rake/testtask'
29
+ Rake::TestTask.new(:test) do |test|
30
+ test.libs << 'lib' << 'test'
31
+ test.pattern = 'test/**/test_*.rb'
32
+ test.verbose = true
33
+ end
34
+
35
+ require 'rcov/rcovtask'
36
+ Rcov::RcovTask.new do |test|
37
+ test.libs << 'test'
38
+ test.pattern = 'test/**/test_*.rb'
39
+ test.verbose = true
40
+ test.rcov_opts << '--exclude "gems/*"'
41
+ end
42
+
43
+ require 'rspec/core'
44
+ require 'rspec/core/rake_task'
45
+ RSpec::Core::RakeTask.new(:spec) do |spec|
46
+ spec.pattern = FileList['spec/**/*_spec.rb']
47
+ end
48
+
49
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
50
+ spec.pattern = 'spec/**/*_spec.rb'
51
+ spec.rcov = true
52
+ end
53
+
54
+ task :default => :test
55
+
56
+ require 'rake/rdoctask'
57
+ Rake::RDocTask.new do |rdoc|
58
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
59
+
60
+ rdoc.rdoc_dir = 'rdoc'
61
+ rdoc.title = "strand #{version}"
62
+ rdoc.rdoc_files.include('README*')
63
+ rdoc.rdoc_files.include('lib/**/*.rb')
64
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
@@ -0,0 +1,132 @@
1
+ require "fiber"
2
+ require "eventmachine"
3
+ require "strand/condition_variable"
4
+
5
+ class Strand
6
+
7
+ @@strands = {}
8
+
9
+ # The strand's underlying fiber.
10
+ attr_reader :fiber
11
+
12
+ # Return an array of all Strands that are alive.
13
+ def self.list
14
+ @@strands.values
15
+ end
16
+
17
+ # Get the currently running strand. Primarily used to access "strand local" variables.
18
+ def self.current
19
+ @@strands[Fiber.current]
20
+ end
21
+
22
+ # EM/fiber safe sleep.
23
+ def self.sleep(seconds)
24
+ fiber = Fiber.current
25
+ EM::Timer.new(seconds){ fiber.resume }
26
+ Fiber.yield
27
+ end
28
+
29
+ # Alias for Fiber.yield.
30
+ def self.yield
31
+ Fiber.yield
32
+ end
33
+
34
+ # Yield the strand, but have EM resume it on the next tick.
35
+ def self.pass
36
+ fiber = Fiber.current
37
+ EM.next_tick{ fiber.resume }
38
+ Fiber.yield
39
+ end
40
+
41
+ # Create and run a strand.
42
+ def initialize(&block)
43
+
44
+ # Initialize our "fiber local" storage.
45
+ @locals = {}
46
+
47
+ # Condition variable for joining.
48
+ @join_cond = ConditionVariable.new
49
+
50
+ # Create our fiber.
51
+ @fiber = Fiber.new{ fiber_body(&block) }
52
+
53
+ # Add us to the list of living strands.
54
+ @@strands[@fiber] = self
55
+
56
+ # Finally start the strand.
57
+ resume
58
+ end
59
+
60
+ # Like Thread#join.
61
+ # s1 = Strand.new{ Strand.sleep(1) }
62
+ # s2 = Strand.new{ Strand.sleep(1) }
63
+ # s1.join
64
+ # s2.join
65
+ def join
66
+ @join_cond.wait if alive?
67
+ raise @exception if @exception
68
+ true
69
+ end
70
+
71
+ # Like Fiber#resume.
72
+ def resume
73
+ @fiber.resume
74
+ end
75
+
76
+ # Like Thread#alive? or Fiber#alive?
77
+ def alive?
78
+ @fiber.alive?
79
+ end
80
+
81
+ # Like Thread#value. Implicitly calls #join.
82
+ # strand = Strand.new{ 1+2 }
83
+ # strand.value # => 3
84
+ def value
85
+ join and @value
86
+ end
87
+
88
+ # Access to "strand local" variables, akin to "thread local" variables.
89
+ # Strand.new do
90
+ # ...
91
+ # Strand.current[:connection].send(data)
92
+ # ...
93
+ # end
94
+ def [](name)
95
+ @locals[name]
96
+ end
97
+
98
+ # Access to "strand local" variables, akin to "thread local" variables.
99
+ # Strand.new do
100
+ # ...
101
+ # Strand.current[:connection] = SomeConnectionClass.new(host, port)
102
+ # ...
103
+ # end
104
+ def []=(name, value)
105
+ @locals[name] = value
106
+ end
107
+
108
+ def inspect #:nodoc:
109
+ "#<Strand:0x%s %s" % [object_id, @fiber == Fiber.current ? "run" : "yielded"]
110
+ end
111
+
112
+ protected
113
+
114
+ def fiber_body(&block) #:nodoc:
115
+ # Run the strand's block and capture the return value.
116
+ begin
117
+ @value = block.call
118
+ rescue StandardError => e
119
+ @exception = e
120
+ end
121
+
122
+ # Mark the strand as finished running.
123
+ @finished = true
124
+
125
+ # Delete from the list of running stands.
126
+ @@strands.delete(@fiber)
127
+
128
+ # Resume anyone who called join on us.
129
+ @join_cond.signal
130
+ end
131
+
132
+ end
@@ -0,0 +1,41 @@
1
+ class Strand
2
+ class Atc #:nodoc:
3
+
4
+ def initialize(options = {})
5
+ @timeout = options[:timeout]
6
+ @cond = ConditionVariable.new
7
+ @states = []
8
+ end
9
+
10
+ # Wait for state to happen.
11
+ def wait(state, timeout = nil)
12
+ return true if @states.include?(state)
13
+ timeout ||= @timeout
14
+ if timeout
15
+ wait_with_timeout(state, timeout)
16
+ else
17
+ wait_without_timeout(state)
18
+ end
19
+ end
20
+
21
+ def signal(state)
22
+ @states << state
23
+ @cond.signal
24
+ end
25
+
26
+ private
27
+
28
+ def wait_with_timeout(state, timeout)
29
+ while not @states.include?(state)
30
+ return @states.include?(state) if @cond.wait(timeout) == false
31
+ end
32
+ true
33
+ end
34
+
35
+ def wait_without_timeout(state)
36
+ @cond.wait while not @states.include?(state)
37
+ true
38
+ end
39
+
40
+ end
41
+ end
@@ -0,0 +1,78 @@
1
+ class Strand
2
+ # Provides for Strands (Fibers) what Ruby's ConditionVariable provides for Threads.
3
+ class ConditionVariable
4
+
5
+ # Create a new condition variable.
6
+ def initialize
7
+ @waiters = []
8
+ end
9
+
10
+ # Wait until signaled. Returns true upon returning.
11
+ # x = nil
12
+ # cond = Strand::ConditionVariable.new
13
+ # Strand.new{ cond.wait; x = 1; cond.signal }
14
+ # puts x # => nil
15
+ # cond.signal
16
+ # cond.wait # => true
17
+ # puts x # => 1
18
+ # If timeout is a number, then returns false if timed out or true if signaled.
19
+ # x = nil
20
+ # cond = Strand::ConditionVariable.new
21
+ # Strand.new{ cond.wait; x = 1; cond.signal }
22
+ # puts x # => nil
23
+ # cond.wait(0.01) # => false
24
+ # puts x # => nil
25
+ def wait(timeout = nil)
26
+ # Get the fiber that called us.
27
+ fiber = Fiber.current
28
+
29
+ # Add the fiber to the list of waiters.
30
+ @waiters << fiber
31
+
32
+ # Setup the timer if they specified a timeout
33
+ timer = EM::Timer.new(timeout){ fiber.resume(:timeout) } if timeout
34
+
35
+ # Wait for signal or timeout.
36
+ if Fiber.yield == :timeout
37
+ # Timeout occurred.
38
+
39
+ # Remove from list of waiters.
40
+ @waiters.delete(fiber)
41
+
42
+ false
43
+ else
44
+ # Ok we were signaled.
45
+
46
+ # Cancel the timer if there is one.
47
+ timer.cancel if timer
48
+
49
+ true
50
+ end
51
+
52
+ end
53
+
54
+ # Asynchronously resume a fiber waiting on this condition variable.
55
+ # The waiter is not resumed immediately, but on the next tick of EM's reactor loop.
56
+ # cond = Strand::ConditionVariable.new
57
+ # Strand.new{ puts 1; cond.wait; puts 2 }
58
+ # puts 3
59
+ # cond.signal
60
+ # puts 4
61
+ # # output is...
62
+ # 1
63
+ # 3
64
+ # 4
65
+ # 2
66
+ def signal
67
+ # If there are no waiters, do nothing.
68
+ return if @waiters.empty?
69
+
70
+ # Find a waiter to wake up.
71
+ waiter = @waiters.shift
72
+
73
+ # Resume it on next tick.
74
+ EM.next_tick{ waiter.resume }
75
+ end
76
+
77
+ end
78
+ end
@@ -0,0 +1,82 @@
1
+ require "spec_helper"
2
+
3
+ describe Strand::ConditionVariable do
4
+ include EM::SpecHelper
5
+
6
+ context "calling #wait" do
7
+ before(:each) do
8
+ @cond = described_class.new
9
+ @atc = Atc.new :timeout => 0.01
10
+ end
11
+ it "should block until signaled" do
12
+ em do
13
+ Strand.new{ @cond.wait; @atc.signal(1) }
14
+ @atc.wait(1).should be_false
15
+ @cond.signal
16
+ @atc.wait(1).should be_true
17
+ done
18
+ end
19
+ end
20
+ it "should block until timed out" do
21
+ em do
22
+ Strand.new{ @cond.wait(0.02); @atc.signal(1) }
23
+ @atc.wait(1).should be_false
24
+ @atc.wait(1).should be_true
25
+ done
26
+ end
27
+ end
28
+ end
29
+
30
+ context "calling #signal" do
31
+ context "with no waiters" do
32
+ before(:all){ @cond = described_class.new }
33
+ it "should not do anything" do
34
+ @cond.signal.should be_nil
35
+ end
36
+ end
37
+ context "with a single waiter" do
38
+ before(:all) do
39
+ @cond = described_class.new
40
+ @atc = Atc.new :timeout => 0.01
41
+ Strand.new{ @cond.wait; @atc.signal(1) }
42
+ end
43
+ it "should wake up the waiter" do
44
+ em do
45
+ @atc.wait(1).should be_false
46
+ @cond.signal
47
+ @atc.wait(1).should be_true
48
+ done
49
+ end
50
+ end
51
+ end
52
+ context "with multiple waiters" do
53
+ before(:all) do
54
+ @cond = described_class.new
55
+ @atc = Atc.new :timeout => 0.01
56
+ Strand.new{ @cond.wait; @atc.signal(1) }
57
+ Strand.new{ @cond.wait; @atc.signal(2) }
58
+ end
59
+ it "should wake up the first waiter" do
60
+ em do
61
+ @atc.wait(1).should be_false
62
+ @atc.wait(2).should be_false
63
+ @cond.signal
64
+ @atc.wait(1).should be_true
65
+ @atc.wait(2).should be_false
66
+ done
67
+ end
68
+ end
69
+ it "then wake up the second waiter" do
70
+ em do
71
+ @atc.wait(1).should be_true
72
+ @atc.wait(2).should be_false
73
+ @cond.signal
74
+ @atc.wait(1).should be_true
75
+ @atc.wait(2).should be_true
76
+ done
77
+ end
78
+ end
79
+ end
80
+ end
81
+
82
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ require 'strand'
4
+ Bundler.require(:development)
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}
9
+
10
+ RSpec.configure do |config|
11
+ config.mock_with :rr
12
+ end
13
+
14
+ require "strand/atc"
15
+ Atc = Strand::Atc
16
+
17
+ require "em-spec/rspec"
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'test/unit'
11
+
12
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
13
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
14
+ require 'strand'
15
+
16
+ class Test::Unit::TestCase
17
+
18
+ def self.test(name, &block)
19
+ name = "test_#{name}" unless name[0,5] == "test_"
20
+ define_method(name) do
21
+ EM.run do
22
+ Fiber.new do
23
+ instance_eval(&block)
24
+ EM.stop
25
+ end.resume
26
+ end
27
+ end
28
+ end
29
+
30
+ end
@@ -0,0 +1,121 @@
1
+ require 'helper'
2
+
3
+ class TestStrand < Test::Unit::TestCase
4
+
5
+ test "em" do
6
+ results = []
7
+
8
+ s1 = Strand.new do
9
+ results << "1a"
10
+ Strand.sleep(0.1)
11
+ results << "1b"
12
+ Strand.sleep(0.1)
13
+ results << "1c"
14
+ end
15
+
16
+ s2 = Strand.new do
17
+ results << "2a"
18
+ Strand.sleep(0.1)
19
+ results << "2b"
20
+ Strand.sleep(0.1)
21
+ results << "2c"
22
+ end
23
+
24
+ s1.join
25
+ s2.join
26
+
27
+ assert_equal false, s1.alive?
28
+ assert_equal false, s2.alive?
29
+ assert_equal %w[1a 2a 1b 2b 1c 2c], results
30
+ end
31
+
32
+ test "yield" do
33
+ results = []
34
+
35
+ s1 = Strand.new do
36
+ results << "1a"
37
+ Strand.pass
38
+ results << "1b"
39
+ Strand.pass
40
+ results << "1c"
41
+ end
42
+
43
+ s2 = Strand.new do
44
+ results << "2a"
45
+ Strand.pass
46
+ results << "2b"
47
+ Strand.pass
48
+ results << "2c"
49
+ end
50
+
51
+ s1.join
52
+ s2.join
53
+
54
+ assert_equal false, s1.alive?
55
+ assert_equal false, s2.alive?
56
+ assert_equal %w[1a 2a 1b 2b 1c 2c], results
57
+ end
58
+
59
+ test "improper_yield" do
60
+ test_fiber = Fiber.current
61
+ strand = Strand.new do
62
+ fiber = Fiber.current
63
+ EM::Timer.new(0.01) do
64
+ assert_raise(FiberError){ fiber.resume }
65
+ test_fiber.resume
66
+ end
67
+ Strand.pass
68
+ end
69
+
70
+ assert strand.join
71
+ Fiber.yield
72
+ end
73
+
74
+ test "improper_pass" do
75
+ fiber = nil
76
+ strand = Strand.new do
77
+ fiber = Fiber.current
78
+ Strand.pass
79
+ Strand.yield
80
+ end
81
+ fiber.resume
82
+ end
83
+
84
+ test "strand_list" do
85
+ Strand.new do
86
+ Strand.new{ Strand.yield }
87
+ assert Strand.list.inspect =~ /#<Strand:0x(.+) run>, #<Strand:0x(.+) yielded>/
88
+ end
89
+ end
90
+
91
+ test "wait" do
92
+ x = nil
93
+ cond = Strand::ConditionVariable.new
94
+ Strand.new{ cond.wait; x = 1; cond.signal }
95
+ assert_nil x
96
+ cond.signal
97
+ cond.wait
98
+ assert_equal 1, x
99
+ end
100
+
101
+ test "wait_timeout" do
102
+ x = nil
103
+ cond = Strand::ConditionVariable.new
104
+ Strand.new{ cond.wait; x = 1; cond.signal }
105
+ assert_nil x
106
+ cond.wait(0.01)
107
+ assert_nil x
108
+ end
109
+
110
+ test "signal" do
111
+ result = []
112
+ cond = Strand::ConditionVariable.new
113
+ strand = Strand.new{ result << 1; cond.wait; result << 2 }
114
+ result << 3
115
+ cond.signal
116
+ result << 4
117
+ strand.join
118
+ assert_equal [1, 3, 4, 2], result
119
+ end
120
+
121
+ end
metadata ADDED
@@ -0,0 +1,142 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: strand
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Christopher J. Bottaro
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2011-08-24 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: &70284397102060 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.0
22
+ type: :development
23
+ prerelease: false
24
+ version_requirements: *70284397102060
25
+ - !ruby/object:Gem::Dependency
26
+ name: em-spec
27
+ requirement: &70284397100280 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: *70284397100280
36
+ - !ruby/object:Gem::Dependency
37
+ name: eventmachine
38
+ requirement: &70284397098140 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: 0.12.0
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70284397098140
47
+ - !ruby/object:Gem::Dependency
48
+ name: jeweler
49
+ requirement: &70284397153900 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: 1.6.4
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70284397153900
58
+ - !ruby/object:Gem::Dependency
59
+ name: rcov
60
+ requirement: &70284397151160 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70284397151160
69
+ - !ruby/object:Gem::Dependency
70
+ name: rr
71
+ requirement: &70284397150240 !ruby/object:Gem::Requirement
72
+ none: false
73
+ requirements:
74
+ - - ! '>='
75
+ - !ruby/object:Gem::Version
76
+ version: '0'
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: *70284397150240
80
+ - !ruby/object:Gem::Dependency
81
+ name: rspec
82
+ requirement: &70284397293200 !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ type: :development
89
+ prerelease: false
90
+ version_requirements: *70284397293200
91
+ description: Get thread-like behavior from fibers using EventMachine.
92
+ email: cjbottaro@alumni.cs.utexas.edu
93
+ executables: []
94
+ extensions: []
95
+ extra_rdoc_files:
96
+ - LICENSE.txt
97
+ - README.rdoc
98
+ files:
99
+ - .document
100
+ - .rspec
101
+ - Gemfile
102
+ - Gemfile.lock
103
+ - LICENSE.txt
104
+ - README.rdoc
105
+ - Rakefile
106
+ - VERSION
107
+ - lib/strand.rb
108
+ - lib/strand/atc.rb
109
+ - lib/strand/condition_variable.rb
110
+ - spec/condition_variable_spec.rb
111
+ - spec/spec_helper.rb
112
+ - test/helper.rb
113
+ - test/test_strand.rb
114
+ homepage: http://github.com/cjbottaro/strand
115
+ licenses:
116
+ - MIT
117
+ post_install_message:
118
+ rdoc_options: []
119
+ require_paths:
120
+ - lib
121
+ required_ruby_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ! '>='
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ segments:
128
+ - 0
129
+ hash: -933947513672593859
130
+ required_rubygems_version: !ruby/object:Gem::Requirement
131
+ none: false
132
+ requirements:
133
+ - - ! '>='
134
+ - !ruby/object:Gem::Version
135
+ version: '0'
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.7
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: Make fibers behave like threads using EventMachine.
142
+ test_files: []