concurrent-ruby 0.1.1 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +48 -1
  3. data/lib/concurrent.rb +8 -1
  4. data/lib/concurrent/agent.rb +19 -40
  5. data/lib/concurrent/cached_thread_pool.rb +10 -11
  6. data/lib/concurrent/defer.rb +8 -12
  7. data/lib/concurrent/executor.rb +95 -0
  8. data/lib/concurrent/fixed_thread_pool.rb +12 -6
  9. data/lib/concurrent/functions.rb +120 -0
  10. data/lib/concurrent/future.rb +8 -20
  11. data/lib/concurrent/global_thread_pool.rb +13 -0
  12. data/lib/concurrent/goroutine.rb +5 -1
  13. data/lib/concurrent/null_thread_pool.rb +22 -0
  14. data/lib/concurrent/obligation.rb +10 -64
  15. data/lib/concurrent/promise.rb +38 -60
  16. data/lib/concurrent/reactor.rb +166 -0
  17. data/lib/concurrent/reactor/drb_async_demux.rb +83 -0
  18. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -0
  19. data/lib/concurrent/supervisor.rb +100 -0
  20. data/lib/concurrent/thread_pool.rb +16 -5
  21. data/lib/concurrent/utilities.rb +8 -0
  22. data/lib/concurrent/version.rb +1 -1
  23. data/md/defer.md +4 -4
  24. data/md/executor.md +187 -0
  25. data/md/promise.md +2 -0
  26. data/md/thread_pool.md +27 -0
  27. data/spec/concurrent/agent_spec.rb +8 -27
  28. data/spec/concurrent/cached_thread_pool_spec.rb +14 -1
  29. data/spec/concurrent/defer_spec.rb +17 -21
  30. data/spec/concurrent/event_machine_defer_proxy_spec.rb +159 -149
  31. data/spec/concurrent/executor_spec.rb +200 -0
  32. data/spec/concurrent/fixed_thread_pool_spec.rb +2 -3
  33. data/spec/concurrent/functions_spec.rb +217 -0
  34. data/spec/concurrent/future_spec.rb +4 -11
  35. data/spec/concurrent/global_thread_pool_spec.rb +38 -0
  36. data/spec/concurrent/goroutine_spec.rb +15 -0
  37. data/spec/concurrent/null_thread_pool_spec.rb +54 -0
  38. data/spec/concurrent/obligation_shared.rb +127 -116
  39. data/spec/concurrent/promise_spec.rb +16 -14
  40. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -0
  41. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -0
  42. data/spec/concurrent/reactor_spec.rb +364 -0
  43. data/spec/concurrent/supervisor_spec.rb +258 -0
  44. data/spec/concurrent/thread_pool_shared.rb +156 -161
  45. data/spec/concurrent/utilities_spec.rb +30 -1
  46. data/spec/spec_helper.rb +13 -0
  47. metadata +38 -9
@@ -26,8 +26,10 @@ module Concurrent
26
26
  end
27
27
 
28
28
  def kill
29
- @status = :killed
30
- @pool.each{|t| Thread.kill(t) }
29
+ mutex.synchronize do
30
+ @status = :killed
31
+ @pool.each{|t| Thread.kill(t) }
32
+ end
31
33
  end
32
34
 
33
35
  def size
@@ -50,7 +52,9 @@ module Concurrent
50
52
 
51
53
  # @private
52
54
  def status # :nodoc:
53
- @pool.collect{|t| t.status }
55
+ mutex.synchronize do
56
+ @pool.collect{|t| t.status }
57
+ end
54
58
  end
55
59
 
56
60
  private
@@ -78,9 +82,11 @@ module Concurrent
78
82
  def collect_garbage # :nodoc:
79
83
  @collector = Thread.new do
80
84
  sleep(1)
81
- @pool.size.times do |i|
82
- if @pool[i].status.nil?
83
- @pool[i] = create_worker_thread
85
+ mutex.synchronize do
86
+ @pool.size.times do |i|
87
+ if @pool[i].status.nil?
88
+ @pool[i] = create_worker_thread
89
+ end
84
90
  end
