concurrent-ruby 0.0.1 → 0.1.0
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.
- checksums.yaml +4 -4
- data/README.md +223 -3
- data/lib/concurrent.rb +19 -0
- data/lib/concurrent/agent.rb +130 -0
- data/lib/concurrent/cached_thread_pool.rb +122 -0
- data/lib/concurrent/defer.rb +69 -0
- data/lib/concurrent/event.rb +60 -0
- data/lib/concurrent/event_machine_defer_proxy.rb +23 -0
- data/lib/concurrent/fixed_thread_pool.rb +89 -0
- data/lib/concurrent/future.rb +42 -0
- data/lib/concurrent/global_thread_pool.rb +3 -0
- data/lib/concurrent/goroutine.rb +25 -0
- data/lib/concurrent/obligation.rb +121 -0
- data/lib/concurrent/promise.rb +194 -0
- data/lib/concurrent/thread_pool.rb +61 -0
- data/lib/concurrent/version.rb +1 -1
- data/lib/concurrent_ruby.rb +1 -0
- data/md/agent.md +123 -0
- data/md/defer.md +174 -0
- data/md/event.md +32 -0
- data/md/future.md +83 -0
- data/md/goroutine.md +52 -0
- data/md/obligation.md +32 -0
- data/md/promise.md +225 -0
- data/md/thread_pool.md +197 -0
- data/spec/concurrent/agent_spec.rb +405 -0
- data/spec/concurrent/cached_thread_pool_spec.rb +112 -0
- data/spec/concurrent/defer_spec.rb +199 -0
- data/spec/concurrent/event_machine_defer_proxy_spec.rb +246 -0
- data/spec/concurrent/event_spec.rb +134 -0
- data/spec/concurrent/fixed_thread_pool_spec.rb +84 -0
- data/spec/concurrent/future_spec.rb +115 -0
- data/spec/concurrent/goroutine_spec.rb +52 -0
- data/spec/concurrent/obligation_shared.rb +121 -0
- data/spec/concurrent/promise_spec.rb +310 -0
- data/spec/concurrent/thread_pool_shared.rb +209 -0
- data/spec/spec_helper.rb +2 -0
- metadata +61 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 3f58685d3bea0c67b29987579c607417ba905790
|
4
|
+
data.tar.gz: 82ab333727880726e30369350eaca336a81c5f2c
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 34d8085f8ca406fd76ecb34d78c84b8faff82f49deb52095c00093f54300beb2a024e5b3dc9bd376aaa317cb7f126bcae2a7a2b87012ef975fa9c4760b23a206
|
7
|
+
data.tar.gz: f707b25361f41d930383a4e12381fbd511966a991cb68c5ae97db9fe9494278b78e165bf9f3aadbd3341c9a728c1478d74ae5a8db1482248ce0ed58d34468e33
|
data/README.md
CHANGED
@@ -1,4 +1,224 @@
|
|
1
|
-
# Concurrent Ruby
|
1
|
+
# Concurrent Ruby [](https://travis-ci.org/jdantonio/concurrent-ruby?branch=master) [](https://gemnasium.com/jdantonio/concurrent-ruby)
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
Erlang, Clojure, and Go inspired concurrent programming tools for Ruby.
|
4
|
+
|
5
|
+
## Introduction
|
6
|
+
|
7
|
+
The old-school "lock and synchronize" approach to concurrency is dead. The future of concurrency
|
8
|
+
is asynchronous. Send out a bunch of independent [actors](http://en.wikipedia.org/wiki/Actor_model)
|
9
|
+
to do your bidding and process the results when you are ready. Although the idea of the concurrent
|
10
|
+
actor originated in the early 1970's it has only recently started catching on. Although there is
|
11
|
+
no one "true" actor implementation (what *exactly* is "object oriented," what *exactly* is
|
12
|
+
"concurrent programming"), many modern programming languages implement variations on the actor
|
13
|
+
theme. This library implements a few of the most interesting and useful of those variations.
|
14
|
+
|
15
|
+
Remember, *there is no silver bullet in concurrent programming.* Concurrency is hard. Very hard.
|
16
|
+
These tools will help ease the burden, but at the end of the day it is essential that you
|
17
|
+
*know what you are doing.*
|
18
|
+
|
19
|
+
The project is hosted on the following sites:
|
20
|
+
|
21
|
+
* [RubyGems project page](https://rubygems.org/gems/concurrent-ruby)
|
22
|
+
* [Source code on GitHub](https://github.com/jdantonio/concurrent-ruby)
|
23
|
+
* [YARD documentation on RubyDoc.info](http://rubydoc.info/github/jdantonio/concurrent-ruby/frames)
|
24
|
+
* [Continuous integration on Travis-CI](https://travis-ci.org/jdantonio/concurrent-ruby)
|
25
|
+
* [Dependency tracking on Gemnasium](https://gemnasium.com/jdantonio/concurrent-ruby)
|
26
|
+
* [Follow me on Twitter](https://twitter.com/jerrydantonio)
|
27
|
+
|
28
|
+
### Goals
|
29
|
+
|
30
|
+
My history with high-performance, highly-concurrent programming goes back to my days with C/C++.
|
31
|
+
I have the same scars as everyone else doing that kind of work with those languages.
|
32
|
+
I'm fascinated by modern concurrency patterns like [Actors](http://en.wikipedia.org/wiki/Actor_model),
|
33
|
+
[Agents](http://doc.akka.io/docs/akka/snapshot/java/agents.html), and
|
34
|
+
[Promises](http://promises-aplus.github.io/promises-spec/). I'm equally fascinated by languages
|
35
|
+
with strong concurrency support like [Erlang](http://www.erlang.org/doc/getting_started/conc_prog.html),
|
36
|
+
[Go](http://golang.org/doc/articles/concurrency_patterns.html), and
|
37
|
+
[Clojure](http://clojure.org/concurrent_programming). My goal is to implement those patterns in Ruby.
|
38
|
+
Specifically:
|
39
|
+
|
40
|
+
* Stay true to the spirit of the languages providing inspiration
|
41
|
+
* But implement in a way that makes sense for Ruby
|
42
|
+
* Keep the semantics as idiomatic Ruby as possible
|
43
|
+
* Support features that make sense in Ruby
|
44
|
+
* Exclude features that don't make sense in Ruby
|
45
|
+
* Keep everything small
|
46
|
+
* Be as fast as reasonably possible
|
47
|
+
|
48
|
+
## Features (and Documentation)
|
49
|
+
|
50
|
+
Several features from Erlang, Go, Clojure, Java, and JavaScript have been implemented thus far:
|
51
|
+
|
52
|
+
* Clojure inspired [Agent](https://github.com/jdantonio/concurrent-ruby/blob/master/md/agent.md)
|
53
|
+
* EventMachine inspired [Defer](https://github.com/jdantonio/concurrent-ruby/blob/master/md/defer.md)
|
54
|
+
* Clojure inspired [Future](https://github.com/jdantonio/concurrent-ruby/blob/master/md/future.md)
|
55
|
+
* Go inspired [Goroutine](https://github.com/jdantonio/concurrent-ruby/blob/master/md/goroutine.md)
|
56
|
+
* JavaScript inspired [Promise](https://github.com/jdantonio/concurrent-ruby/blob/master/md/promise.md)
|
57
|
+
* Java inspired [Thread Pools](https://github.com/jdantonio/concurrent-ruby/blob/master/md/thread_pool.md)
|
58
|
+
|
59
|
+
### Is it any good?
|
60
|
+
|
61
|
+
[Yes](http://news.ycombinator.com/item?id=3067434)
|
62
|
+
|
63
|
+
### Supported Ruby versions
|
64
|
+
|
65
|
+
MRI 1.9.2, 1.9.3, and 2.0. This library is pure Ruby and has no gem dependencies. It should be
|
66
|
+
fully compatible with any Ruby interpreter that is 1.9.x compliant. I simply don't know enough
|
67
|
+
about JRuby, Rubinius, or the others to fully support them. I can promise good karma and
|
68
|
+
attribution on this page to anyone wishing to take responsibility for verifying compaitibility
|
69
|
+
with any Ruby other than MRI.
|
70
|
+
|
71
|
+
### Install
|
72
|
+
|
73
|
+
```shell
|
74
|
+
gem install concurrent-ruby
|
75
|
+
```
|
76
|
+
|
77
|
+
or add the following line to Gemfile:
|
78
|
+
|
79
|
+
```ruby
|
80
|
+
gem 'concurrent-ruby'
|
81
|
+
```
|
82
|
+
|
83
|
+
and run `bundle install` from your shell.
|
84
|
+
|
85
|
+
Once you've installed the gem you must `require` it in your project:
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
require 'concurrent'
|
89
|
+
```
|
90
|
+
|
91
|
+
### Examples
|
92
|
+
|
93
|
+
For complete examples, see the specific documentation linked above. Below are a few examples to whet your appetite.
|
94
|
+
|
95
|
+
#### Goroutine (Go)
|
96
|
+
|
97
|
+
```ruby
|
98
|
+
require 'concurrent'
|
99
|
+
|
100
|
+
@expected = nil
|
101
|
+
go(1, 2, 3){|a, b, c| @expected = [c, b, a] }
|
102
|
+
sleep(0.1)
|
103
|
+
@expected #=> [3, 2, 1]
|
104
|
+
```
|
105
|
+
|
106
|
+
#### Agent (Clojure)
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
require 'concurrent'
|
110
|
+
|
111
|
+
score = agent(10)
|
112
|
+
score.value #=> 10
|
113
|
+
|
114
|
+
score << proc{|current| current + 100 }
|
115
|
+
sleep(0.1)
|
116
|
+
score.value #=> 110
|
117
|
+
|
118
|
+
score << proc{|current| current * 2 }
|
119
|
+
sleep(0.1)
|
120
|
+
deref score #=> 220
|
121
|
+
|
122
|
+
score << proc{|current| current - 50 }
|
123
|
+
sleep(0.1)
|
124
|
+
score.value #=> 170
|
125
|
+
```
|
126
|
+
|
127
|
+
#### Defer (EventMachine)
|
128
|
+
|
129
|
+
```ruby
|
130
|
+
require 'concurrent'
|
131
|
+
|
132
|
+
Concurrent::Defer.new{ "Jerry D'Antonio" }.
|
133
|
+
then{|result| puts "Hello, #{result}!" }.
|
134
|
+
rescue{|ex| puts ex.message }.
|
135
|
+
go
|
136
|
+
|
137
|
+
#=> Hello, Jerry D'Antonio!
|
138
|
+
|
139
|
+
operation = proc{ raise StandardError.new('Boom!') }
|
140
|
+
callback = proc{|result| puts result }
|
141
|
+
errorback = proc{|ex| puts ex.message }
|
142
|
+
defer(operation, callback, errorback)
|
143
|
+
sleep(0.1)
|
144
|
+
|
145
|
+
#=> "Boom!"
|
146
|
+
```
|
147
|
+
|
148
|
+
#### Future (Clojure)
|
149
|
+
|
150
|
+
```ruby
|
151
|
+
require 'concurrent'
|
152
|
+
|
153
|
+
count = future{ sleep(1); 10 }
|
154
|
+
count.state #=> :pending
|
155
|
+
# do stuff...
|
156
|
+
count.value #=> 10 (after blocking)
|
157
|
+
deref count #=> 10
|
158
|
+
```
|
159
|
+
|
160
|
+
#### Promise (JavaScript)
|
161
|
+
|
162
|
+
```ruby
|
163
|
+
require 'concurrent'
|
164
|
+
|
165
|
+
p = promise("Jerry", "D'Antonio"){|a, b| "#{a} #{b}" }.
|
166
|
+
then{|result| "Hello #{result}." }.
|
167
|
+
rescue(StandardError){|ex| puts "Boom!" }.
|
168
|
+
then{|result| "#{result} Would you like to play a game?"}
|
169
|
+
sleep(1)
|
170
|
+
p.value #=> "Hello Jerry D'Antonio. Would you like to play a game?"
|
171
|
+
```
|
172
|
+
|
173
|
+
#### Thread Pools
|
174
|
+
|
175
|
+
```ruby
|
176
|
+
require 'concurrent'
|
177
|
+
|
178
|
+
pool = Concurrent::FixedThreadPool.new(10)
|
179
|
+
@expected = 0
|
180
|
+
pool.post{ sleep(0.5); @expected += 100 }
|
181
|
+
pool.post{ sleep(0.5); @expected += 100 }
|
182
|
+
pool.post{ sleep(0.5); @expected += 100 }
|
183
|
+
@expected #=> nil
|
184
|
+
sleep(1)
|
185
|
+
@expected #=> 300
|
186
|
+
|
187
|
+
pool = Concurrent::CachedThreadPool.new
|
188
|
+
@expected = 0
|
189
|
+
pool << proc{ sleep(0.5); @expected += 10 }
|
190
|
+
pool << proc{ sleep(0.5); @expected += 10 }
|
191
|
+
pool << proc{ sleep(0.5); @expected += 10 }
|
192
|
+
@expected #=> 0
|
193
|
+
sleep(1)
|
194
|
+
@expected #=> 30
|
195
|
+
```
|
196
|
+
|
197
|
+
## Copyright
|
198
|
+
|
199
|
+
*Concurrent Ruby* is Copyright © 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
|
200
|
+
It is free software and may be redistributed under the terms specified in the LICENSE file.
|
201
|
+
|
202
|
+
## License
|
203
|
+
|
204
|
+
Released under the MIT license.
|
205
|
+
|
206
|
+
http://www.opensource.org/licenses/mit-license.php
|
207
|
+
|
208
|
+
> Permission is hereby granted, free of charge, to any person obtaining a copy
|
209
|
+
> of this software and associated documentation files (the "Software"), to deal
|
210
|
+
> in the Software without restriction, including without limitation the rights
|
211
|
+
> to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
212
|
+
> copies of the Software, and to permit persons to whom the Software is
|
213
|
+
> furnished to do so, subject to the following conditions:
|
214
|
+
>
|
215
|
+
> The above copyright notice and this permission notice shall be included in
|
216
|
+
> all copies or substantial portions of the Software.
|
217
|
+
>
|
218
|
+
> THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
219
|
+
> IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
220
|
+
> FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
221
|
+
> AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
222
|
+
> LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
223
|
+
> OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
224
|
+
> THE SOFTWARE.
|
data/lib/concurrent.rb
CHANGED
@@ -1 +1,20 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
1
3
|
require 'concurrent/version'
|
4
|
+
|
5
|
+
require 'concurrent/event'
|
6
|
+
|
7
|
+
require 'concurrent/agent'
|
8
|
+
require 'concurrent/defer'
|
9
|
+
require 'concurrent/future'
|
10
|
+
require 'concurrent/goroutine'
|
11
|
+
require 'concurrent/promise'
|
12
|
+
require 'concurrent/obligation'
|
13
|
+
|
14
|
+
require 'concurrent/thread_pool'
|
15
|
+
require 'concurrent/cached_thread_pool'
|
16
|
+
require 'concurrent/fixed_thread_pool'
|
17
|
+
|
18
|
+
require 'concurrent/global_thread_pool'
|
19
|
+
|
20
|
+
require 'concurrent/event_machine_defer_proxy' if defined?(EventMachine)
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'observer'
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
require 'concurrent/global_thread_pool'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
# An agent is a single atomic value that represents an identity. The current value
|
9
|
+
# of the agent can be requested at any time (#deref). Each agent has a work queue and operates on
|
10
|
+
# the global thread pool. Consumers can #post code blocks to the agent. The code block (function)
|
11
|
+
# will receive the current value of the agent as its sole parameter. The return value of the block
|
12
|
+
# will become the new value of the agent. Agents support two error handling modes: fail and continue.
|
13
|
+
# A good example of an agent is a shared incrementing counter, such as the score in a video game.
|
14
|
+
class Agent
|
15
|
+
include Observable
|
16
|
+
|
17
|
+
TIMEOUT = 5
|
18
|
+
|
19
|
+
attr_reader :initial
|
20
|
+
attr_reader :timeout
|
21
|
+
|
22
|
+
def initialize(initial, timeout = TIMEOUT)
|
23
|
+
@value = initial
|
24
|
+
@timeout = timeout
|
25
|
+
@rescuers = []
|
26
|
+
@validator = nil
|
27
|
+
@queue = Queue.new
|
28
|
+
|
29
|
+
$GLOBAL_THREAD_POOL << proc{ work }
|
30
|
+
end
|
31
|
+
|
32
|
+
def value(timeout = 0) return @value; end
|
33
|
+
alias_method :deref, :value
|
34
|
+
|
35
|
+
def rescue(clazz = Exception, &block)
|
36
|
+
@rescuers << Rescuer.new(clazz, block) if block_given?
|
37
|
+
return self
|
38
|
+
end
|
39
|
+
alias_method :catch, :rescue
|
40
|
+
alias_method :on_error, :rescue
|
41
|
+
|
42
|
+
def validate(&block)
|
43
|
+
@validator = block if block_given?
|
44
|
+
return self
|
45
|
+
end
|
46
|
+
alias_method :validates, :validate
|
47
|
+
alias_method :validate_with, :validate
|
48
|
+
alias_method :validates_with, :validate
|
49
|
+
|
50
|
+
def post(&block)
|
51
|
+
return @queue.length unless block_given?
|
52
|
+
@queue << block
|
53
|
+
return @queue.length
|
54
|
+
end
|
55
|
+
|
56
|
+
def <<(block)
|
57
|
+
self.post(&block)
|
58
|
+
return self
|
59
|
+
end
|
60
|
+
|
61
|
+
def length
|
62
|
+
@queue.length
|
63
|
+
end
|
64
|
+
alias_method :size, :length
|
65
|
+
alias_method :count, :length
|
66
|
+
|
67
|
+
alias_method :add_watch, :add_observer
|
68
|
+
|
69
|
+
private
|
70
|
+
|
71
|
+
# @private
|
72
|
+
Rescuer = Struct.new(:clazz, :block)
|
73
|
+
|
74
|
+
# @private
|
75
|
+
def try_rescue(ex) # :nodoc:
|
76
|
+
rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
|
77
|
+
rescuer.block.call(ex) if rescuer
|
78
|
+
rescue Exception => e
|
79
|
+
# supress
|
80
|
+
end
|
81
|
+
|
82
|
+
# @private
|
83
|
+
def work # :nodoc:
|
84
|
+
loop do
|
85
|
+
Thread.pass
|
86
|
+
handler = @queue.pop
|
87
|
+
begin
|
88
|
+
result = Timeout.timeout(@timeout){
|
89
|
+
handler.call(@value)
|
90
|
+
}
|
91
|
+
if @validator.nil? || @validator.call(result)
|
92
|
+
@value = result
|
93
|
+
changed
|
94
|
+
notify_observers(Time.now, @value)
|
95
|
+
end
|
96
|
+
rescue Exception => ex
|
97
|
+
try_rescue(ex)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
module Kernel
|
105
|
+
|
106
|
+
def agent(initial, timeout = Concurrent::Agent::TIMEOUT)
|
107
|
+
return Concurrent::Agent.new(initial, timeout)
|
108
|
+
end
|
109
|
+
module_function :agent
|
110
|
+
|
111
|
+
def deref(agent, timeout = nil)
|
112
|
+
if agent.respond_to?(:deref)
|
113
|
+
return agent.deref(timeout)
|
114
|
+
elsif agent.respond_to?(:value)
|
115
|
+
return agent.deref(timeout)
|
116
|
+
else
|
117
|
+
return nil
|
118
|
+
end
|
119
|
+
end
|
120
|
+
module_function :deref
|
121
|
+
|
122
|
+
def post(agent, &block)
|
123
|
+
if agent.respond_to?(:post)
|
124
|
+
return agent.post(&block)
|
125
|
+
else
|
126
|
+
return nil
|
127
|
+
end
|
128
|
+
end
|
129
|
+
module_function :deref
|
130
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
require 'thread'
|
2
|
+
|
3
|
+
require 'concurrent/thread_pool'
|
4
|
+
require 'functional/utilities'
|
5
|
+
|
6
|
+
module Concurrent
|
7
|
+
|
8
|
+
def self.new_cached_thread_pool
|
9
|
+
return CachedThreadPool.new
|
10
|
+
end
|
11
|
+
|
12
|
+
class CachedThreadPool < ThreadPool
|
13
|
+
behavior(:thread_pool)
|
14
|
+
|
15
|
+
DEFAULT_GC_INTERVAL = 60
|
16
|
+
DEFAULT_THREAD_IDLETIME = 60
|
17
|
+
|
18
|
+
attr_reader :working
|
19
|
+
|
20
|
+
def initialize(opts = {})
|
21
|
+
@gc_interval = opts[:gc_interval] || DEFAULT_GC_INTERVAL
|
22
|
+
@thread_idletime = opts[:thread_idletime] || DEFAULT_THREAD_IDLETIME
|
23
|
+
super()
|
24
|
+
@working = 0
|
25
|
+
@mutex = Mutex.new
|
26
|
+
end
|
27
|
+
|
28
|
+
def kill
|
29
|
+
@status = :killed
|
30
|
+
@mutex.synchronize do
|
31
|
+
@pool.each{|t| Thread.kill(t.thread) }
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def size
|
36
|
+
return @pool.length
|
37
|
+
end
|
38
|
+
|
39
|
+
def post(*args, &block)
|
40
|
+
raise ArgumentError.new('no block given') unless block_given?
|
41
|
+
if running?
|
42
|
+
collect_garbage if @pool.empty?
|
43
|
+
@mutex.synchronize do
|
44
|
+
if @working >= @pool.length
|
45
|
+
create_worker_thread
|
46
|
+
end
|
47
|
+
@queue << [args, block]
|
48
|
+
end
|
49
|
+
return true
|
50
|
+
else
|
51
|
+
return false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# @private
|
56
|
+
def status # :nodoc:
|
57
|
+
@mutex.synchronize do
|
58
|
+
@pool.collect do |worker|
|
59
|
+
[
|
60
|
+
worker.status,
|
61
|
+
worker.status == :idle ? delta(worker.idletime, timestamp) : nil,
|
62
|
+
worker.thread.status
|
63
|
+
]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
Worker = Struct.new(:status, :idletime, :thread)
|
71
|
+
|
72
|
+
# @private
|
73
|
+
def create_worker_thread # :nodoc:
|
74
|
+
worker = Worker.new(:idle, timestamp, nil)
|
75
|
+
|
76
|
+
worker.thread = Thread.new(worker) do |me|
|
77
|
+
|
78
|
+
loop do
|
79
|
+
task = @queue.pop
|
80
|
+
|
81
|
+
@working += 1
|
82
|
+
me.status = :working
|
83
|
+
|
84
|
+
if task == :stop
|
85
|
+
me.status = :stopping
|
86
|
+
break
|
87
|
+
else
|
88
|
+
task.last.call(*task.first)
|
89
|
+
@working -= 1
|
90
|
+
me.status = :idle
|
91
|
+
me.idletime = timestamp
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
@pool.delete(me)
|
96
|
+
if @pool.empty?
|
97
|
+
@termination.set
|
98
|
+
@status = :shutdown unless killed?
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
@pool << worker
|
103
|
+
end
|
104
|
+
|
105
|
+
# @private
|
106
|
+
def collect_garbage # :nodoc:
|
107
|
+
@collector = Thread.new do
|
108
|
+
loop do
|
109
|
+
sleep(@gc_interval)
|
110
|
+
@mutex.synchronize do
|
111
|
+
@pool.reject! do |worker|
|
112
|
+
worker.thread.status.nil? ||
|
113
|
+
(worker.status == :idle && @thread_idletime >= delta(worker.idletime, timestamp))
|
114
|
+
end
|
115
|
+
end
|
116
|
+
@working = @pool.count{|worker| worker.status == :working}
|
117
|
+
break if @pool.empty?
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|