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
data/.gitignore
ADDED
data/CHANGELOG
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
== 0.2.0
|
2
|
+
New features
|
3
|
+
* Strand now implements many more ::Thread methods and equivalents for ::Queue and ::Mutex
|
4
|
+
* Strand is now a module that behaves like a class. Strand.new will return a ::Thread or a Strand::EM::Thread
|
5
|
+
depending on whether it is running within the EventMachine reactor or not
|
6
|
+
|
7
|
+
Changes
|
8
|
+
* EventMachine is now an optional dependency (because it can work without EM)
|
9
|
+
* Strand.yield and Strand.resume can now take arguments (like Fiber)
|
10
|
+
* Final call to Strand.resume will return strand's value (or exception)
|
11
|
+
* Normalize access to strand local variables (converts everything to symbols interally)
|
12
|
+
|
13
|
+
== 0.1.0 - Initial version
|
data/Gemfile
CHANGED
@@ -1,16 +1,4 @@
|
|
1
|
-
source
|
2
|
-
# Add dependencies required to use your gem here.
|
3
|
-
# Example:
|
4
|
-
# gem "activesupport", ">= 2.3.5"
|
1
|
+
source 'https://rubygems.org'
|
5
2
|
|
6
|
-
#
|
7
|
-
|
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
|
3
|
+
# Specify your gem's dependencies in strand.gemspec
|
4
|
+
gemspec
|
data/LICENSE.txt
CHANGED
data/README.rdoc
CHANGED
@@ -1,12 +1,12 @@
|
|
1
1
|
= strand
|
2
2
|
|
3
|
-
Strand is a
|
3
|
+
Strand is a module that provides Thread-like behavior for Fibers in EventMachine.
|
4
4
|
|
5
|
-
|
5
|
+
http://rubygems.org/gems/strand
|
6
6
|
|
7
7
|
== Like Threads
|
8
8
|
|
9
|
-
Strand has an interface
|
9
|
+
Strand has an interface identical to Thread wherever possible. The specs for Strand are based on the ruby-spec for Thread
|
10
10
|
|
11
11
|
thread = Thread.new{ 1+2 }
|
12
12
|
thread.join
|
@@ -18,15 +18,7 @@ Strand has an interface similar to Thread.
|
|
18
18
|
|
19
19
|
== Fiber/EventMachine aware sleep
|
20
20
|
|
21
|
-
Calling 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.
|
21
|
+
Calling ruby's Kernel.sleep will block the EventMachine reactor, but Strand defines an EM+Fiber safe sleep.
|
30
22
|
|
31
23
|
Assuming that EventMachine is running:
|
32
24
|
|
@@ -34,8 +26,11 @@ Assuming that EventMachine is running:
|
|
34
26
|
s2 = Strand.new{ Strand.sleep(1) }
|
35
27
|
s1.join
|
36
28
|
s2.join
|
29
|
+
|
37
30
|
# Roughly 1 second will have passed.
|
38
31
|
|
32
|
+
When run outside of the EventMachine reactor, Strand delegates to ruby's native ::Thread so the above code would still work as expected.
|
33
|
+
|
39
34
|
== Thread local, Fiber local and Strand local variables
|
40
35
|
|
41
36
|
Thread local storage is the same as Fiber local storage in Ruby.
|
@@ -152,10 +147,6 @@ The output is:
|
|
152
147
|
3
|
153
148
|
3
|
154
149
|
|
155
|
-
== RDoc
|
156
|
-
|
157
|
-
{\http://doc.stochasticbytes.com/strand/index.html}[http://doc.stochasticbytes.com/strand/index.html]
|
158
|
-
|
159
150
|
== Contributing to strand
|
160
151
|
|
161
152
|
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
@@ -163,11 +154,13 @@ The output is:
|
|
163
154
|
* Fork the project
|
164
155
|
* Start a feature/bugfix branch
|
165
156
|
* Commit and push until you are happy with your contribution
|
166
|
-
* Make sure to add
|
157
|
+
* Make sure to add specs, preferably based on ruby-spec
|
167
158
|
* 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
159
|
|
169
160
|
== Copyright
|
170
161
|
|
171
|
-
Copyright (c) 2011 Christopher J. Bottaro.
|
172
|
-
|
162
|
+
Copyright (c) 2011 Christopher J. Bottaro.
|
163
|
+
Copyright (c) 2012 Grant Gardner.
|
164
|
+
|
165
|
+
See LICENSE.txt for further details.
|
173
166
|
|
data/Rakefile
CHANGED
@@ -1,64 +1,17 @@
|
|
1
|
-
|
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
|
1
|
+
require "bundler/gem_tasks"
|
42
2
|
|
43
3
|
require 'rspec/core'
|
44
4
|
require 'rspec/core/rake_task'
|
45
|
-
|
46
|
-
|
47
|
-
end
|
5
|
+
require 'rdoc/task'
|
6
|
+
require 'rake/clean'
|
48
7
|
|
49
|
-
RSpec::Core::RakeTask.new(:
|
50
|
-
spec.pattern = 'spec/**/*_spec.rb'
|
51
|
-
spec.rcov = true
|
52
|
-
end
|
8
|
+
RSpec::Core::RakeTask.new(:spec)
|
53
9
|
|
54
|
-
|
10
|
+
RDoc::Task.new do |rdoc|
|
11
|
+
rdoc.main = "README.rdoc"
|
12
|
+
rdoc.rdoc_files.include("README.rdoc", "CHANGELOG","lib/**/*.rb")
|
13
|
+
rdoc.title = "Strand"
|
14
|
+
end
|
55
15
|
|
56
|
-
|
57
|
-
Rake::RDocTask.new do |rdoc|
|
58
|
-
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
16
|
+
CLOBBER.include [ "pkg/" ]
|
59
17
|
|
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/lib/strand.rb
CHANGED
@@ -1,132 +1,157 @@
|
|
1
|
-
require
|
2
|
-
require
|
3
|
-
require
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
1
|
+
require 'strand/version'
|
2
|
+
require 'thread'
|
3
|
+
require 'fiber'
|
4
|
+
|
5
|
+
# This module provides a shim between using standard ruby Threads
|
6
|
+
# and the thread-like behaviour for Fibers provided by classes in
|
7
|
+
# the Strand::EM module
|
8
|
+
#
|
9
|
+
# # For the Strand::EM classes to be available
|
10
|
+
# # you must first load EventMachine
|
11
|
+
# 'require eventmachine'
|
12
|
+
#
|
13
|
+
# 'require strand'
|
14
|
+
#
|
15
|
+
# t = Strand.new() do
|
16
|
+
# # "t" is a standard ::Thread
|
17
|
+
# ...something...
|
18
|
+
# end
|
19
|
+
#
|
20
|
+
# EventMachine.run do
|
21
|
+
# t = Strand.new() do
|
22
|
+
# # "t" is a ::Strand::EM::Thread
|
23
|
+
# # which wraps a ::Fiber
|
24
|
+
# end
|
25
|
+
# end
|
26
|
+
#
|
27
|
+
# # Outside of event machine
|
28
|
+
# t = Strand.new() do
|
29
|
+
# # "t" is a raw ::Thread
|
30
|
+
# end
|
31
|
+
#
|
32
|
+
# Code using Strand that may be used in both Fiber or Thread contexts
|
33
|
+
# should take care to rescue both {FiberError} and {ThreadError} in any
|
34
|
+
# exception handling code
|
35
|
+
#
|
36
|
+
# {::Thread} methods not implemented by Strand
|
37
|
+
# * .main - although there is a root fiber it is not really equivalent to {::Thread.main}
|
38
|
+
# * .start - not implemented
|
39
|
+
# * #exclusive - not implemented
|
40
|
+
# * #critical - not implemented
|
41
|
+
# * #set_trace_func - not implemented
|
42
|
+
# * #safe_level - not implemented
|
43
|
+
# * #priority - not implemented
|
44
|
+
module Strand
|
45
|
+
|
46
|
+
# Test whether we have real fibers or a thread based fiber implmentation
|
47
|
+
t = Thread.current
|
48
|
+
ft = nil
|
49
|
+
Fiber.new { ft = Thread.current }.resume
|
50
|
+
|
51
|
+
ROOT_FIBER = Fiber.current
|
52
|
+
REAL_FIBERS = ( t == ft )
|
53
|
+
|
54
|
+
# Specifically try to enable use of Eventmachine if it is now available
|
55
|
+
def self.reload()
|
56
|
+
@em_class_map = if defined?(EventMachine) then enable_eventmachine() else nil end
|
120
57
|
end
|
121
58
|
|
122
|
-
#
|
123
|
-
|
59
|
+
# Private
|
60
|
+
def self.enable_eventmachine
|
61
|
+
return false unless defined?(EventMachine)
|
62
|
+
|
63
|
+
require 'strand/em/thread.rb'
|
64
|
+
require 'strand/em/queue.rb'
|
65
|
+
|
66
|
+
# Classes if eventmachine has been previously loaded
|
67
|
+
{
|
68
|
+
::Thread => Strand::EM::Thread,
|
69
|
+
::Kernel => Strand::EM::Thread,
|
70
|
+
::Mutex => Strand::EM::Mutex,
|
71
|
+
::ConditionVariable => Strand::EM::ConditionVariable,
|
72
|
+
::Queue => Strand::EM::Queue
|
73
|
+
}
|
74
|
+
end
|
75
|
+
|
76
|
+
# If EM already required then enable it, otherwise defer until first use
|
77
|
+
reload()
|
78
|
+
|
79
|
+
# Are we running in the EventMachine reactor thread
|
80
|
+
#
|
81
|
+
# For JRuby or other interpreters where fibers are implemented with threads
|
82
|
+
# this will return true if the reactor is running and the code is called from
|
83
|
+
# *any* fiber other than the root fiber
|
84
|
+
def self.event_machine?
|
85
|
+
@em_class_map = enable_eventmachine() if @em_class_map.nil?
|
86
|
+
|
87
|
+
@em_class_map && EventMachine.reactor_running? &&
|
88
|
+
( EventMachine.reactor_thread? || (!REAL_FIBERS && ROOT_FIBER != Fiber.current))
|
89
|
+
end
|
90
|
+
|
91
|
+
def self.delegate_class(class_key)
|
92
|
+
if self.event_machine? then @em_class_map[class_key] else class_key end
|
93
|
+
end
|
94
|
+
|
95
|
+
# ::Thread::list or EM::Thread::list
|
96
|
+
def self.list
|
97
|
+
delegate_class(::Thread).list()
|
98
|
+
end
|
99
|
+
|
100
|
+
# ::Thread::current or EM::Thread::current
|
101
|
+
def self.current
|
102
|
+
delegate_class(::Thread).current()
|
103
|
+
end
|
104
|
+
|
105
|
+
# ::Kernel::sleep or EM::Thread::sleep
|
106
|
+
# Note that passing nil will sleep forever (where Kernel.sleep raises TypeError)
|
107
|
+
def self.sleep(*args)
|
108
|
+
# Kernel.sleep treats nil as an error, but we use it to indicate sleeping forever
|
109
|
+
args.shift if args.length == 1 and args.first.nil?
|
110
|
+
delegate_class(::Kernel).sleep(*args)
|
111
|
+
end
|
112
|
+
|
113
|
+
# ::Thread::stop or EM::Thread::stop
|
114
|
+
def self.stop()
|
115
|
+
delegate_class(::Thread).stop()
|
116
|
+
end
|
117
|
+
|
118
|
+
# ::Thread::pass or EM::Thread::pass
|
119
|
+
def self.pass()
|
120
|
+
delegate_class(::Thread).pass()
|
121
|
+
end
|
122
|
+
|
123
|
+
#TODO Is there some equivalence between the root Fiber and Thread.main?
|
124
|
+
|
125
|
+
# Convenience to call Fiber.yield. Note the warning on EM::Thread::yield
|
126
|
+
def self.yield(*args)
|
127
|
+
Fiber.yield(*args)
|
128
|
+
end
|
129
|
+
|
130
|
+
# Behave like a class returning
|
131
|
+
# an instance of ::Thread, or ::Strand::EM::Thread based on
|
132
|
+
# whether the caller is within the EventMachine reactor
|
133
|
+
def self.new(*args,&block)
|
134
|
+
delegate_class(::Thread).new(*args,&block)
|
135
|
+
end
|
124
136
|
|
125
|
-
|
126
|
-
|
137
|
+
module Mutex
|
138
|
+
# Return a new ::Mutex or EM::Mutex
|
139
|
+
def self.new(*args,&block)
|
140
|
+
Strand.delegate_class(::Mutex).new(*args,&block)
|
141
|
+
end
|
142
|
+
end
|
127
143
|
|
128
|
-
|
129
|
-
|
130
|
-
|
144
|
+
module ConditionVariable
|
145
|
+
# Return a new ::ConditionVariable or EM::ConditionVariable
|
146
|
+
def self.new(*args,&block)
|
147
|
+
Strand.delegate_class(::ConditionVariable).new(*args,&block)
|
148
|
+
end
|
149
|
+
end
|
131
150
|
|
151
|
+
module Queue
|
152
|
+
# Return a new ::Queue or EM::Queue
|
153
|
+
def self.new(*args,&block)
|
154
|
+
Strand.delegate_class(::Queue).new(*args,&block)
|
155
|
+
end
|
156
|
+
end
|
132
157
|
end
|