85
91
  end
86
92
  end
@@ -0,0 +1,120 @@
1
+ require 'concurrent/agent'
2
+ require 'concurrent/defer'
3
+ require 'concurrent/future'
4
+ require 'concurrent/promise'
5
+
6
+ module Kernel
7
+
8
+ ## agent
9
+
10
+ def agent(initial, timeout = Concurrent::Agent::TIMEOUT)
11
+ return Concurrent::Agent.new(initial, timeout)
12
+ end
13
+ module_function :agent
14
+
15
+ def post(object, &block)
16
+ if object.respond_to?(:post)
17
+ return object.post(&block)
18
+ else
19
+ raise ArgumentError.new('object does not support #post')
20
+ end
21
+ end
22
+ module_function :post
23
+
24
+ ## defer
25
+
26
+ def defer(*args, &block)
27
+ return Concurrent::Defer.new(*args, &block)
28
+ end
29
+ module_function :defer
30
+
31
+ ## executor
32
+
33
+ def executor(*args, &block)
34
+ return Concurrent::Executor.run(*args, &block)
35
+ end
36
+ module_function :executor
37
+
38
+ ## future
39
+
40
+ def future(*args, &block)
41
+ return Concurrent::Future.new(*args, &block)
42
+ end
43
+ module_function :future
44
+
45
+ ## obligation
46
+
47
+ def deref(object, timeout = nil)
48
+ if object.respond_to?(:deref)
49
+ return object.deref(timeout)
50
+ elsif object.respond_to?(:value)
51
+ return object.value(timeout)
52
+ else
53
+ raise ArgumentError.new('object does not support #deref')
54
+ end
55
+ end
56
+ module_function :deref
57
+
58
+ def pending?(object)
59
+ if object.respond_to?(:pending?)
60
+ return object.pending?
61
+ else
62
+ raise ArgumentError.new('object does not support #pending?')
63
+ end
64
+ end
65
+ module_function :pending?
66
+
67
+ def fulfilled?(object)
68
+ if object.respond_to?(:fulfilled?)
69
+ return object.fulfilled?
70
+ elsif object.respond_to?(:realized?)
71
+ return object.realized?
72
+ else
73
+ raise ArgumentError.new('object does not support #fulfilled?')
74
+ end
75
+ end
76
+ module_function :fulfilled?
77
+
78
+ def realized?(object)
79
+ if object.respond_to?(:realized?)
80
+ return object.realized?
81
+ elsif object.respond_to?(:fulfilled?)
82
+ return object.fulfilled?
83
+ else
84
+ raise ArgumentError.new('object does not support #realized?')
85
+ end
86
+ end
87
+ module_function :realized?
88
+
89
+ def rejected?(object)
90
+ if object.respond_to?(:rejected?)
91
+ return object.rejected?
92
+ else
93
+ raise ArgumentError.new('object does not support #rejected?')
94
+ end
95
+ end
96
+ module_function :rejected?
97
+
98
+ ## promise
99
+
100
+ # Creates a new promise object. "A promise represents the eventual
101
+ # value returned from the single completion of an operation."
102
+ # Promises can be chained in a tree structure where each promise
103
+ # has zero or more children. Promises are resolved asynchronously
104
+ # in the order they are added to the tree. Parents are guaranteed
105
+ # to be resolved before their children. The result of each promise
106
+ # is passes to each of its children when the child resolves. When
107
+ # a promise is rejected all its children will be summarily rejected.
108
+ # A promise added to a rejected promise will immediately be rejected.
109
+ # A promise that is neither resolved or rejected is pending.
110
+ #
111
+ # @param args [Array] zero or more arguments for the block
112
+ # @param block [Proc] the block to call when attempting fulfillment
113
+ #
114
+ # @see Promise
115
+ # @see http://wiki.commonjs.org/wiki/Promises/A
116
+ def promise(*args, &block)
117
+ return Concurrent::Promise.new(*args, &block)
118
+ end
119
+ module_function :promise
120
+ end
@@ -8,17 +8,17 @@ module Concurrent
8
8
 
