promise_pool 0.1.0 → 0.9.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ba178cdd8145f5c65340a8599ffc9dbd4775ccef
4
- data.tar.gz: a978b9d5072e8038f9cb2e6884432bc9e4f0a6ac
3
+ metadata.gz: 8cdae0e0cfd7f377206d08c5898bc2637a4eca77
4
+ data.tar.gz: a7a4f91c93b6b81fa4c1472772d445b43ff2764e
5
5
  SHA512:
6
- metadata.gz: d531803ba5e8d28b438da3d152e8315ba7ce0de796ee781bcf0f0c2de23f7bf598bed2e9693d548ed05414cd8bd623b34f754588b1cb4ac3ac2bdf4dc898848e
7
- data.tar.gz: 406aa62fec4d02adf8eaf0088a0ed8ac7e08a70cf147d01c8ab7d41cbc588541385ff4a1633d18fb3c6e848860a4cb7f9f28c9b8c361f358006071c0954b0257
6
+ metadata.gz: 9d0c873467132e5132e83ecb6f000baed9acc44c282fe02b39455f56a3b6db6222803f65e82e12501e5c7f717c683166bbcfb917a4101736610ceb43bbc98e9d
7
+ data.tar.gz: 1143bb7a1a4438909b3efe14471012638168cb920a18d00d6644aeafe0ad4652d6e904f33023b97adcc90b66a5c6652d56bb13f5c0714ea10cb88fc25d49e1f7
data/CHANGES.md ADDED
@@ -0,0 +1,9 @@
1
+ # CHANGES
2
+
3
+ ## promise_pool 0.9.0 -- 2016-01-29
4
+
5
+ * First beta!
6
+
7
+ ## promise_pool 0.5.0 -- 2016-01-26
8
+
9
+ * Birthday!
data/README.md CHANGED
@@ -10,15 +10,21 @@ by Lin Jen-Shin ([godfat](http://godfat.org))
10
10
 
11
11
  ## DESCRIPTION:
12
12
 
13
- promise_pool
13
+ promise_pool is a promise implementation backed by threads or threads pool.
14
14
 
15
15
  ## FEATURES:
16
16
 
17
- * promise_pool
17
+ * PromisePool::Promise
18
+ * PromisePool::ThreadPool
19
+ * PromisePool::Timer
18
20
 
19
21
  ## WHY?
20
22
 
21
- promise_pool
23
+ This was extracted from [rest-core][] because rest-core itself is getting
24
+ too complex, and extracting promises from it could greatly reduce complexity
25
+ and improve modularity for both rest-core and promise_pool.
26
+
27
+ * [rest-core]: https://github.com/godfat/rest-core
22
28
 
23
29
  ## REQUIREMENTS:
24
30
 
@@ -40,6 +46,135 @@ gem 'promise_pool', :git => 'git://github.com/godfat/promise_pool.git',
40
46
  :submodules => true
41
47
  ```
42
48
 
49
+ ## SYNOPSIS:
50
+
51
+ ### Basic Usage
52
+
53
+ ``` ruby
54
+ require 'promise_pool/promise'
55
+ promise = PromisePool::Promise.new
56
+ promise.defer do
57
+ sleep 1
58
+ puts "Doing works..."
59
+ sleep 1
60
+ "Done!"
61
+ end
62
+ puts "It's not blocking!"
63
+ puts promise.yield
64
+ ```
65
+
66
+ Prints:
67
+
68
+ ```
69
+ It's not blocking!
70
+ Doing works...
71
+ Done!
72
+ ```
73
+
74
+ ### Multiple Concurrent Promises
75
+
76
+ Doing multiple things at the same time, and wait for all of them at once.
77
+
78
+ ``` ruby
79
+ require 'promise_pool/promise'
80
+
81
+ futures = 3.times.map do |i|
82
+ PromisePool::Promise.new.defer do
83
+ sleep i
84
+ i
85
+ end.future
86
+ end
87
+
88
+ futures.each(&method(:puts))
89
+ ```
90
+
91
+ Prints:
92
+
93
+ ```
94
+ 0
95
+ 1
96
+ 2
97
+ ```
98
+
99
+ ### Error Handling
100
+
101
+ If an exception was raised in the `defer` block, it would propagate whenever
102
+ `yield` is called. Note that futures would implicitly call `yield` for you.
103
+
104
+ ``` ruby
105
+ require 'promise_pool/promise'
106
+
107
+ future = PromisePool::Promise.new.defer do
108
+ raise 'nnf'
109
+ end.future
110
+
111
+ begin
112
+ future.to_s
113
+ never reached
114
+ rescue RuntimeError => e
115
+ puts e
116
+ end
117
+ ```
118
+
119
+ Prints:
120
+
121
+ ```
122
+ nnf
123
+ ```
124
+
125
+ ### PromisePool::ThreadPool
126
+
127
+ With a thread pool, we could throttle the process and avoid exhausting
128
+ resources whenever needed.
129
+
130
+ ``` ruby
131
+ require 'promise_pool/promise'
132
+ require 'promise_pool/thread_pool'
133
+
134
+ pool = PromisePool::ThreadPool.new(10, 60) # max_size=10, idle_time=60
135
+ future = PromisePool::Promise.new.defer(pool) do
136
+ 'Only process this whenever a worker is available.'
137
+ end.future
138
+
139
+ puts future
140
+
141
+ pool.shutdown # Make sure all the tasks are done in the pool before exit.
142
+ # You'll surely need this for shutting down gracefully.
143
+ ```
144
+
145
+ Prints:
146
+
147
+ ```
148
+ Only process this whenever a worker is available.
149
+ ```
150
+
151
+ ### PromisePool::Timer
152
+
153
+ If a task is taking too much time, we could time it out.
154
+
155
+ ``` ruby
156
+ require 'promise_pool/promise'
157
+ require 'promise_pool/timer'
158
+
159
+ timer = PromisePool::Timer.new(1)
160
+ future = PromisePool::Promise.new(timer).defer do
161
+ sleep
162
+ never reached
163
+ end.future
164
+
165
+ begin
166
+ future.to_s
167
+ rescue PromisePool::Timer::Error => e
168
+ puts e.message
169
+ end
170
+ ```
171
+
172
+ Prints:
173
+
174
+ ```
175
+ execution expired
176
+ ```
177
+
43
178
  ## CHANGES:
44
179
 
45
180
  * [CHANGES](CHANGES.md)
data/Rakefile CHANGED
@@ -2,7 +2,7 @@
2
2
  begin
3
3
  require "#{dir = File.dirname(__FILE__)}/task/gemgem"
4
4
  rescue LoadError
5
- sh 'git submodule update --init'
5
+ sh 'git submodule update --init --recursive'
6
6
  exec Gem.ruby, '-S', $PROGRAM_NAME, *ARGV
7
7
  end
8
8
 
@@ -8,5 +8,9 @@ module PromisePool
8
8
  def method_missing msg, *args, &block
9
9
  @promise.yield.__send__(msg, *args, &block)
10
10
  end
11
+
12
+ def respond_to_missing? msg, *args, &block
13
+ @promise.yield.respond_to?(msg, *args, &block)
14
+ end
11
15
  end
12
16
  end
@@ -4,8 +4,9 @@ require 'promise_pool/future'
4
4
 
5
5
  module PromisePool
6
6
  class Promise
7
- def self.claim value
7
+ def self.claim value, &callback
8
8
  promise = new
9
+ promise.then(&callback) if block_given?
9
10
  promise.fulfill(value)
10
11
  promise
11
12
  end
@@ -21,9 +22,9 @@ module PromisePool
21
22
 
22
23
  def initialize timer=nil
23
24
  self.value = self.error = self.result = nil
24
- self.resolved = self.called = false
25
+ self.resolved = false
26
+ self.callbacks = []
25
27
 
26
- self.k = []
27
28
  self.timer = timer
28
29
  self.condv = ConditionVariable.new
29
30
  self.mutex = Mutex.new
@@ -54,6 +55,7 @@ module PromisePool
54
55
  def call
55
56
  self.thread = Thread.current # set working thread
56
57
  protected_yield{ yield } # avoid any exception and do the job
58
+ self
57
59
  end
58
60
 
59
61
  def future
@@ -69,7 +71,12 @@ module PromisePool
69
71
  # called in client thread (from the future (e.g. body))
70
72
  def yield
71
73
  wait
72
- mutex.synchronize{ callback }
74
+ case result
75
+ when Exception
76
+ raise result
77
+ else
78
+ result
79
+ end
73
80
  end
74
81
 
75
82
  # called in requesting thread after the request is done
@@ -84,7 +91,7 @@ module PromisePool
84
91
 
85
92
  # append your actions, which would be called when we're calling back
86
93
  def then &action
87
- k << action
94
+ callbacks << action
88
95
  self
89
96
  end
90
97
 
@@ -93,24 +100,28 @@ module PromisePool
93
100
  end
94
101
 
95
102
  protected
96
- attr_accessor :value, :error, :result, :resolved, :called,
97
- :k, :timer, :condv, :mutex, :task, :thread
103
+ attr_accessor :value, :error, :result, :resolved, :callbacks,
104
+ :timer, :condv, :mutex, :task, :thread
98
105
 
99
106
  private
100
- def fulfilling value
107
+ def fulfilling value # should be synchronized
101
108
  self.value = value
102
109
  resolve
103
110
  end
104
111
 
105
- def rejecting error
112
+ def rejecting error # should be synchronized
106
113
  self.error = error
107
114
  resolve
108
115
  end
109
116
 
110
- def resolve
111
- self.resolved = true
112
- yield if block_given?
117
+ def resolve # should be synchronized
118
+ self.result = callbacks.inject(error || value){ |r, k| k.call(r) }
119
+ rescue Exception => err
120
+ self.class.set_backtrace(err)
121
+ self.result = err
122
+ log_callback_error(err)
113
123
  ensure
124
+ self.resolved = true
114
125
  condv.broadcast # client or response might be waiting
115
126
  end
116
127
 
@@ -130,20 +141,12 @@ module PromisePool
130
141
 
131
142
  def timeout_protected_yield
132
143
  # timeout might already be set for thread_pool (pool_size > 0)
133
- timer.on_timeout{ cancel_task } unless timer
144
+ timer.on_timeout{ cancel_task } unless timer.timer
134
145
  yield
135
146
  ensure
136
147
  timer.cancel
137
148
  end
138
149
 
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
150
  # timeout!
148
151
  def cancel_task
149
152
  mutex.synchronize do
@@ -159,5 +162,12 @@ module PromisePool
159
162
  end
160
163
  end
161
164
  end
165
+
166
+ # log user callback error, should never raise
167
+ def log_callback_error err
168
+ warn "#{self.class}: ERROR: #{err}\n from #{err.backtrace.inspect}"
169
+ rescue Exception => e
170
+ Thread.main.raise(e) if !!$DEBUG
171
+ end
162
172
  end
163
173
  end
@@ -9,7 +9,7 @@ require 'promise_pool/task'
9
9
  module PromisePool
10
10
  class ThreadPool
11
11
  attr_reader :workers
12
- attr_accessor :idle_time, :max_size
12
+ attr_accessor :max_size, :idle_time
13
13
 
14
14
  def initialize max_size, idle_time=60
15
15
  @max_size = max_size
@@ -4,6 +4,8 @@ require 'timers'
4
4
 
5
5
  module PromisePool
6
6
  class Timer
7
+ Error = Class.new(RuntimeError)
8
+
7
9
  @mutex = Mutex.new
8
10
  @interval = 1
9
11
 
@@ -31,16 +33,14 @@ module PromisePool
31
33
  end
32
34
 
33
35
  attr_accessor :timeout, :error, :timer
34
- def initialize timeout, error, &block
36
+ def initialize timeout, error=Error.new('execution expired')
35
37
  self.timeout = timeout
36
38
  self.error = error
37
- self.block = block
38
- start if block_given?
39
39
  end
40
40
 
41
41
  def on_timeout &block
42
42
  self.block = block
43
- start if block_given?
43
+ start
44
44
  end
45
45
 
46
46
  # should never raise!
@@ -1,4 +1,4 @@
1
1
 
2
2
  module PromisePool
3
- VERSION = '0.1.0'
3
+ VERSION = '0.9.0'
4
4
  end
data/lib/promise_pool.rb CHANGED
@@ -1,5 +1,4 @@
1
1
 
2
2
  require 'promise_pool/promise'
3
- require 'promise_pool/promise_eager'
4
3
  require 'promise_pool/thread_pool'
5
4
  require 'promise_pool/timer'
data/promise_pool.gemspec CHANGED
@@ -1,27 +1,27 @@
1
1
  # -*- encoding: utf-8 -*-
2
- # stub: promise_pool 0.1.0 ruby lib
2
+ # stub: promise_pool 0.9.0 ruby lib
3
3
 
4
4
  Gem::Specification.new do |s|
5
5
  s.name = "promise_pool"
6
- s.version = "0.1.0"
6
+ s.version = "0.9.0"
7
7
 
8
8
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
9
9
  s.require_paths = ["lib"]
10
10
  s.authors = ["Lin Jen-Shin (godfat)"]
11
- s.date = "2016-01-21"
12
- s.description = "promise_pool"
11
+ s.date = "2016-01-29"
12
+ s.description = "promise_pool is a promise implementation backed by threads or threads pool."
13
13
  s.email = ["godfat (XD) godfat.org"]
14
14
  s.files = [
15
15
  ".gitignore",
16
16
  ".gitmodules",
17
17
  ".travis.yml",
18
+ "CHANGES.md",
18
19
  "Gemfile",
19
20
  "README.md",
20
21
  "Rakefile",
21
22
  "lib/promise_pool.rb",
22
23
  "lib/promise_pool/future.rb",
23
24
  "lib/promise_pool/promise.rb",
24
- "lib/promise_pool/promise_eager.rb",
25
25
  "lib/promise_pool/queue.rb",
26
26
  "lib/promise_pool/task.rb",
27
27
  "lib/promise_pool/test.rb",
@@ -31,18 +31,20 @@ Gem::Specification.new do |s|
31
31
  "promise_pool.gemspec",
32
32
  "task/README.md",
33
33
  "task/gemgem.rb",
34
- "test/test_pool.rb",
34
+ "test/test_future.rb",
35
35
  "test/test_promise.rb",
36
- "test/test_promise_eager.rb",
36
+ "test/test_readme.rb",
37
+ "test/test_thread_pool.rb",
37
38
  "test/test_timer.rb"]
38
39
  s.homepage = "https://github.com/godfat/promise_pool"
39
40
  s.licenses = ["Apache License 2.0"]
40
41
  s.rubygems_version = "2.5.1"
41
- s.summary = "promise_pool"
42
+ s.summary = "promise_pool is a promise implementation backed by threads or threads pool."
42
43
  s.test_files = [
43
- "test/test_pool.rb",
44
+ "test/test_future.rb",
44
45
  "test/test_promise.rb",
45
- "test/test_promise_eager.rb",
46
+ "test/test_readme.rb",
47
+ "test/test_thread_pool.rb",
46
48
  "test/test_timer.rb"]
47
49
 
48
50
  if s.respond_to? :specification_version then
@@ -0,0 +1,26 @@
1
+
2
+ require 'promise_pool/test'
3
+
4
+ describe PromisePool::Future do
5
+ would 'return the value' do
6
+ Promise.new.defer{ 'value' }.future.should.eq 'value'
7
+ end
8
+
9
+ would 'raise an exception' do
10
+ expect.raise(RuntimeError) do
11
+ Promise.new.defer do
12
+ raise 'nnf'
13
+ end.future.oops
14
+ end.message.should.eq 'nnf'
15
+ end
16
+
17
+ would 'raise an exception if it is returning an exception' do
18
+ expect.raise(RuntimeError) do
19
+ Promise.new.defer{ RuntimeError.new('nnf') }.future.oops
20
+ end.message.should.eq 'nnf'
21
+ end
22
+
23
+ would 'respond_to_missing? properly' do
24
+ [Promise.new.defer{0}.future].flatten.first.should.eq 0
25
+ end
26
+ end
data/test/test_promise.rb CHANGED
@@ -2,33 +2,141 @@
2
2
  require 'promise_pool/test'
3
3
 
4
4
  describe PromisePool::Promise do
5
- would 'claim' do
6
- value = 'body'
7
- Promise.claim(value).future.should.eq value
5
+ describe 'claim' do
6
+ would 'without block' do
7
+ value = 'body'
8
+ Promise.claim(value).yield.should.eq value
9
+ end
10
+
11
+ would 'with block' do
12
+ value = 'body'
13
+ Promise.claim(value, &:reverse).yield.should.eq 'ydob'
14
+ end
8
15
  end
9
16
 
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
17
+ describe 'then' do
18
+ describe 'with mock' do
19
+ after do
20
+ Muack.verify
21
+ end
22
+
23
+ would 'handle exceptions in callbacks' do
24
+ errors = []
25
+ promise = Promise.new.then(&errors.method(:<<))
26
+
27
+ mock(promise).warn(is_a(String)) do |msg|
28
+ msg.should.start_with?("PromisePool::Promise: ERROR: nnf\n")
29
+ end
30
+
31
+ promise.then do |es|
32
+ es.first.message.should.eq 'boom'
33
+ raise 'nnf'
34
+ end
35
+
36
+ promise.defer do
37
+ raise 'boom'
38
+ end.wait
39
+
40
+ errors.map(&:message).should.eq ['boom']
41
+ end
42
+ end
43
+
44
+ would 'then then then' do
45
+ plusone = lambda{ |r| r + 1 }
46
+ promise = Promise.new
47
+ 2.times{ promise.then(&plusone).then(&plusone).then(&plusone) }
48
+ promise.fulfill(0)
49
+ promise.yield.should.eq 6
50
+ end
51
+
52
+ would 'transform to an exception and raise it' do
53
+ promise = Promise.new
54
+ promise.then(&RuntimeError.method(:new))
55
+ promise.defer{ 'nnf' }
56
+ expect.raise(RuntimeError) do
57
+ promise.yield
58
+ end.message.should.eq 'nnf'
59
+ end
16
60
  end
17
61
 
18
- after do
19
- Muack.verify
62
+ describe 'call' do
63
+ would 'call in the current thread' do
64
+ promise = Promise.new
65
+ promise.call{ raise 'nnf' }
66
+ promise.send(:thread).should.eq Thread.current
67
+ promise.send(:task) .should.eq nil
68
+ promise.send(:error).message.should.eq 'nnf'
69
+ expect.raise(RuntimeError) do
70
+ promise.yield
71
+ end.message.should.eq 'nnf'
72
+ end
20
73
  end
21
74
 
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
75
+ describe 'defer' do
76
+ describe 'with mock' do
77
+ after do
78
+ Muack.verify
79
+ end
80
+
81
+ would 'call in a new thread if no pool' do
82
+ thread = nil
83
+ rd, wr = IO.pipe
84
+ mock(Thread).new.with_any_args.peek_return do |t|
85
+ thread = t
86
+ wr.puts
87
+ end
88
+ Promise.new.defer do
89
+ rd.gets
90
+ Thread.current.should.eq thread
91
+ end.yield
92
+ end
93
+ end
94
+
95
+ would 'work, fulfill, yield' do
96
+ value = 'body'
97
+ flag = 0
98
+ promise = Promise.new.defer do
99
+ flag.should.eq 0
100
+ flag += 1
101
+ value
102
+ end
103
+ promise.yield.should.eq value
104
+ promise.send(:value).should.eq value
105
+ promise.send(:result).should.eq value
106
+ promise.should.resolved?
107
+ flag.should.eq 1
108
+ end
109
+
110
+ would 'work, reject, wait' do
111
+ flag = 0
112
+ promise = Promise.new.defer do
113
+ flag.should.eq 0
114
+ flag += 1
115
+ raise 'boom'
116
+ end
117
+ promise.wait
118
+ flag.should.eq 1
119
+ promise.send(:error).message.should.eq 'boom'
120
+ end
121
+ end
122
+
123
+ describe 'wait' do
124
+ would 'broadcast to different threads' do
125
+ flag = 0
126
+ mutex = Mutex.new
127
+ promise = Promise.new
128
+ threads = 3.times.map do
129
+ Thread.new do
130
+ mutex.synchronize{ flag += 1 }
131
+ promise.wait
132
+ mutex.synchronize{ flag += 1 }
133
+ promise.yield
134
+ end
135
+ end
136
+ Thread.pass until flag == 3
137
+ promise.fulfill('ok')
138
+ Thread.pass until flag == 6
139
+ threads.map(&:value).should.eq %w[ok ok ok]
140
+ end
33
141
  end
34
142
  end
@@ -0,0 +1,30 @@
1
+
2
+ require 'promise_pool/test'
3
+
4
+ describe 'README.md' do
5
+ readme = File.read("#{__dir__}/../README.md")
6
+ codes = readme.scan(
7
+ /### ([^\n]+).+?``` ruby\n(.+?)\n```\n\nPrints:\n\n```\n(.+?)```/m)
8
+
9
+ context = Class.new(Struct.new(:result)) do
10
+ def sleep sec=nil
11
+ if sec
12
+ Kernel.sleep(sec / 100.0)
13
+ else
14
+ Kernel.sleep
15
+ end
16
+ end
17
+
18
+ def puts str
19
+ result << "#{str}\n"
20
+ end
21
+ end
22
+
23
+ codes.each.with_index do |(title, code, test), index|
24
+ would "pass README.md #%02d #{title}" % index do
25
+ ctx = context.new([])
26
+ ctx.instance_eval(code, 'README.md', 0)
27
+ ctx.result.should.eq test.lines
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,50 @@
1
+
2
+ require 'promise_pool/test'
3
+
4
+ describe PromisePool::ThreadPool do
5
+ before do
6
+ @pool = ThreadPool.new(1)
7
+ @promise = Promise.new
8
+ end
9
+
10
+ after do
11
+ @pool.shutdown
12
+ @pool.size.should.eq 0
13
+ end
14
+
15
+ def defer &block
16
+ @promise.defer(@pool, &block)
17
+ end
18
+
19
+ would 'use the pool if passed' do
20
+ @pool.size.should.eq 0
21
+ defer do
22
+ @pool.size
23
+ end.yield.should.eq 1
24
+ end
25
+
26
+ would 'call in thread pool if pool_size > 0' do
27
+ flag = 0
28
+ rd, wr = IO.pipe
29
+ defer do
30
+ rd.gets
31
+ flag.should.eq 0
32
+ flag += 1
33
+ raise 'nnf'
34
+ end
35
+ p1 = Promise.new
36
+ p1.defer(@pool) do # block until promise #0 is done because max_size == 1
37
+ flag.should.eq 1
38
+ flag += 1
39
+ raise 'boom'
40
+ end
41
+ wr.puts # start promise #0
42
+
43
+ # even if we're not yielding, the block should still be resolved,
44
+ # so there should not have any deadlock here.
45
+ expect.raise(RuntimeError){ p1.yield }.message.should.eq 'boom'
46
+ expect.raise(RuntimeError){ @promise.yield }.message.should.eq 'nnf'
47
+
48
+ flag.should.eq 2
49
+ end
50
+ end
data/test/test_timer.rb CHANGED
@@ -2,4 +2,64 @@
2
2
  require 'promise_pool/test'
3
3
 
4
4
  describe PromisePool::Timer do
5
+ before do
6
+ @timer = Timer.new(0.01)
7
+ end
8
+
9
+ def expect_raise
10
+ expect.raise(@timer.error.class) do
11
+ yield
12
+ end.should.eq @timer.error
13
+ end
14
+
15
+ would 'raise timeout if task has not started' do
16
+ pool = ThreadPool.new(0)
17
+ expect_raise do
18
+ Promise.new(@timer).defer(pool) do
19
+ never called
20
+ end.yield
21
+ end
22
+ end
23
+
24
+ describe 'with flag' do
25
+ before do
26
+ @flag = false
27
+ end
28
+
29
+ after do
30
+ @flag.should.eq true
31
+ end
32
+
33
+ would 'raise timeout if the task started' do
34
+ pool = ThreadPool.new(1)
35
+ expect_raise do
36
+ Promise.new(@timer).defer(pool) do
37
+ @flag = true
38
+ sleep
39
+ never called
40
+ end.yield
41
+ end
42
+ pool.shutdown
43
+ end
44
+
45
+ would 'raise timeout in the thread' do
46
+ expect_raise do
47
+ Promise.new(@timer).defer do
48
+ @flag = true
49
+ sleep
50
+ never called
51
+ end.yield
52
+ end
53
+ end
54
+
55
+ would 'raise timeout even with inline call' do
56
+ expect_raise do
57
+ Promise.new(@timer).call do
58
+ @flag = true
59
+ sleep
60
+ never called
61
+ end.yield
62
+ end
63
+ end
64
+ end
5
65
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: promise_pool
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Lin Jen-Shin (godfat)
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2016-01-21 00:00:00.000000000 Z
11
+ date: 2016-01-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: timers
@@ -24,7 +24,8 @@ dependencies:
24
24
  - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: 4.0.1
27
- description: promise_pool
27
+ description: promise_pool is a promise implementation backed by threads or threads
28
+ pool.
28
29
  email:
29
30
  - godfat (XD) godfat.org
30
31
  executables: []
@@ -34,13 +35,13 @@ files:
34
35
  - ".gitignore"
35
36
  - ".gitmodules"
36
37
  - ".travis.yml"
38
+ - CHANGES.md
37
39
  - Gemfile
38
40
  - README.md
39
41
  - Rakefile
40
42
  - lib/promise_pool.rb
41
43
  - lib/promise_pool/future.rb
42
44
  - lib/promise_pool/promise.rb
43
- - lib/promise_pool/promise_eager.rb
44
45
  - lib/promise_pool/queue.rb
45
46
  - lib/promise_pool/task.rb
46
47
  - lib/promise_pool/test.rb
@@ -50,9 +51,10 @@ files:
50
51
  - promise_pool.gemspec
51
52
  - task/README.md
52
53
  - task/gemgem.rb
53
- - test/test_pool.rb
54
+ - test/test_future.rb
54
55
  - test/test_promise.rb
55
- - test/test_promise_eager.rb
56
+ - test/test_readme.rb
57
+ - test/test_thread_pool.rb
56
58
  - test/test_timer.rb
57
59
  homepage: https://github.com/godfat/promise_pool
58
60
  licenses:
@@ -77,9 +79,10 @@ rubyforge_project:
77
79
  rubygems_version: 2.5.1
78
80
  signing_key:
79
81
  specification_version: 4
80
- summary: promise_pool
82
+ summary: promise_pool is a promise implementation backed by threads or threads pool.
81
83
  test_files:
82
- - test/test_pool.rb
84
+ - test/test_future.rb
83
85
  - test/test_promise.rb
84
- - test/test_promise_eager.rb
86
+ - test/test_readme.rb
87
+ - test/test_thread_pool.rb
85
88
  - test/test_timer.rb
@@ -1,36 +0,0 @@
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
data/test/test_pool.rb DELETED
@@ -1,74 +0,0 @@
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
@@ -1,41 +0,0 @@
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