promise_pool 0.1.0 → 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
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