concurrent-ruby 0.1.1.pre.5 → 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -48
  3. data/lib/concurrent.rb +0 -6
  4. data/lib/concurrent/agent.rb +40 -19
  5. data/lib/concurrent/cached_thread_pool.rb +11 -10
  6. data/lib/concurrent/defer.rb +12 -8
  7. data/lib/concurrent/fixed_thread_pool.rb +6 -12
  8. data/lib/concurrent/future.rb +20 -8
  9. data/lib/concurrent/global_thread_pool.rb +0 -13
  10. data/lib/concurrent/goroutine.rb +1 -5
  11. data/lib/concurrent/obligation.rb +64 -10
  12. data/lib/concurrent/promise.rb +60 -38
  13. data/lib/concurrent/thread_pool.rb +5 -16
  14. data/lib/concurrent/utilities.rb +0 -8
  15. data/lib/concurrent/version.rb +1 -1
  16. data/md/defer.md +4 -4
  17. data/md/promise.md +0 -2
  18. data/md/thread_pool.md +0 -27
  19. data/spec/concurrent/agent_spec.rb +27 -8
  20. data/spec/concurrent/cached_thread_pool_spec.rb +1 -14
  21. data/spec/concurrent/defer_spec.rb +21 -17
  22. data/spec/concurrent/event_machine_defer_proxy_spec.rb +149 -159
  23. data/spec/concurrent/fixed_thread_pool_spec.rb +3 -2
  24. data/spec/concurrent/future_spec.rb +10 -3
  25. data/spec/concurrent/goroutine_spec.rb +0 -15
  26. data/spec/concurrent/obligation_shared.rb +2 -16
  27. data/spec/concurrent/promise_spec.rb +13 -15
  28. data/spec/concurrent/thread_pool_shared.rb +5 -5
  29. data/spec/concurrent/utilities_spec.rb +1 -30
  30. data/spec/spec_helper.rb +0 -25
  31. metadata +7 -28
  32. data/lib/concurrent/executor.rb +0 -95
  33. data/lib/concurrent/functions.rb +0 -120
  34. data/lib/concurrent/null_thread_pool.rb +0 -22
  35. data/lib/concurrent/reactor.rb +0 -161
  36. data/lib/concurrent/reactor/drb_async_demux.rb +0 -74
  37. data/lib/concurrent/reactor/tcp_sync_demux.rb +0 -98
  38. data/md/executor.md +0 -176
  39. data/spec/concurrent/executor_spec.rb +0 -200
  40. data/spec/concurrent/functions_spec.rb +0 -217
  41. data/spec/concurrent/global_thread_pool_spec.rb +0 -38
  42. data/spec/concurrent/null_thread_pool_spec.rb +0 -54
  43. data/spec/concurrent/reactor/drb_async_demux_spec.rb +0 -12
  44. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +0 -12
  45. data/spec/concurrent/reactor_spec.rb +0 -351
@@ -1,16 +1,3 @@
1
1
  require 'concurrent/cached_thread_pool'
2
2
 
3
3
  $GLOBAL_THREAD_POOL ||= Concurrent::CachedThreadPool.new
4
-
5
- module Concurrent
6
-
7
- module UsesGlobalThreadPool
8
-
9
- def self.included(base)
10
- class << base
11
- attr_accessor :thread_pool
12
- end
13
- base.thread_pool = $GLOBAL_THREAD_POOL
14
- end
15
- end
16
- end
@@ -19,11 +19,7 @@ module Kernel
19
19
  # @see https://gobyexample.com/goroutines
20
20
  def go(*args, &block)
21
21
  return false unless block_given?
22
- if args.first.behaves_as?(:global_thread_pool)
23
- args.first.post(*args.slice(1, args.length), &block)
24
- else
25
- $GLOBAL_THREAD_POOL.post(*args, &block)
26
- end
22
+ $GLOBAL_THREAD_POOL.post(*args, &block)
27
23
  end
28
24
  module_function :go
29
25
  end
@@ -33,23 +33,19 @@ module Concurrent
33
33
  def fulfilled?() return(@state == :fulfilled); end
34
34
  alias_method :realized?, :fulfilled?
35
35
 
36
- # Has the promise been rejected?
37
- # @return [Boolean]
38
- def rejected?() return(@state == :rejected); end
39
-
40
36
  # Is obligation completion still pending?
41
37
  # @return [Boolean]