9
9
  class Future
10
10
  include Obligation
11
+ include UsesGlobalThreadPool
12
+
11
13
  behavior(:future)
12
14
 
13
15
  def initialize(*args, &block)
14
-
15
16
  unless block_given?
16
17
  @state = :fulfilled
17
18
  else
18
19
  @value = nil
19
20
  @state = :pending
20
- $GLOBAL_THREAD_POOL.post do
21
- Thread.pass
21
+ Future.thread_pool.post(*args) do
22
22
  work(*args, &block)
23
23
  end
24
24
  end
@@ -28,27 +28,15 @@ module Concurrent
28
28
 
29
29
  # @private
30
30
  def work(*args) # :nodoc:
31
- semaphore.synchronize do
31
+ mutex.synchronize do
32
32
  begin
33
- atomic {
34
- @value = yield(*args)
35
- @state = :fulfilled
36
- }
33
+ @value = yield(*args)
34
+ @state = :fulfilled
37
35
  rescue Exception => ex
38
- atomic {
39
- @state = :rejected
40
- @reason = ex
41
- }
36
+ @state = :rejected
37
+ @reason = ex
42
38
  end
43
39
  end
44
40
  end
45
41
  end
46
42
  end
47
-
48
- module Kernel
49
-
50
- def future(*args, &block)
51
- return Concurrent::Future.new(*args, &block)
52
- end
53
- module_function :future
54
- end
@@ -1,3 +1,16 @@
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,7 +19,11 @@ module Kernel
19
19
  # @see https://gobyexample.com/goroutines
20
20
  def go(*args, &block)
21
21
  return false unless block_given?
22
- $GLOBAL_THREAD_POOL.post(*args, &block)
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
23
27
  end
24
28
  module_function :go
25
29
  end
@@ -0,0 +1,22 @@
1
+ require 'concurrent/global_thread_pool'
2
+
3
+ module Concurrent
4
+
5
+ class NullThreadPool
6
+ behavior(:global_thread_pool)
7
+
8
+ def self.post(*args, &block)
9
+ Thread.new(*args, &block)
10
+ return true
11
+ end
12
+
13
+ def post(*args, &block)
14
+ return NullThreadPool.post(*args, &block)
15
+ end
16
+
17
+ def <<(block)
18
+ NullThreadPool.post(&block)
19
+ return self
20
+ end
21
+ end
22
+ end
@@ -33,19 +33,23 @@ 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
+
36
40
  # Is obligation completion still pending?
37
41
  # @return [Boolean]
38
- def pending?() return(!(fulfilled? || rejected?)); end
42
+ def pending?() return(@state == :pending); end
39
43
 
40
44
  def value(timeout = nil)
41
- if !pending? || timeout == 0
45
+ if timeout == 0 || ! pending?
42
46
  return @value
43
47
  elsif timeout.nil?
44
- return semaphore.synchronize { @value }
48
+ return mutex.synchronize { v = @value }
45
49
  else
46
50
  begin
47
51
  return Timeout::timeout(timeout.to_f) {
48
- semaphore.synchronize { @value }
52
+ mutex.synchronize { v = @value }
49
53
  }
50
54
  rescue Timeout::Error => ex
51
55
  return nil
@@ -54,68 +58,10 @@ module Concurrent
54
58
  end
55
59
  alias_method :deref, :value
56
60
 
57
- # Has the promise been rejected?
58
- # @return [Boolean]
59
- def rejected?() return(@state == :rejected); end
60
-
61
61
  protected
62
62
 
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
63
+ def mutex
64
+ @mutex ||= Mutex.new
118
65
  end
119
66
  end
120
- module_function :rejected?
121
67
  end
@@ -1,5 +1,6 @@
1
1
  require 'thread'
2
2
 
3
+ require 'concurrent/global_thread_pool'
3
4
  require 'concurrent/obligation'
4
5
  require 'concurrent/utilities'
5
6
 
