concurrent-ruby 0.1.1.pre.5 → 0.1.1

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.
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