42
- def pending?() return(@state == :pending); end
38
+ def pending?() return(!(fulfilled? || rejected?)); end
43
39
 
44
40
  def value(timeout = nil)
45
- if timeout == 0 || ! pending?
41
+ if !pending? || timeout == 0
46
42
  return @value
47
43
  elsif timeout.nil?
48
- return mutex.synchronize { v = @value }
44
+ return semaphore.synchronize { @value }
49
45
  else
50
46
  begin
51
47
  return Timeout::timeout(timeout.to_f) {
52
- mutex.synchronize { v = @value }
48
+ semaphore.synchronize { @value }
53
49
  }
54
50
  rescue Timeout::Error => ex
55
51
  return nil
@@ -58,10 +54,68 @@ module Concurrent
58
54
  end
59
55
  alias_method :deref, :value
60
56
 
57
+ # Has the promise been rejected?
58
+ # @return [Boolean]
59
+ def rejected?() return(@state == :rejected); end
60
+
61
61
  protected
62
62
 
63
- def mutex
64
- @mutex ||= Mutex.new
63
+ def semaphore
64
+ @semaphore ||= Mutex.new
65
+ end
66
+ end
67
+ end
68
+
69
+ module Kernel
70
+
71
+ def deref(obligation, timeout = nil)
72
+ if obligation.respond_to?(:deref)
73
+ return obligation.deref(timeout)
74
+ elsif obligation.respond_to?(:value)
75
+ return obligation.deref(timeout)
76
+ else
77
+ return nil
78
+ end
79
+ end
80
+ module_function :deref
81
+
82
+ def pending?(obligation)
83
+ if obligation.respond_to?(:pending?)
84
+ return obligation.pending?
85
+ else
86
+ return false
87
+ end
88
+ end
89
+ module_function :pending?
90
+
91
+ def fulfilled?(obligation)
92
+ if obligation.respond_to?(:fulfilled?)
93
+ return obligation.fulfilled?
94
+ elsif obligation.respond_to?(:realized?)
95
+ return obligation.realized?
96
+ else
97
+ return false
98
+ end
99
+ end
100
+ module_function :fulfilled?
101
+
102
+ def realized?(obligation)
103
+ if obligation.respond_to?(:realized?)
104
+ return obligation.realized?
105
+ elsif obligation.respond_to?(:fulfilled?)
106
+ return obligation.fulfilled?
107
+ else
108
+ return false
109
+ end
110
+ end
111
+ module_function :realized?
112
+
113
+ def rejected?(obligation)
114
+ if obligation.respond_to?(:rejected?)
115
+ return obligation.rejected?
116
+ else
117
+ return false
65
118
  end
66
119
  end
120
+ module_function :rejected?
67
121
  end
@@ -1,6 +1,5 @@
1
1
  require 'thread'
2
2
 
3
- require 'concurrent/global_thread_pool'
4
3
  require 'concurrent/obligation'
5
4
  require 'concurrent/utilities'
6
5
 
@@ -8,8 +7,6 @@ module Concurrent
8
7
 
9
8
  class Promise
10
9
  include Obligation
11
- include UsesGlobalThreadPool
12
-
13
10
  behavior(:future)
14
11
  behavior(:promise)
15
12
 
@@ -36,22 +33,17 @@ module Concurrent
36
33
  @chain = [self]
37
34
  end
38
35
 
39
- @lock = Mutex.new
36
+ @mutex = Mutex.new
40
37
  @handler = block || Proc.new{|result| result }
41
38
  @state = :pending
42
39
  @value = nil
43
40
  @reason = nil
44
- @rescued = false
45
41
  @children = []
46
42
  @rescuers = []
47
43
 
48
44
  realize(*args) if root?
49
45
  end
50
46
 
51
- def rescued?
52
- return @rescued
53
- end
54
-
55
47
  # Create a new child Promise. The block argument for the child will
56
48
  # be the result of fulfilling its parent. If the child will
57
49
  # immediately be rejected if the parent has already been rejected.
@@ -60,7 +52,7 @@ module Concurrent
60
52
  #
61
53
  # @return [Promise] the new promise
62
54
  def then(&block)
63
- child = @lock.synchronize do
55
+ child = @mutex.synchronize do
64
56
  block = Proc.new{|result| result } unless block_given?
65
57
  @children << Promise.new(self, &block)
66
58
  @children.last.on_reject(@reason) if rejected?
@@ -81,14 +73,8 @@ module Concurrent
81
73
  #