@@ -7,6 +8,8 @@ module Concurrent
7
8
 
8
9
  class Promise
9
10
  include Obligation
11
+ include UsesGlobalThreadPool
12
+
10
13
  behavior(:future)
11
14
  behavior(:promise)
12
15
 
@@ -33,17 +36,22 @@ module Concurrent
33
36
  @chain = [self]
34
37
  end
35
38
 
36
- @mutex = Mutex.new
39
+ @lock = Mutex.new
37
40
  @handler = block || Proc.new{|result| result }
38
41
  @state = :pending
39
42
  @value = nil
40
43
  @reason = nil
44
+ @rescued = false
41
45
  @children = []
42
46
  @rescuers = []
43
47
 
44
48
  realize(*args) if root?
45
49
  end
46
50
 
51
+ def rescued?
52
+ return @rescued
53
+ end
54
+
47
55
  # Create a new child Promise. The block argument for the child will
48
56
  # be the result of fulfilling its parent. If the child will
49
57
  # immediately be rejected if the parent has already been rejected.
@@ -52,7 +60,7 @@ module Concurrent
52
60
  #
53
61
  # @return [Promise] the new promise
54
62
  def then(&block)
55
- child = @mutex.synchronize do
63
+ child = @lock.synchronize do
56
64
  block = Proc.new{|result| result } unless block_given?
57
65
  @children << Promise.new(self, &block)
58
66
  @children.last.on_reject(@reason) if rejected?
@@ -73,8 +81,14 @@ module Concurrent
73
81
  #
74
82
  # @return [self] so that additional chaining can occur
75
83
  def rescue(clazz = Exception, &block)
76
- @mutex.synchronize do
77
- @rescuers << Rescuer.new(clazz, block) if block_given?
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
78
92
  end
79
93
  return self
80
94
  end
@@ -90,15 +104,6 @@ module Concurrent
90
104
  # @private
91
105
  Rescuer = Struct.new(:clazz, :block)
92
106
 
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
-
102
107
  # @private
103
108
  def root? # :nodoc:
104
109
  @parent.nil?
@@ -115,47 +120,44 @@ module Concurrent
115
120
 
116
121
  # @private
117
122
  def on_fulfill(value) # :nodoc:
118
- @mutex.synchronize do
119
- if pending?
120
- @value = @handler.call(value)
121
- @state = :fulfilled
122
- @reason = nil
123
- end
123
+ @lock.synchronize do
124
+ @value = @handler.call(value)
125
+ @state = :fulfilled
126
+ @reason = nil
124
127
  end
125
128
  return @value
126
129
  end
127
130
 
128
131
  # @private
129
132
  def on_reject(reason) # :nodoc:
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
133
+ @value = nil
134
+ @state = :rejected
135
+ @reason = reason
136
+ try_rescue(reason)
137
+ @children.each{|child| child.on_reject(reason) }
139
138
  end
140
139
 
141
140
  # @private
142
- def try_rescue(ex) # :nodoc:
143
- rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
144
- rescuer.block.call(ex) if rescuer
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
145
148
  rescue Exception => e
146
149
  # supress
147
150
  end
148
151
 
149
152
  # @private
150
153
  def realize(*args) # :nodoc:
151
- Thread.new(@chain, @mutex, args) do |chain, mutex, args|
154
+ Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
152
155
  result = args.length == 1 ? args.first : args
153
156
  index = 0
154
157
  loop do
155
- Thread.pass
156
- current = mutex.synchronize{ chain[index] }
158
+ current = lock.synchronize{ chain[index] }
157
159
  unless current.rejected?
158
- current.semaphore.synchronize do
160
+ current.mutex.synchronize do
159
161
  begin
160
162
  result = current.on_fulfill(result)
161
163
  rescue Exception => ex
@@ -164,33 +166,9 @@ module Concurrent
164
166
  end
165
167
  end
166
168
  index += 1
167
- sleep while index >= chain.length
169
+ Thread.pass while index >= chain.length
168
170
  end
169
171
  end
170
172
  end
171
173
  end
172
174
  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