promise_pool 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 +7 -0
- data/.gitignore +2 -0
- data/.gitmodules +3 -0
- data/.travis.yml +15 -0
- data/Gemfile +15 -0
- data/README.md +67 -0
- data/Rakefile +14 -0
- data/lib/promise_pool.rb +5 -0
- data/lib/promise_pool/future.rb +12 -0
- data/lib/promise_pool/promise.rb +163 -0
- data/lib/promise_pool/promise_eager.rb +36 -0
- data/lib/promise_pool/queue.rb +36 -0
- data/lib/promise_pool/task.rb +18 -0
- data/lib/promise_pool/test.rb +7 -0
- data/lib/promise_pool/thread_pool.rb +70 -0
- data/lib/promise_pool/timer.rb +60 -0
- data/lib/promise_pool/version.rb +4 -0
- data/promise_pool.gemspec +59 -0
- data/task/README.md +54 -0
- data/task/gemgem.rb +316 -0
- data/test/test_pool.rb +74 -0
- data/test/test_promise.rb +34 -0
- data/test/test_promise_eager.rb +41 -0
- data/test/test_timer.rb +5 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: ba178cdd8145f5c65340a8599ffc9dbd4775ccef
|
4
|
+
data.tar.gz: a978b9d5072e8038f9cb2e6884432bc9e4f0a6ac
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: d531803ba5e8d28b438da3d152e8315ba7ce0de796ee781bcf0f0c2de23f7bf598bed2e9693d548ed05414cd8bd623b34f754588b1cb4ac3ac2bdf4dc898848e
|
7
|
+
data.tar.gz: 406aa62fec4d02adf8eaf0088a0ed8ac7e08a70cf147d01c8ab7d41cbc588541385ff4a1633d18fb3c6e848860a4cb7f9f28c9b8c361f358006071c0954b0257
|
data/.gitignore
ADDED
data/.gitmodules
ADDED
data/.travis.yml
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
sudo: false
|
2
|
+
language: ruby
|
3
|
+
rvm:
|
4
|
+
- 2.1
|
5
|
+
- 2.2
|
6
|
+
- 2.3.0
|
7
|
+
- rbx
|
8
|
+
- jruby-9
|
9
|
+
|
10
|
+
before_install:
|
11
|
+
- rvm get head
|
12
|
+
- rvm reload
|
13
|
+
- rvm use --install $TRAVIS_RUBY_VERSION --binary --latest
|
14
|
+
install: 'bundle install --retry=3'
|
15
|
+
script: 'ruby -vr bundler/setup -S rake test'
|
data/Gemfile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
|
2
|
+
source 'https://rubygems.org/'
|
3
|
+
|
4
|
+
gemspec
|
5
|
+
|
6
|
+
gem 'rake'
|
7
|
+
gem 'pork'
|
8
|
+
gem 'muack'
|
9
|
+
|
10
|
+
gem 'simplecov', :require => false if ENV['COV']
|
11
|
+
gem 'coveralls', :require => false if ENV['CI']
|
12
|
+
|
13
|
+
platforms :rbx do
|
14
|
+
gem 'rubysl-singleton' # used in rake
|
15
|
+
end
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# promise_pool [](http://travis-ci.org/godfat/promise_pool) [](https://coveralls.io/r/godfat/promise_pool) [](https://gitter.im/godfat/promise_pool)
|
2
|
+
|
3
|
+
by Lin Jen-Shin ([godfat](http://godfat.org))
|
4
|
+
|
5
|
+
## LINKS:
|
6
|
+
|
7
|
+
* [github](https://github.com/godfat/promise_pool)
|
8
|
+
* [rubygems](https://rubygems.org/gems/promise_pool)
|
9
|
+
* [rdoc](http://rdoc.info/projects/godfat/promise_pool)
|
10
|
+
|
11
|
+
## DESCRIPTION:
|
12
|
+
|
13
|
+
promise_pool
|
14
|
+
|
15
|
+
## FEATURES:
|
16
|
+
|
17
|
+
* promise_pool
|
18
|
+
|
19
|
+
## WHY?
|
20
|
+
|
21
|
+
promise_pool
|
22
|
+
|
23
|
+
## REQUIREMENTS:
|
24
|
+
|
25
|
+
* Tested with MRI (official CRuby), Rubinius and JRuby.
|
26
|
+
* gem [timers][]
|
27
|
+
|
28
|
+
[timers]: https://github.com/celluloid/timers
|
29
|
+
|
30
|
+
## INSTALLATION:
|
31
|
+
|
32
|
+
``` shell
|
33
|
+
gem install promise_pool
|
34
|
+
```
|
35
|
+
|
36
|
+
Or if you want development version, put this in Gemfile:
|
37
|
+
|
38
|
+
``` ruby
|
39
|
+
gem 'promise_pool', :git => 'git://github.com/godfat/promise_pool.git',
|
40
|
+
:submodules => true
|
41
|
+
```
|
42
|
+
|
43
|
+
## CHANGES:
|
44
|
+
|
45
|
+
* [CHANGES](CHANGES.md)
|
46
|
+
|
47
|
+
## CONTRIBUTORS:
|
48
|
+
|
49
|
+
* Lin Jen-Shin (@godfat)
|
50
|
+
|
51
|
+
## LICENSE:
|
52
|
+
|
53
|
+
Apache License 2.0
|
54
|
+
|
55
|
+
Copyright (c) 2016, Lin Jen-Shin (godfat)
|
56
|
+
|
57
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
58
|
+
you may not use this file except in compliance with the License.
|
59
|
+
You may obtain a copy of the License at
|
60
|
+
|
61
|
+
<http://www.apache.org/licenses/LICENSE-2.0>
|
62
|
+
|
63
|
+
Unless required by applicable law or agreed to in writing, software
|
64
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
65
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
66
|
+
See the License for the specific language governing permissions and
|
67
|
+
limitations under the License.
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
|
2
|
+
begin
|
3
|
+
require "#{dir = File.dirname(__FILE__)}/task/gemgem"
|
4
|
+
rescue LoadError
|
5
|
+
sh 'git submodule update --init'
|
6
|
+
exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV
|
7
|
+
end
|
8
|
+
|
9
|
+
Gemgem.init(dir) do |s|
|
10
|
+
require 'promise_pool/version'
|
11
|
+
s.name = 'promise_pool'
|
12
|
+
s.version = PromisePool::VERSION
|
13
|
+
s.add_runtime_dependency('timers', '>=4.0.1')
|
14
|
+
end
|
data/lib/promise_pool.rb
ADDED
@@ -0,0 +1,163 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
require 'promise_pool/future'
|
4
|
+
|
5
|
+
module PromisePool
|
6
|
+
class Promise
|
7
|
+
def self.claim value
|
8
|
+
promise = new
|
9
|
+
promise.fulfill(value)
|
10
|
+
promise
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.backtrace
|
14
|
+
Thread.current[:promise_pool_backtrace] || []
|
15
|
+
end
|
16
|
+
|
17
|
+
# should never raise!
|
18
|
+
def self.set_backtrace e
|
19
|
+
e.set_backtrace((e.backtrace || caller) + backtrace)
|
20
|
+
end
|
21
|
+
|
22
|
+
def initialize timer=nil
|
23
|
+
self.value = self.error = self.result = nil
|
24
|
+
self.resolved = self.called = false
|
25
|
+
|
26
|
+
self.k = []
|
27
|
+
self.timer = timer
|
28
|
+
self.condv = ConditionVariable.new
|
29
|
+
self.mutex = Mutex.new
|
30
|
+
end
|
31
|
+
|
32
|
+
# called in client thread
|
33
|
+
def defer pool=nil
|
34
|
+
backtrace = caller + self.class.backtrace # retain the backtrace so far
|
35
|
+
if pool
|
36
|
+
mutex.synchronize do
|
37
|
+
# still timing it out if the task never processed
|
38
|
+
timer.on_timeout{ cancel_task } if timer
|
39
|
+
self.task = pool.defer(mutex) do
|
40
|
+
Thread.current[:promise_pool_backtrace] = backtrace
|
41
|
+
protected_yield{ yield }
|
42
|
+
Thread.current[:promise_pool_backtrace] = nil
|
43
|
+
end
|
44
|
+
end
|
45
|
+
else
|
46
|
+
self.thread = Thread.new do
|
47
|
+
Thread.current[:promise_pool_backtrace] = backtrace
|
48
|
+
protected_yield{ yield }
|
49
|
+
end
|
50
|
+
end
|
51
|
+
self
|
52
|
+
end
|
53
|
+
|
54
|
+
def call
|
55
|
+
self.thread = Thread.current # set working thread
|
56
|
+
protected_yield{ yield } # avoid any exception and do the job
|
57
|
+
end
|
58
|
+
|
59
|
+
def future
|
60
|
+
Future.new(self)
|
61
|
+
end
|
62
|
+
|
63
|
+
# called in client thread (client.wait)
|
64
|
+
def wait
|
65
|
+
# it might be awaken by some other futures!
|
66
|
+
mutex.synchronize{ condv.wait(mutex) until resolved? } unless resolved?
|
67
|
+
end
|
68
|
+
|
69
|
+
# called in client thread (from the future (e.g. body))
|
70
|
+
def yield
|
71
|
+
wait
|
72
|
+
mutex.synchronize{ callback }
|
73
|
+
end
|
74
|
+
|
75
|
+
# called in requesting thread after the request is done
|
76
|
+
def fulfill value
|
77
|
+
mutex.synchronize{ fulfilling(value) }
|
78
|
+
end
|
79
|
+
|
80
|
+
# called in requesting thread if something goes wrong or timed out
|
81
|
+
def reject error
|
82
|
+
mutex.synchronize{ rejecting(error) }
|
83
|
+
end
|
84
|
+
|
85
|
+
# append your actions, which would be called when we're calling back
|
86
|
+
def then &action
|
87
|
+
k << action
|
88
|
+
self
|
89
|
+
end
|
90
|
+
|
91
|
+
def resolved?
|
92
|
+
resolved
|
93
|
+
end
|
94
|
+
|
95
|
+
protected
|
96
|
+
attr_accessor :value, :error, :result, :resolved, :called,
|
97
|
+
:k, :timer, :condv, :mutex, :task, :thread
|
98
|
+
|
99
|
+
private
|
100
|
+
def fulfilling value
|
101
|
+
self.value = value
|
102
|
+
resolve
|
103
|
+
end
|
104
|
+
|
105
|
+
def rejecting error
|
106
|
+
self.error = error
|
107
|
+
resolve
|
108
|
+
end
|
109
|
+
|
110
|
+
def resolve
|
111
|
+
self.resolved = true
|
112
|
+
yield if block_given?
|
113
|
+
ensure
|
114
|
+
condv.broadcast # client or response might be waiting
|
115
|
+
end
|
116
|
+
|
117
|
+
# called in a new thread if pool_size == 0, otherwise from the pool
|
118
|
+
# i.e. requesting thread
|
119
|
+
def protected_yield
|
120
|
+
value = if timer
|
121
|
+
timeout_protected_yield{ yield }
|
122
|
+
else
|
123
|
+
yield
|
124
|
+
end
|
125
|
+
fulfill(value)
|
126
|
+
rescue Exception => err
|
127
|
+
self.class.set_backtrace(err)
|
128
|
+
reject(err)
|
129
|
+
end
|
130
|
+
|
131
|
+
def timeout_protected_yield
|
132
|
+
# timeout might already be set for thread_pool (pool_size > 0)
|
133
|
+
timer.on_timeout{ cancel_task } unless timer
|
134
|
+
yield
|
135
|
+
ensure
|
136
|
+
timer.cancel
|
137
|
+
end
|
138
|
+
|
139
|
+
# called in client thread, when yield is called
|
140
|
+
def callback
|
141
|
+
return result if called
|
142
|
+
self.result = k.inject(error || value){ |r, i| i.call(r) }
|
143
|
+
ensure
|
144
|
+
self.called = true
|
145
|
+
end
|
146
|
+
|
147
|
+
# timeout!
|
148
|
+
def cancel_task
|
149
|
+
mutex.synchronize do
|
150
|
+
if resolved?
|
151
|
+
# do nothing if it's already done
|
152
|
+
elsif t = thread || task.thread
|
153
|
+
t.raise(timer.error) # raise Timeout::Error to working thread
|
154
|
+
else
|
155
|
+
# task was queued and never started, just cancel it and
|
156
|
+
# fulfill the promise with Timeout::Error
|
157
|
+
task.cancel
|
158
|
+
rejecting(timer.error)
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
require 'promise_pool/promise'
|
3
|
+
|
4
|
+
module PromisePool
|
5
|
+
class PromiseEager < Promise
|
6
|
+
attr_accessor :error_callback
|
7
|
+
|
8
|
+
def initialize timer=nil, &error_callback
|
9
|
+
super(timer)
|
10
|
+
self.error_callback = error_callback
|
11
|
+
end
|
12
|
+
|
13
|
+
def resolved?
|
14
|
+
super && called
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def resolve
|
19
|
+
super{ callback } # under ASYNC callback, should call immediately
|
20
|
+
rescue Exception => err
|
21
|
+
self.class.set_backtrace(err)
|
22
|
+
call_error_callback(err)
|
23
|
+
end
|
24
|
+
|
25
|
+
# log user callback error, should never raise
|
26
|
+
def call_error_callback err
|
27
|
+
if error_callback
|
28
|
+
error_callback.call(err)
|
29
|
+
else
|
30
|
+
warn "#{self.class}: ERROR: #{err}\n from #{err.backtrace.inspect}"
|
31
|
+
end
|
32
|
+
rescue Exception => e
|
33
|
+
Thread.main.raise(e) if !!$DEBUG
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
|
4
|
+
module PromisePool
|
5
|
+
class Queue
|
6
|
+
def initialize
|
7
|
+
@queue = []
|
8
|
+
@condv = ConditionVariable.new
|
9
|
+
end
|
10
|
+
|
11
|
+
def size
|
12
|
+
@queue.size
|
13
|
+
end
|
14
|
+
|
15
|
+
def << task
|
16
|
+
queue << task
|
17
|
+
condv.signal
|
18
|
+
end
|
19
|
+
|
20
|
+
def pop mutex, timeout=60
|
21
|
+
if queue.empty?
|
22
|
+
condv.wait(mutex, timeout)
|
23
|
+
queue.shift || lambda{ |_| false } # shutdown idle workers
|
24
|
+
else
|
25
|
+
queue.shift
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def clear
|
30
|
+
queue.clear
|
31
|
+
end
|
32
|
+
|
33
|
+
protected
|
34
|
+
attr_reader :queue, :condv
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
|
2
|
+
module PromisePool
|
3
|
+
class Task < Struct.new(:job, :mutex, :thread, :cancelled)
|
4
|
+
# this should never fail
|
5
|
+
def call working_thread
|
6
|
+
mutex.synchronize do
|
7
|
+
return if cancelled
|
8
|
+
self.thread = working_thread
|
9
|
+
end
|
10
|
+
job.call
|
11
|
+
true
|
12
|
+
end
|
13
|
+
|
14
|
+
def cancel
|
15
|
+
self.cancelled = true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
|
2
|
+
# reference implementation: puma
|
3
|
+
# https://github.com/puma/puma/blob/v2.7.1/lib/puma/thread_pool.rb
|
4
|
+
|
5
|
+
require 'thread'
|
6
|
+
require 'promise_pool/queue'
|
7
|
+
require 'promise_pool/task'
|
8
|
+
|
9
|
+
module PromisePool
|
10
|
+
class ThreadPool
|
11
|
+
attr_reader :workers
|
12
|
+
attr_accessor :idle_time, :max_size
|
13
|
+
|
14
|
+
def initialize max_size, idle_time=60
|
15
|
+
@max_size = max_size
|
16
|
+
@idle_time = idle_time
|
17
|
+
@queue = Queue.new
|
18
|
+
@mutex = Mutex.new
|
19
|
+
@workers = []
|
20
|
+
@waiting = 0
|
21
|
+
end
|
22
|
+
|
23
|
+
def size
|
24
|
+
workers.size
|
25
|
+
end
|
26
|
+
|
27
|
+
def defer promise_mutex, &job
|
28
|
+
mutex.synchronize do
|
29
|
+
task = Task.new(job, promise_mutex)
|
30
|
+
queue << task
|
31
|
+
spawn_worker if waiting < queue.size && workers.size < max_size
|
32
|
+
task
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def trim force=false
|
37
|
+
mutex.synchronize do
|
38
|
+
queue << lambda{ |_| false } if force || waiting > 0
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
# Block on shutting down, and should not add more jobs while shutting down
|
43
|
+
def shutdown
|
44
|
+
workers.size.times{ trim(true) }
|
45
|
+
workers.first.join && trim(true) until workers.empty?
|
46
|
+
mutex.synchronize{ queue.clear }
|
47
|
+
end
|
48
|
+
|
49
|
+
protected
|
50
|
+
attr_reader :queue, :mutex, :condv, :waiting
|
51
|
+
|
52
|
+
private
|
53
|
+
def spawn_worker
|
54
|
+
workers << Thread.new{
|
55
|
+
Thread.current.abort_on_exception = true
|
56
|
+
|
57
|
+
task = nil
|
58
|
+
begin
|
59
|
+
mutex.synchronize do
|
60
|
+
@waiting += 1
|
61
|
+
task = queue.pop(mutex, idle_time)
|
62
|
+
@waiting -= 1
|
63
|
+
end
|
64
|
+
end while task.call(Thread.current)
|
65
|
+
|
66
|
+
mutex.synchronize{ workers.delete(Thread.current) }
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
|
2
|
+
require 'thread'
|
3
|
+
require 'timers'
|
4
|
+
|
5
|
+
module PromisePool
|
6
|
+
class Timer
|
7
|
+
@mutex = Mutex.new
|
8
|
+
@interval = 1
|
9
|
+
|
10
|
+
singleton_class.module_eval do
|
11
|
+
attr_accessor :interval
|
12
|
+
|
13
|
+
def group
|
14
|
+
@group ||= @mutex.synchronize{ @group ||= group_new }
|
15
|
+
end
|
16
|
+
|
17
|
+
private
|
18
|
+
def group_new
|
19
|
+
g = Timers::Group.new
|
20
|
+
g.every(interval){}
|
21
|
+
@thread = Thread.new do
|
22
|
+
begin
|
23
|
+
g.wait
|
24
|
+
rescue => e
|
25
|
+
warn "#{self.class}: ERROR: #{e}\n from #{e.backtrace.inspect}"
|
26
|
+
end while g.count > 1
|
27
|
+
@group = nil
|
28
|
+
end
|
29
|
+
g
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_accessor :timeout, :error, :timer
|
34
|
+
def initialize timeout, error, &block
|
35
|
+
self.timeout = timeout
|
36
|
+
self.error = error
|
37
|
+
self.block = block
|
38
|
+
start if block_given?
|
39
|
+
end
|
40
|
+
|
41
|
+
def on_timeout &block
|
42
|
+
self.block = block
|
43
|
+
start if block_given?
|
44
|
+
end
|
45
|
+
|
46
|
+
# should never raise!
|
47
|
+
def cancel
|
48
|
+
timer.cancel if timer
|
49
|
+
self.block = nil
|
50
|
+
end
|
51
|
+
|
52
|
+
def start
|
53
|
+
return if timeout.nil? || timeout.zero?
|
54
|
+
self.timer = self.class.group.after(timeout){ block.call if block }
|
55
|
+
end
|
56
|
+
|
57
|
+
protected
|
58
|
+
attr_accessor :block
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
# stub: promise_pool 0.1.0 ruby lib
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "promise_pool"
|
6
|
+
s.version = "0.1.0"
|
7
|
+
|
8
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
9
|
+
s.require_paths = ["lib"]
|
10
|
+
s.authors = ["Lin Jen-Shin (godfat)"]
|
11
|
+
s.date = "2016-01-21"
|
12
|
+
s.description = "promise_pool"
|
13
|
+
s.email = ["godfat (XD) godfat.org"]
|
14
|
+
s.files = [
|
15
|
+
".gitignore",
|
16
|
+
".gitmodules",
|
17
|
+
".travis.yml",
|
18
|
+
"Gemfile",
|
19
|
+
"README.md",
|
20
|
+
"Rakefile",
|
21
|
+
"lib/promise_pool.rb",
|
22
|
+
"lib/promise_pool/future.rb",
|
23
|
+
"lib/promise_pool/promise.rb",
|
24
|
+
"lib/promise_pool/promise_eager.rb",
|
25
|
+
"lib/promise_pool/queue.rb",
|
26
|
+
"lib/promise_pool/task.rb",
|
27
|
+
"lib/promise_pool/test.rb",
|
28
|
+
"lib/promise_pool/thread_pool.rb",
|
29
|
+
"lib/promise_pool/timer.rb",
|
30
|
+
"lib/promise_pool/version.rb",
|
31
|
+
"promise_pool.gemspec",
|
32
|
+
"task/README.md",
|
33
|
+
"task/gemgem.rb",
|
34
|
+
"test/test_pool.rb",
|
35
|
+
"test/test_promise.rb",
|
36
|
+
"test/test_promise_eager.rb",
|
37
|
+
"test/test_timer.rb"]
|
38
|
+
s.homepage = "https://github.com/godfat/promise_pool"
|
39
|
+
s.licenses = ["Apache License 2.0"]
|
40
|
+
s.rubygems_version = "2.5.1"
|
41
|
+
s.summary = "promise_pool"
|
42
|
+
s.test_files = [
|
43
|
+
"test/test_pool.rb",
|
44
|
+
"test/test_promise.rb",
|
45
|
+
"test/test_promise_eager.rb",
|
46
|
+
"test/test_timer.rb"]
|
47
|
+
|
48
|
+
if s.respond_to? :specification_version then
|
49
|
+
s.specification_version = 4
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_runtime_dependency(%q<timers>, [">= 4.0.1"])
|
53
|
+
else
|
54
|
+
s.add_dependency(%q<timers>, [">= 4.0.1"])
|
55
|
+
end
|
56
|
+
else
|
57
|
+
s.add_dependency(%q<timers>, [">= 4.0.1"])
|
58
|
+
end
|
59
|
+
end
|
data/task/README.md
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
# Gemgem
|
2
|
+
|
3
|
+
## DESCRIPTION:
|
4
|
+
|
5
|
+
Provided tasks:
|
6
|
+
|
7
|
+
rake clean # Remove ignored files
|
8
|
+
rake gem:build # Build gem
|
9
|
+
rake gem:install # Install gem
|
10
|
+
rake gem:release # Release gem
|
11
|
+
rake gem:spec # Generate gemspec
|
12
|
+
rake test # Run tests in memory
|
13
|
+
|
14
|
+
## REQUIREMENTS:
|
15
|
+
|
16
|
+
* Tested with MRI (official CRuby) 1.9.3, 2.0.0, Rubinius and JRuby.
|
17
|
+
|
18
|
+
## INSTALLATION:
|
19
|
+
|
20
|
+
git submodule add git://github.com/godfat/gemgem.git task
|
21
|
+
|
22
|
+
And in Rakefile:
|
23
|
+
|
24
|
+
``` ruby
|
25
|
+
begin
|
26
|
+
require "#{dir = File.dirname(__FILE__)}/task/gemgem"
|
27
|
+
rescue LoadError
|
28
|
+
sh 'git submodule update --init'
|
29
|
+
exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV
|
30
|
+
end
|
31
|
+
|
32
|
+
Gemgem.init(dir) do |s|
|
33
|
+
s.name = 'your-gem'
|
34
|
+
s.version = '0.1.0'
|
35
|
+
end
|
36
|
+
```
|
37
|
+
|
38
|
+
## LICENSE:
|
39
|
+
|
40
|
+
Apache License 2.0
|
41
|
+
|
42
|
+
Copyright (c) 2011-2013, Lin Jen-Shin (godfat)
|
43
|
+
|
44
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
45
|
+
you may not use this file except in compliance with the License.
|
46
|
+
You may obtain a copy of the License at
|
47
|
+
|
48
|
+
<http://www.apache.org/licenses/LICENSE-2.0>
|
49
|
+
|
50
|
+
Unless required by applicable law or agreed to in writing, software
|
51
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
52
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
53
|
+
See the License for the specific language governing permissions and
|
54
|
+
limitations under the License.
|
data/task/gemgem.rb
ADDED
@@ -0,0 +1,316 @@
|
|
1
|
+
|
2
|
+
module Gemgem
|
3
|
+
class << self
|
4
|
+
attr_accessor :dir, :spec, :spec_create
|
5
|
+
end
|
6
|
+
|
7
|
+
module_function
|
8
|
+
def gem_tag ; "#{spec.name}-#{spec.version}" ; end
|
9
|
+
def gem_path ; "#{pkg_dir}/#{gem_tag}.gem" ; end
|
10
|
+
def spec_path ; "#{dir}/#{spec.name}.gemspec" ; end
|
11
|
+
def pkg_dir ; "#{dir}/pkg" ; end
|
12
|
+
def escaped_dir; @escaped_dir ||= Regexp.escape(dir); end
|
13
|
+
|
14
|
+
def init dir, &block
|
15
|
+
self.dir = dir
|
16
|
+
$LOAD_PATH.unshift("#{dir}/lib")
|
17
|
+
ENV['RUBYLIB'] = "#{dir}/lib:#{ENV['RUBYLIB']}"
|
18
|
+
ENV['PATH'] = "#{dir}/bin:#{ENV['PATH']}"
|
19
|
+
self.spec_create = block
|
20
|
+
end
|
21
|
+
|
22
|
+
def create
|
23
|
+
spec = Gem::Specification.new do |s|
|
24
|
+
s.authors = ['Lin Jen-Shin (godfat)']
|
25
|
+
s.email = ['godfat (XD) godfat.org']
|
26
|
+
|
27
|
+
s.description = description.join
|
28
|
+
s.summary = description.first
|
29
|
+
s.license = readme['LICENSE'].sub(/.+\n\n/, '').lines.first.strip
|
30
|
+
|
31
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
32
|
+
s.files = gem_files
|
33
|
+
s.test_files = test_files
|
34
|
+
s.executables = bin_files
|
35
|
+
end
|
36
|
+
spec_create.call(spec)
|
37
|
+
spec.homepage ||= "https://github.com/godfat/#{spec.name}"
|
38
|
+
self.spec = spec
|
39
|
+
end
|
40
|
+
|
41
|
+
def gem_install
|
42
|
+
require 'rubygems/commands/install_command'
|
43
|
+
# read ~/.gemrc
|
44
|
+
Gem.use_paths(Gem.configuration[:gemhome], Gem.configuration[:gempath])
|
45
|
+
Gem::Command.extra_args = Gem.configuration[:gem]
|
46
|
+
|
47
|
+
# setup install options
|
48
|
+
cmd = Gem::Commands::InstallCommand.new
|
49
|
+
cmd.handle_options([])
|
50
|
+
|
51
|
+
# install
|
52
|
+
install = Gem::Installer.new(gem_path, cmd.options)
|
53
|
+
install.install
|
54
|
+
puts "\e[35mGem installed: \e[33m#{strip_path(install.gem_dir)}\e[0m"
|
55
|
+
end
|
56
|
+
|
57
|
+
def gem_spec
|
58
|
+
create
|
59
|
+
write
|
60
|
+
end
|
61
|
+
|
62
|
+
def gem_build
|
63
|
+
require 'fileutils'
|
64
|
+
require 'rubygems/package'
|
65
|
+
gem = nil
|
66
|
+
Dir.chdir(dir) do
|
67
|
+
gem = Gem::Package.build(Gem::Specification.load(spec_path))
|
68
|
+
FileUtils.mkdir_p(pkg_dir)
|
69
|
+
FileUtils.mv(gem, pkg_dir) # gem is relative path, but might be ok
|
70
|
+
end
|
71
|
+
puts "\e[35mGem built: \e[33m#{strip_path("#{pkg_dir}/#{gem}")}\e[0m"
|
72
|
+
end
|
73
|
+
|
74
|
+
def gem_release
|
75
|
+
sh_git('tag', gem_tag)
|
76
|
+
sh_git('push')
|
77
|
+
sh_git('push', '--tags')
|
78
|
+
sh_gem('push', gem_path)
|
79
|
+
end
|
80
|
+
|
81
|
+
def gem_check
|
82
|
+
unless git('status', '--porcelain').empty?
|
83
|
+
puts("\e[35mWorking copy is not clean.\e[0m")
|
84
|
+
exit(3)
|
85
|
+
end
|
86
|
+
|
87
|
+
ver = spec.version.to_s
|
88
|
+
|
89
|
+
if ENV['VERSION'].nil?
|
90
|
+
puts("\e[35mExpected " \
|
91
|
+
"\e[33mVERSION\e[35m=\e[33m#{ver}\e[0m")
|
92
|
+
exit(1)
|
93
|
+
|
94
|
+
elsif ENV['VERSION'] != ver
|
95
|
+
puts("\e[35mExpected \e[33mVERSION\e[35m=\e[33m#{ver} " \
|
96
|
+
"\e[35mbut got\n " \
|
97
|
+
"\e[33mVERSION\e[35m=\e[33m#{ENV['VERSION']}\e[0m")
|
98
|
+
exit(2)
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
def test
|
103
|
+
return if test_files.empty?
|
104
|
+
|
105
|
+
if ENV['COV'] || ENV['CI']
|
106
|
+
require 'simplecov'
|
107
|
+
if ENV['CI']
|
108
|
+
begin
|
109
|
+
require 'coveralls'
|
110
|
+
SimpleCov.formatter = Coveralls::SimpleCov::Formatter
|
111
|
+
rescue LoadError => e
|
112
|
+
puts "Cannot load coveralls, skip: #{e}"
|
113
|
+
end
|
114
|
+
end
|
115
|
+
SimpleCov.start do
|
116
|
+
add_filter('test/')
|
117
|
+
add_filter('test.rb')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
test_files.each{ |file| require "#{dir}/#{file[0..-4]}" }
|
122
|
+
end
|
123
|
+
|
124
|
+
def clean
|
125
|
+
return if ignored_files.empty?
|
126
|
+
|
127
|
+
require 'fileutils'
|
128
|
+
trash = File.expand_path("~/.Trash/#{spec.name}")
|
129
|
+
puts "Move the following files into: \e[35m#{strip_path(trash)}\e[33m"
|
130
|
+
|
131
|
+
ignored_files.each do |file|
|
132
|
+
from = "#{dir}/#{file}"
|
133
|
+
to = "#{trash}/#{File.dirname(file)}"
|
134
|
+
puts strip_path(from)
|
135
|
+
|
136
|
+
FileUtils.mkdir_p(to)
|
137
|
+
FileUtils.mv(from, to)
|
138
|
+
end
|
139
|
+
|
140
|
+
print "\e[0m"
|
141
|
+
end
|
142
|
+
|
143
|
+
def write
|
144
|
+
File.open(spec_path, 'w'){ |f| f << split_lines(spec.to_ruby) }
|
145
|
+
end
|
146
|
+
|
147
|
+
def split_lines ruby
|
148
|
+
ruby.gsub(/(.+?)\s*=\s*\[(.+?)\]/){ |s|
|
149
|
+
if $2.index(',')
|
150
|
+
"#{$1} = [\n #{$2.split(',').map(&:strip).join(",\n ")}]"
|
151
|
+
else
|
152
|
+
s
|
153
|
+
end
|
154
|
+
}
|
155
|
+
end
|
156
|
+
|
157
|
+
def strip_path path
|
158
|
+
strip_home_path(strip_cwd_path(path))
|
159
|
+
end
|
160
|
+
|
161
|
+
def strip_home_path path
|
162
|
+
path.sub(ENV['HOME'], '~')
|
163
|
+
end
|
164
|
+
|
165
|
+
def strip_cwd_path path
|
166
|
+
path.sub(Dir.pwd, '.')
|
167
|
+
end
|
168
|
+
|
169
|
+
def git *args
|
170
|
+
`git --git-dir=#{dir}/.git #{args.join(' ')}`
|
171
|
+
end
|
172
|
+
|
173
|
+
def sh_git *args
|
174
|
+
Rake.sh('git', "--git-dir=#{dir}/.git", *args)
|
175
|
+
end
|
176
|
+
|
177
|
+
def sh_gem *args
|
178
|
+
Rake.sh(Gem.ruby, '-S', 'gem', *args)
|
179
|
+
end
|
180
|
+
|
181
|
+
def glob path=dir
|
182
|
+
Dir.glob("#{path}/**/*", File::FNM_DOTMATCH)
|
183
|
+
end
|
184
|
+
|
185
|
+
def readme
|
186
|
+
@readme ||=
|
187
|
+
if (path = "#{Gemgem.dir}/README.md") && File.exist?(path)
|
188
|
+
ps = "##{File.read(path)}".
|
189
|
+
scan(/((#+)[^\n]+\n\n.+?(?=(\n\n\2[^#\n]+\n)|\Z))/m).map(&:first)
|
190
|
+
ps.inject('HEADER' => ps.first){ |r, s, i|
|
191
|
+
r[s[/\w+/]] = s
|
192
|
+
r
|
193
|
+
}
|
194
|
+
else
|
195
|
+
{}
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def description
|
200
|
+
# JRuby String#lines is returning an enumerator
|
201
|
+
@description ||= (readme['DESCRIPTION']||'').sub(/.+\n\n/, '').lines.to_a
|
202
|
+
end
|
203
|
+
|
204
|
+
def all_files
|
205
|
+
@all_files ||= fold_files(glob).sort
|
206
|
+
end
|
207
|
+
|
208
|
+
def fold_files files
|
209
|
+
files.inject([]){ |r, path|
|
210
|
+
if File.file?(path) && path !~ %r{/\.git(/|$)} &&
|
211
|
+
(rpath = path[%r{^#{escaped_dir}/(.*$)}, 1])
|
212
|
+
r << rpath
|
213
|
+
elsif File.symlink?(path) # walk into symlinks...
|
214
|
+
r.concat(fold_files(glob(File.expand_path(path,
|
215
|
+
File.readlink(path)))))
|
216
|
+
else
|
217
|
+
r
|
218
|
+
end
|
219
|
+
}
|
220
|
+
end
|
221
|
+
|
222
|
+
def gem_files
|
223
|
+
@gem_files ||= all_files.reject{ |f|
|
224
|
+
f =~ ignored_pattern && !git_files.include?(f)
|
225
|
+
}
|
226
|
+
end
|
227
|
+
|
228
|
+
def test_files
|
229
|
+
@test_files ||= gem_files.grep(%r{^test/(.+?/)*test_.+?\.rb$})
|
230
|
+
end
|
231
|
+
|
232
|
+
def bin_files
|
233
|
+
@bin_files ||= gem_files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
234
|
+
end
|
235
|
+
|
236
|
+
def git_files
|
237
|
+
@git_files ||= if File.exist?("#{dir}/.git")
|
238
|
+
git('ls-files').split("\n")
|
239
|
+
else
|
240
|
+
[]
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
def ignored_files
|
245
|
+
@ignored_files ||= all_files.grep(ignored_pattern)
|
246
|
+
end
|
247
|
+
|
248
|
+
def ignored_pattern
|
249
|
+
@ignored_pattern ||= if gitignore.empty?
|
250
|
+
/^$/
|
251
|
+
else
|
252
|
+
Regexp.new(expand_patterns(gitignore).join('|'))
|
253
|
+
end
|
254
|
+
end
|
255
|
+
|
256
|
+
def expand_patterns pathes
|
257
|
+
# http://git-scm.com/docs/gitignore
|
258
|
+
pathes.flat_map{ |path|
|
259
|
+
# we didn't implement negative pattern for now
|
260
|
+
Regexp.escape(path).sub(%r{^/}, '^').gsub(/\\\*/, '[^/]*')
|
261
|
+
}
|
262
|
+
end
|
263
|
+
|
264
|
+
def gitignore
|
265
|
+
@gitignore ||= if File.exist?(path = "#{dir}/.gitignore")
|
266
|
+
File.read(path).lines.
|
267
|
+
reject{ |l| l == /^\s*(#|\s+$)/ }.map(&:strip)
|
268
|
+
else
|
269
|
+
[]
|
270
|
+
end
|
271
|
+
end
|
272
|
+
end
|
273
|
+
|
274
|
+
namespace :gem do
|
275
|
+
|
276
|
+
desc 'Install gem'
|
277
|
+
task :install => [:build] do
|
278
|
+
Gemgem.gem_install
|
279
|
+
end
|
280
|
+
|
281
|
+
desc 'Build gem'
|
282
|
+
task :build => [:spec] do
|
283
|
+
Gemgem.gem_build
|
284
|
+
end
|
285
|
+
|
286
|
+
desc 'Generate gemspec'
|
287
|
+
task :spec do
|
288
|
+
Gemgem.gem_spec
|
289
|
+
end
|
290
|
+
|
291
|
+
desc 'Release gem'
|
292
|
+
task :release => [:spec, :check, :build] do
|
293
|
+
Gemgem.gem_release
|
294
|
+
end
|
295
|
+
|
296
|
+
task :check do
|
297
|
+
Gemgem.gem_check
|
298
|
+
end
|
299
|
+
|
300
|
+
end # of gem namespace
|
301
|
+
|
302
|
+
desc 'Run tests'
|
303
|
+
task :test do
|
304
|
+
Gemgem.test
|
305
|
+
end
|
306
|
+
|
307
|
+
desc 'Trash ignored files'
|
308
|
+
task :clean => ['gem:spec'] do
|
309
|
+
Gemgem.clean
|
310
|
+
end
|
311
|
+
|
312
|
+
task :default do
|
313
|
+
# Is there a reliable way to do this in the current process?
|
314
|
+
# It failed miserably before between Rake versions...
|
315
|
+
exec "#{Gem.ruby} -S #{$PROGRAM_NAME} -f #{Rake.application.rakefile} -T"
|
316
|
+
end
|
data/test/test_pool.rb
ADDED
@@ -0,0 +1,74 @@
|
|
1
|
+
|
2
|
+
require 'promise_pool/test'
|
3
|
+
|
4
|
+
describe PromisePool::ThreadPool do
|
5
|
+
before do
|
6
|
+
@pool = ThreadPool.new(3)
|
7
|
+
@promise = Promise.new
|
8
|
+
end
|
9
|
+
|
10
|
+
after do
|
11
|
+
@pool.shutdown
|
12
|
+
@pool.size.should.eq 0
|
13
|
+
end
|
14
|
+
|
15
|
+
would 'work, reject, yield' do
|
16
|
+
@pool.max_size = 1
|
17
|
+
flag = 0
|
18
|
+
@promise.defer(@pool) do
|
19
|
+
flag.should.eq 0
|
20
|
+
flag += 1
|
21
|
+
raise 'boom'
|
22
|
+
end.yield
|
23
|
+
flag.should.eq 1
|
24
|
+
@promise.send(:error).message.should.eq 'boom'
|
25
|
+
end
|
26
|
+
|
27
|
+
would 'work, fulfill, yield' do
|
28
|
+
value = 'body'
|
29
|
+
@pool.max_size = 2
|
30
|
+
flag = 0
|
31
|
+
@promise.defer(@pool) do
|
32
|
+
flag.should.eq 0
|
33
|
+
flag += 1
|
34
|
+
value
|
35
|
+
end
|
36
|
+
@promise.future.should.eq value
|
37
|
+
@promise.send(:value).should.eq value
|
38
|
+
@promise.send(:result).should.eq value
|
39
|
+
@promise.should.resolved?
|
40
|
+
flag.should.eq 1
|
41
|
+
end
|
42
|
+
|
43
|
+
would 'work, check body', :groups => [:only] do
|
44
|
+
flag = 0
|
45
|
+
result = @promise.defer(@pool) do
|
46
|
+
flag.should.eq 0
|
47
|
+
flag += 1
|
48
|
+
end.future
|
49
|
+
result.should.eq 1
|
50
|
+
flag.should.eq 1
|
51
|
+
end
|
52
|
+
|
53
|
+
would 'call in thread pool if pool_size > 0' do
|
54
|
+
@pool.max_size = 1
|
55
|
+
flag = 0
|
56
|
+
rd, wr = IO.pipe
|
57
|
+
@promise.defer(@pool) do
|
58
|
+
rd.gets
|
59
|
+
flag.should.eq 0
|
60
|
+
flag += 1
|
61
|
+
raise 'nnf'
|
62
|
+
end
|
63
|
+
p1 = Promise.new
|
64
|
+
p1.defer(@pool) do # block until promise #0 is done because max_size == 1
|
65
|
+
flag.should.eq 1
|
66
|
+
flag += 1
|
67
|
+
raise 'boom'
|
68
|
+
end
|
69
|
+
wr.puts # start promise #0
|
70
|
+
@promise.yield
|
71
|
+
p1.yield # block until promise #1 is done
|
72
|
+
flag.should.eq 2
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
|
2
|
+
require 'promise_pool/test'
|
3
|
+
|
4
|
+
describe PromisePool::Promise do
|
5
|
+
would 'claim' do
|
6
|
+
value = 'body'
|
7
|
+
Promise.claim(value).future.should.eq value
|
8
|
+
end
|
9
|
+
|
10
|
+
would 'then then then' do
|
11
|
+
plusone = lambda{ |r| r + 1 }
|
12
|
+
promise = Promise.new
|
13
|
+
2.times{ promise.then(&plusone).then(&plusone).then(&plusone) }
|
14
|
+
promise.fulfill(0)
|
15
|
+
promise.future.should.eq 6
|
16
|
+
end
|
17
|
+
|
18
|
+
after do
|
19
|
+
Muack.verify
|
20
|
+
end
|
21
|
+
|
22
|
+
would 'call in a new thread if no pool' do
|
23
|
+
thread = nil
|
24
|
+
rd, wr = IO.pipe
|
25
|
+
mock(Thread).new.with_any_args.peek_return do |t|
|
26
|
+
thread = t
|
27
|
+
wr.puts
|
28
|
+
end
|
29
|
+
Promise.new.defer do
|
30
|
+
rd.gets
|
31
|
+
Thread.current.should.eq thread
|
32
|
+
end.yield
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
require 'promise_pool/test'
|
3
|
+
|
4
|
+
describe PromisePool::PromiseEager do
|
5
|
+
would 'call error_callback on errors' do
|
6
|
+
errors = []
|
7
|
+
promise = PromiseEager.new(&errors.method(:<<))
|
8
|
+
|
9
|
+
promise.then do |err|
|
10
|
+
err.message.should.eq 'boom'
|
11
|
+
raise 'nnf'
|
12
|
+
end
|
13
|
+
|
14
|
+
promise.defer do
|
15
|
+
raise 'boom'
|
16
|
+
end.wait
|
17
|
+
|
18
|
+
errors.map(&:message).should.eq ['nnf']
|
19
|
+
end
|
20
|
+
|
21
|
+
after do
|
22
|
+
Muack.verify
|
23
|
+
end
|
24
|
+
|
25
|
+
would 'warn if there is no error_callback' do
|
26
|
+
promise = PromiseEager.new
|
27
|
+
|
28
|
+
mock(promise).warn(is_a(String)) do |msg|
|
29
|
+
msg.should.start_with?("PromisePool::PromiseEager: ERROR: nnf\n")
|
30
|
+
end
|
31
|
+
|
32
|
+
promise.then do |value|
|
33
|
+
value.should.eq 'value'
|
34
|
+
raise 'nnf'
|
35
|
+
end
|
36
|
+
|
37
|
+
promise.defer do
|
38
|
+
'value'
|
39
|
+
end.wait
|
40
|
+
end
|
41
|
+
end
|
data/test/test_timer.rb
ADDED
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: promise_pool
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Lin Jen-Shin (godfat)
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2016-01-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: timers
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.0.1
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.0.1
|
27
|
+
description: promise_pool
|
28
|
+
email:
|
29
|
+
- godfat (XD) godfat.org
|
30
|
+
executables: []
|
31
|
+
extensions: []
|
32
|
+
extra_rdoc_files: []
|
33
|
+
files:
|
34
|
+
- ".gitignore"
|
35
|
+
- ".gitmodules"
|
36
|
+
- ".travis.yml"
|
37
|
+
- Gemfile
|
38
|
+
- README.md
|
39
|
+
- Rakefile
|
40
|
+
- lib/promise_pool.rb
|
41
|
+
- lib/promise_pool/future.rb
|
42
|
+
- lib/promise_pool/promise.rb
|
43
|
+
- lib/promise_pool/promise_eager.rb
|
44
|
+
- lib/promise_pool/queue.rb
|
45
|
+
- lib/promise_pool/task.rb
|
46
|
+
- lib/promise_pool/test.rb
|
47
|
+
- lib/promise_pool/thread_pool.rb
|
48
|
+
- lib/promise_pool/timer.rb
|
49
|
+
- lib/promise_pool/version.rb
|
50
|
+
- promise_pool.gemspec
|
51
|
+
- task/README.md
|
52
|
+
- task/gemgem.rb
|
53
|
+
- test/test_pool.rb
|
54
|
+
- test/test_promise.rb
|
55
|
+
- test/test_promise_eager.rb
|
56
|
+
- test/test_timer.rb
|
57
|
+
homepage: https://github.com/godfat/promise_pool
|
58
|
+
licenses:
|
59
|
+
- Apache License 2.0
|
60
|
+
metadata: {}
|
61
|
+
post_install_message:
|
62
|
+
rdoc_options: []
|
63
|
+
require_paths:
|
64
|
+
- lib
|
65
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - ">="
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
requirements: []
|
76
|
+
rubyforge_project:
|
77
|
+
rubygems_version: 2.5.1
|
78
|
+
signing_key:
|
79
|
+
specification_version: 4
|
80
|
+
summary: promise_pool
|
81
|
+
test_files:
|
82
|
+
- test/test_pool.rb
|
83
|
+
- test/test_promise.rb
|
84
|
+
- test/test_promise_eager.rb
|
85
|
+
- test/test_timer.rb
|