82
74
  # @return [self] so that additional chaining can occur
83
75
  def rescue(clazz = Exception, &block)
84
- return self if fulfilled? || rescued? || ! block_given?
85
- @lock.synchronize do
86
- rescuer = Rescuer.new(clazz, block)
87
- if pending?
88
- @rescuers << rescuer
89
- else
90
- try_rescue(reason, rescuer)
91
- end
76
+ @mutex.synchronize do
77
+ @rescuers << Rescuer.new(clazz, block) if block_given?
92
78
  end
93
79
  return self
94
80
  end
@@ -104,6 +90,15 @@ module Concurrent
104
90
  # @private
105
91
  Rescuer = Struct.new(:clazz, :block)
106
92
 
93
+ # @private
94
+ def root # :nodoc:
95
+ return atomic {
96
+ current = self
97
+ current = current.parent until current.root?
98
+ current
99
+ }
100
+ end
101
+
107
102
  # @private
108
103
  def root? # :nodoc:
109
104
  @parent.nil?
@@ -120,44 +115,47 @@ module Concurrent
120
115
 
121
116
  # @private
122
117
  def on_fulfill(value) # :nodoc:
123
- @lock.synchronize do
124
- @value = @handler.call(value)
125
- @state = :fulfilled
126
- @reason = nil
118
+ @mutex.synchronize do
119
+ if pending?
120
+ @value = @handler.call(value)
121
+ @state = :fulfilled
122
+ @reason = nil
123
+ end
127
124
  end
128
125
  return @value
129
126
  end
130
127
 
131
128
  # @private
132
129
  def on_reject(reason) # :nodoc:
133
- @value = nil
134
- @state = :rejected
135
- @reason = reason
136
- try_rescue(reason)
137
- @children.each{|child| child.on_reject(reason) }
130
+ @mutex.synchronize do
131
+ if pending?
132
+ @state = :rejected
133
+ @reason = reason
134
+ self.try_rescue(reason)
135
+ @value = nil
136
+ end
137
+ @children.each{|child| child.on_reject(reason) }
138
+ end
138
139
  end
139
140
 
140
141
  # @private
141
- def try_rescue(ex, *rescuers) # :nodoc:
142
- rescuers = @rescuers if rescuers.empty?
143
- rescuer = rescuers.find{|r| ex.is_a?(r.clazz) }
144
- if rescuer
145
- rescuer.block.call(ex)
146
- @rescued = true
147
- end
142
+ def try_rescue(ex) # :nodoc:
143
+ rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
144
+ rescuer.block.call(ex) if rescuer
148
145
  rescue Exception => e
149
146
  # supress
150
147
  end
151
148
 
152
149
  # @private
153
150
  def realize(*args) # :nodoc:
154
- Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
151
+ Thread.new(@chain, @mutex, args) do |chain, mutex, args|
155
152
  result = args.length == 1 ? args.first : args
156
153
  index = 0
157
154
  loop do
158
- current = lock.synchronize{ chain[index] }
155
+ Thread.pass
156
+ current = mutex.synchronize{ chain[index] }
159
157
  unless current.rejected?
160
- current.mutex.synchronize do
158
+ current.semaphore.synchronize do
161
159
  begin
162
160
  result = current.on_fulfill(result)
163
161
  rescue Exception => ex
@@ -166,9 +164,33 @@ module Concurrent
166
164
  end
167
165
  end
168
166
  index += 1
169
- Thread.pass while index >= chain.length
167
+ sleep while index >= chain.length
170
168
  end
171
169
  end
172
170
  end
173
171
  end
174
172
  end
173
+
174
+ module Kernel
175
+
176
+ # Creates a new promise object. "A promise represents the eventual
177
+ # value returned from the single completion of an operation."
178
+ # Promises can be chained in a tree structure where each promise
179
+ # has zero or more children. Promises are resolved asynchronously
180
+ # in the order they are added to the tree. Parents are guaranteed
181
+ # to be resolved before their children. The result of each promise
182
+ # is passes to each of its children when the child resolves. When
183
+ # a promise is rejected all its children will be summarily rejected.
184
+ # A promise added to a rejected promise will immediately be rejected.
185
+ # A promise that is neither resolved or rejected is pending.
186
+ #
187
+ # @param args [Array] zero or more arguments for the block
188
+ # @param block [Proc] the block to call when attempting fulfillment
189
+ #
190
+ # @see Promise
191
+ # @see http://wiki.commonjs.org/wiki/Promises/A
192
+ def promise(*args, &block)
193
+ return Concurrent::Promise.new(*args, &block)
194
+ end
195
+ module_function :promise
196
+ end
@@ -35,7 +35,7 @@ module Concurrent
35
35
  end
36
36
 
37
37
  def shutdown?
38
- return @status == :shutdown
38
+ return ! running?
39
39
  end
40
40
 
41
41
  def killed?
@@ -43,14 +43,10 @@ module Concurrent
43
43
  end
44
44
 
45
45
  def shutdown
46
- mutex.synchronize do
47
- if @pool.empty?
48
- @status = :shutdown
49
- else
50
- @status = :shuttingdown
51
- @pool.size.times{ @queue << :stop }
52
- end
53
- end
46
+ atomic {
47
+ @pool.size.times{ @queue << :stop }
48
+ @status = :shuttingdown
49
+ }
54
50
  end
55
51
 
56
52
  def wait_for_termination(timeout = nil)
@@ -65,12 +61,5 @@ module Concurrent
65
61
  self.post(&block)
66
62
  return self
67
63
  end
68
-
69
- protected
70
-
71
- # @private
72
- def mutex # :nodoc:
73
- @mutex || Mutex.new
74
- end
75
64
  end
76
65
  end
@@ -20,13 +20,5 @@ module Kernel
20
20
  }.resume
21
21
  end
22
22
  module_function :atomic
23
- end
24
-
25
- class Mutex
26
23
 
27
- def sync_with_timeout(timeout, &block)
28
- Timeout::timeout(timeout) {
29
- synchronize(&block)
30
- }
31
- end
32
24
  end
@@ -1,3 +1,3 @@
1
1
  module Concurrent
2
- VERSION = '0.1.1.pre.5'
2
+ VERSION = '0.1.1'
3
3
  end
@@ -2,14 +2,14 @@
2
2
 
3
3
  In the pantheon of concurrency objects a `Defer` sits somewhere between `Future` and `Promise`.
4
4
  Inspired by [EventMachine's *defer* method](https://github.com/eventmachine/eventmachine/wiki/EM::Deferrable-and-EM.defer),
5
- a `Defer` can be considered a non-blocking `Future` or a simplified, non-blocking `Promise`. Defers run on the global thread pool.
5
+ a `Defer` can be considered a non-blocking `Future` or a simplified, non-blocking `Promise`.
6
6
 
7
7
  Unlike `Future` and `Promise` a defer is non-blocking. The deferred *operation* is performed on another
8
8
  thread. If the *operation* is successful an optional *callback* is called on the same thread as the *operation*.
9
9
  The result of the *operation* is passed to the *callbacl*. If the *operation* fails (by raising an exception)
10
- then an optional *errorback* (error callback) is called on the same thread as the *operation*. The raised
11
- exception is passed to the *errorback*. The calling thread is never aware of the result of the *operation*.
12
- This approach fits much more cleanly within an
10
+ then an optional *errorback* (error callback) is called on
11
+ the same thread as the *operation*. The raised exception is passed to the *errorback*. The calling thread is
12
+ never aware of the result of the *operation*. This approach fits much more cleanly within an
13
13
  [event-driven](http://en.wikipedia.org/wiki/Event-driven_programming) application.
14
14
 
15
15
  The operation of a `Defer` can easily be simulated using either `Future` or `Promise` and traditional branching
@@ -28,8 +28,6 @@ A *timeout* value can be passed to `value` to limit how long the call will block
28
28
  block indefinitely. If `0` the call will not block. Any other integer or float value will indicate the
29
29
  maximum number of seconds to block.
30
30
 
31
- Promises run on the global thread pool.
32
-
33
31
  ## Examples
34
32
 
35
33
  Start by requiring promises
@@ -151,12 +151,6 @@ $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10)
151
151
  old_global_pool.shutdown
152
152
  ```
153
153
 
154
- ### NullThreadPool
155
-
156
- If for some reason an appliction would be better served by *not* having a global thread pool, the
157
- `NullThreadPool` is provided. The `NullThreadPool` is compatible with the global thread pool but
158
- it is not an actual thread pool. Instead it spawns a new thread on every call to the `post` method.
159
-
160
154
  ### EventMachine
161
155
 
162
156
  The [EventMachine](http://rubyeventmachine.com/) library (source [online](https://github.com/eventmachine/eventmachine))
@@ -173,27 +167,6 @@ require 'functional/concurrency'
173
167
  $GLOBAL_THREAD_POOL = EventMachineDeferProxy.new
174
168
  ```
175
169
 
176
- ## Per-class Thread Pools
177
-
178
- Many of the classes in this library use the global thread pool rather than creating new threads.
179
- Classes such as `Agent`, `Defer`, and others follow this pattern. There may be cases where a
180
- program would be better suited for one or more of these classes used a different thread pool.
181
- All classes that use the global thread pool support a class-level `thread_pool` attribute accessor.
182
- This property defaults to the global thread pool but can be changed at any time. Once changed, all
183
- new instances of that class will use the new thread pool.
184
-
185
- ```ruby
186
- Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> true
187
-
188
- $GLOBAL_THREAD_POOL = Concurrent::FixedThreadPool.new(10) #=> #<Concurrent::FixedThreadPool:0x007fe31130f1f0 ...
189
-
190
- Concurrent::Agent.thread_pool == $GLOBAL_THREAD_POOL #=> false
191
-
192
- Concurrent::Defer.thread_pool = Concurrent::CachedThreadPool.new #=> #<Concurrent::CachedThreadPool:0x007fef1c6b6b48 ...
193
- Concurrent::Defer.thread_pool == Concurrent::Agent.thread_pool #=> false
194
- Concurrent::Defer.thread_pool == $GLOBAL_THREAD_POOL #=> false
195
- ```
196
-
197
170
  ## Copyright
198
171
 
199
172
  *Concurrent Ruby* is Copyright &copy; 2013 [Jerry D'Antonio](https://twitter.com/jerrydantonio).
@@ -16,7 +16,7 @@ module Concurrent
16
16
  end
17
17
 
18
18
  before(:each) do
19
- Agent.thread_pool = FixedThreadPool.new(1)
19
+ $GLOBAL_THREAD_POOL = CachedThreadPool.new
20
20
  end
21
21
 
22
22
  context '#initialize' do
@@ -38,7 +38,7 @@ module Concurrent
38
38
  end
39
39
 
40
40
  it 'spawns the worker thread' do
41
- Agent.thread_pool.should_receive(:post).once.with(any_args())
41
+ $GLOBAL_THREAD_POOL.should_receive(:post).once.with(any_args())
42
42
  Agent.new(0)
43
43
  end
44
44
  end
@@ -92,8 +92,6 @@ module Concurrent
92
92
  context '#post' do
93
93
 
94
94
  it 'adds the given block to the queue' do
95
- subject.post{ sleep(100) }
96
- sleep(0.1)
97
95
  before = subject.length
98
96
  subject.post{ nil }
99
97
  subject.post{ nil }
@@ -101,8 +99,6 @@ module Concurrent
101
99
  end
102
100
 
103
101
  it 'does not add to the queue when no block is given' do
104
- subject.post{ sleep(100) }
105
- sleep(0.1)
106
102
  before = subject.length
107
103
  subject.post
108
104
  subject.post{ nil }
@@ -117,8 +113,6 @@ module Concurrent
117
113
  end
118
114
 
119
115
  it 'should increase by one for each #post' do
120
- subject.post{ sleep(100) }
121
- sleep(0.1)
122
116
  subject.post{ sleep }
123
117
  subject.post{ sleep }
124
118
  subject.post{ sleep }
@@ -381,6 +375,31 @@ module Concurrent
381
375
  sleep(0.1)
382
376
  observer.value.should eq 10
383
377
  end
378
+
379
+ it 'aliases #<< for Agent#post' do
380
+ subject << proc{ 100 }
381
+ sleep(0.1)
382
+ subject.value.should eq 100
383
+
384
+ subject << lambda{ 100 }
385
+ sleep(0.1)
386
+ subject.value.should eq 100
387
+ end
388
+
389
+ it 'aliases Kernel#agent for Agent.new' do
390
+ agent(10).should be_a(Agent)
391
+ end
392
+
393
+ it 'aliases Kernel#deref for #deref' do
394
+ deref(Agent.new(10)).should eq 10
395
+ deref(Agent.new(10), 10).should eq 10
396
+ end
397
+
398
+ it 'aliases Kernel:post for Agent#post' do
399
+ post(subject){ 100 }
400
+ sleep(0.1)
401
+ subject.value.should eq 100
402
+ end
384
403
  end
385
404
  end
386
405
  end