concurrent-ruby 0.2.1 → 0.2.2

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 (58) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -21
  3. data/README.md +276 -275
  4. data/lib/concurrent.rb +28 -28
  5. data/lib/concurrent/agent.rb +114 -114
  6. data/lib/concurrent/cached_thread_pool.rb +131 -131
  7. data/lib/concurrent/defer.rb +65 -65
  8. data/lib/concurrent/event.rb +60 -60
  9. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  10. data/lib/concurrent/executor.rb +96 -96
  11. data/lib/concurrent/fixed_thread_pool.rb +99 -99
  12. data/lib/concurrent/functions.rb +120 -120
  13. data/lib/concurrent/future.rb +42 -42
  14. data/lib/concurrent/global_thread_pool.rb +24 -16
  15. data/lib/concurrent/goroutine.rb +29 -29
  16. data/lib/concurrent/null_thread_pool.rb +22 -22
  17. data/lib/concurrent/obligation.rb +67 -67
  18. data/lib/concurrent/promise.rb +174 -174
  19. data/lib/concurrent/reactor.rb +166 -166
  20. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  21. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  22. data/lib/concurrent/supervisor.rb +105 -105
  23. data/lib/concurrent/thread_pool.rb +76 -76
  24. data/lib/concurrent/utilities.rb +32 -32
  25. data/lib/concurrent/version.rb +3 -3
  26. data/lib/concurrent_ruby.rb +1 -1
  27. data/md/agent.md +123 -123
  28. data/md/defer.md +174 -174
  29. data/md/event.md +32 -32
  30. data/md/executor.md +187 -187
  31. data/md/future.md +83 -83
  32. data/md/goroutine.md +52 -52
  33. data/md/obligation.md +32 -32
  34. data/md/promise.md +227 -227
  35. data/md/thread_pool.md +224 -224
  36. data/spec/concurrent/agent_spec.rb +390 -386
  37. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  38. data/spec/concurrent/defer_spec.rb +199 -195
  39. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  40. data/spec/concurrent/event_spec.rb +134 -134
  41. data/spec/concurrent/executor_spec.rb +200 -200
  42. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  43. data/spec/concurrent/functions_spec.rb +217 -217
  44. data/spec/concurrent/future_spec.rb +112 -108
  45. data/spec/concurrent/global_thread_pool_spec.rb +11 -38
  46. data/spec/concurrent/goroutine_spec.rb +67 -67
  47. data/spec/concurrent/null_thread_pool_spec.rb +57 -57
  48. data/spec/concurrent/obligation_shared.rb +132 -132
  49. data/spec/concurrent/promise_spec.rb +316 -312
  50. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  51. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  52. data/spec/concurrent/reactor_spec.rb +364 -364
  53. data/spec/concurrent/supervisor_spec.rb +269 -269
  54. data/spec/concurrent/thread_pool_shared.rb +204 -204
  55. data/spec/concurrent/uses_global_thread_pool_shared.rb +64 -0
  56. data/spec/concurrent/utilities_spec.rb +74 -74
  57. data/spec/spec_helper.rb +32 -32
  58. metadata +17 -19
@@ -1,16 +1,24 @@
1
- require 'concurrent/cached_thread_pool'
2
-
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
1
+ require 'concurrent/cached_thread_pool'
2
+
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
+ def thread_pool
12
+ @thread_pool || $GLOBAL_THREAD_POOL
13
+ end
14
+ def thread_pool=(pool)
15
+ if pool == $GLOBAL_THREAD_POOL
16
+ @thread_pool = nil
17
+ else
18
+ @thread_pool = pool
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -1,29 +1,29 @@
1
- require 'thread'
2
-
3
- require 'concurrent/global_thread_pool'
4
-
5
- module Kernel
6
-
7
- # Post the given agruments and block to the Global Thread Pool.
8
- #
9
- # @param args [Array] zero or more arguments for the block
10
- # @param block [Proc] operation to be performed concurrently
11
- #
12
- # @return [true,false] success/failre of thread creation
13
- #
14
- # @note Althought based on Go's goroutines and Erlang's spawn/1,
15
- # Ruby has a vastly different runtime. Threads aren't nearly as
16
- # efficient in Ruby. Use this function appropriately.
17
- #
18
- # @see http://golang.org/doc/effective_go.html#goroutines
19
- # @see https://gobyexample.com/goroutines
20
- def go(*args, &block)
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
27
- end
28
- module_function :go
29
- end
1
+ require 'thread'
2
+
3
+ require 'concurrent/global_thread_pool'
4
+
5
+ module Kernel
6
+
7
+ # Post the given agruments and block to the Global Thread Pool.
8
+ #
9
+ # @param args [Array] zero or more arguments for the block
10
+ # @param block [Proc] operation to be performed concurrently
11
+ #
12
+ # @return [true,false] success/failre of thread creation
13
+ #
14
+ # @note Althought based on Go's goroutines and Erlang's spawn/1,
15
+ # Ruby has a vastly different runtime. Threads aren't nearly as
16
+ # efficient in Ruby. Use this function appropriately.
17
+ #
18
+ # @see http://golang.org/doc/effective_go.html#goroutines
19
+ # @see https://gobyexample.com/goroutines
20
+ def go(*args, &block)
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
27
+ end
28
+ module_function :go
29
+ end
@@ -1,22 +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).abort_on_exception = false
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
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).abort_on_exception = false
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
@@ -1,67 +1,67 @@
1
- require 'thread'
2
- require 'timeout'
3
-
4
- require 'functional/behavior'
5
-
6
- behavior_info(:future,
7
- state: 0,
8
- value: -1,
9
- reason: 0,
10
- pending?: 0,
11
- fulfilled?: 0,
12
- rejected?: 0)
13
-
14
- behavior_info(:promise,
15
- state: 0,
16
- value: -1,
17
- reason: 0,
18
- pending?: 0,
19
- fulfilled?: 0,
20
- rejected?: 0,
21
- then: 0,
22
- rescue: -1)
23
-
24
- module Concurrent
25
-
26
- module Obligation
27
-
28
- attr_reader :state
29
- attr_reader :reason
30
-
31
- # Has the obligation been fulfilled?
32
- # @return [Boolean]
33
- def fulfilled?() return(@state == :fulfilled); end
34
- alias_method :realized?, :fulfilled?
35
-
36
- # Has the promise been rejected?
37
- # @return [Boolean]
38
- def rejected?() return(@state == :rejected); end
39
-
40
- # Is obligation completion still pending?
41
- # @return [Boolean]
42
- def pending?() return(@state == :pending); end
43
-
44
- def value(timeout = nil)
45
- if timeout == 0 || ! pending?
46
- return @value
47
- elsif timeout.nil?
48
- return mutex.synchronize { v = @value }
49
- else
50
- begin
51
- return Timeout::timeout(timeout.to_f) {
52
- mutex.synchronize { v = @value }
53
- }
54
- rescue Timeout::Error => ex
55
- return nil
56
- end
57
- end
58
- end
59
- alias_method :deref, :value
60
-
61
- protected
62
-
63
- def mutex
64
- @mutex ||= Mutex.new
65
- end
66
- end
67
- end
1
+ require 'thread'
2
+ require 'timeout'
3
+
4
+ require 'functional/behavior'
5
+
6
+ behavior_info(:future,
7
+ state: 0,
8
+ value: -1,
9
+ reason: 0,
10
+ pending?: 0,
11
+ fulfilled?: 0,
12
+ rejected?: 0)
13
+
14
+ behavior_info(:promise,
15
+ state: 0,
16
+ value: -1,
17
+ reason: 0,
18
+ pending?: 0,
19
+ fulfilled?: 0,
20
+ rejected?: 0,
21
+ then: 0,
22
+ rescue: -1)
23
+
24
+ module Concurrent
25
+
26
+ module Obligation
27
+
28
+ attr_reader :state
29
+ attr_reader :reason
30
+
31
+ # Has the obligation been fulfilled?
32
+ # @return [Boolean]
33
+ def fulfilled?() return(@state == :fulfilled); end
34
+ alias_method :realized?, :fulfilled?
35
+
36
+ # Has the promise been rejected?
37
+ # @return [Boolean]
38
+ def rejected?() return(@state == :rejected); end
39
+
40
+ # Is obligation completion still pending?
41
+ # @return [Boolean]
42
+ def pending?() return(@state == :pending); end
43
+
44
+ def value(timeout = nil)
45
+ if timeout == 0 || ! pending?
46
+ return @value
47
+ elsif timeout.nil?
48
+ return mutex.synchronize { v = @value }
49
+ else
50
+ begin
51
+ return Timeout::timeout(timeout.to_f) {
52
+ mutex.synchronize { v = @value }
53
+ }
54
+ rescue Timeout::Error => ex
55
+ return nil
56
+ end
57
+ end
58
+ end
59
+ alias_method :deref, :value
60
+
61
+ protected
62
+
63
+ def mutex
64
+ @mutex ||= Mutex.new
65
+ end
66
+ end
67
+ end
@@ -1,174 +1,174 @@
1
- require 'thread'
2
-
3
- require 'concurrent/global_thread_pool'
4
- require 'concurrent/obligation'
5
- require 'concurrent/utilities'
6
-
7
- module Concurrent
8
-
9
- class Promise
10
- include Obligation
11
- include UsesGlobalThreadPool
12
-
13
- behavior(:future)
14
- behavior(:promise)
15
-
16
- # Creates a new promise object. "A promise represents the eventual
17
- # value returned from the single completion of an operation."
18
- # Promises can be chained in a tree structure where each promise
19
- # has zero or more children. Promises are resolved asynchronously
20
- # in the order they are added to the tree. Parents are guaranteed
21
- # to be resolved before their children. The result of each promise
22
- # is passed to each of its children upon resolution. When
23
- # a promise is rejected all its children will be summarily rejected.
24
- # A promise that is neither resolved or rejected is pending.
25
- #
26
- # @param args [Array] zero or more arguments for the block
27
- # @param block [Proc] the block to call when attempting fulfillment
28
- #
29
- # @see http://wiki.commonjs.org/wiki/Promises/A
30
- # @see http://promises-aplus.github.io/promises-spec/
31
- def initialize(*args, &block)
32
- if args.first.is_a?(Promise)
33
- @parent = args.first
34
- else
35
- @parent = nil
36
- @chain = [self]
37
- end
38
-
39
- @lock = Mutex.new
40
- @handler = block || Proc.new{|result| result }
41
- @state = :pending
42
- @value = nil
43
- @reason = nil
44
- @rescued = false
45
- @children = []
46
- @rescuers = []
47
-
48
- realize(*args) if root?
49
- end
50
-
51
- def rescued?
52
- return @rescued
53
- end
54
-
55
- # Create a new child Promise. The block argument for the child will
56
- # be the result of fulfilling its parent. If the child will
57
- # immediately be rejected if the parent has already been rejected.
58
- #
59
- # @param block [Proc] the block to call when attempting fulfillment
60
- #
61
- # @return [Promise] the new promise
62
- def then(&block)
63
- child = @lock.synchronize do
64
- block = Proc.new{|result| result } unless block_given?
65
- @children << Promise.new(self, &block)
66
- @children.last.on_reject(@reason) if rejected?
67
- push(@children.last)
68
- @children.last
69
- end
70
- return child
71
- end
72
-
73
- # Add a rescue handler to be run if the promise is rejected (via raised
74
- # exception). Multiple rescue handlers may be added to a Promise.
75
- # Rescue blocks will be checked in order and the first one with a
76
- # matching Exception class will be processed. The block argument
77
- # will be the exception that caused the rejection.
78
- #
79
- # @param clazz [Class] The class of exception to rescue
80
- # @param block [Proc] the block to call if the rescue is matched
81
- #
82
- # @return [self] so that additional chaining can occur
83
- 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
92
- end
93
- return self
94
- end
95
- alias_method :catch, :rescue
96
- alias_method :on_error, :rescue
97
-
98
- protected
99
-
100
- attr_reader :parent
101
- attr_reader :handler
102
- attr_reader :rescuers
103
-
104
- # @private
105
- Rescuer = Struct.new(:clazz, :block)
106
-
107
- # @private
108
- def root? # :nodoc:
109
- @parent.nil?
110
- end
111
-
112
- # @private
113
- def push(promise) # :nodoc:
114
- if root?
115
- @chain << promise
116
- else
117
- @parent.push(promise)
118
- end
119
- end
120
-
121
- # @private
122
- def on_fulfill(value) # :nodoc:
123
- @lock.synchronize do
124
- @value = @handler.call(value)
125
- @state = :fulfilled
126
- @reason = nil
127
- end
128
- return @value
129
- end
130
-
131
- # @private
132
- 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) }
138
- end
139
-
140
- # @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
148
- rescue Exception => e
149
- # supress
150
- end
151
-
152
- # @private
153
- def realize(*args) # :nodoc:
154
- Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
155
- result = args.length == 1 ? args.first : args
156
- index = 0
157
- loop do
158
- current = lock.synchronize{ chain[index] }
159
- unless current.rejected?
160
- current.mutex.synchronize do
161
- begin
162
- result = current.on_fulfill(result)
163
- rescue Exception => ex
164
- current.on_reject(ex)
165
- end
166
- end
167
- end
168
- index += 1
169
- Thread.pass while index >= chain.length
170
- end
171
- end
172
- end
173
- end
174
- end
1
+ require 'thread'
2
+
3
+ require 'concurrent/global_thread_pool'
4
+ require 'concurrent/obligation'
5
+ require 'concurrent/utilities'
6
+
7
+ module Concurrent
8
+
9
+ class Promise
10
+ include Obligation
11
+ include UsesGlobalThreadPool
12
+
13
+ behavior(:future)
14
+ behavior(:promise)
15
+
16
+ # Creates a new promise object. "A promise represents the eventual
17
+ # value returned from the single completion of an operation."
18
+ # Promises can be chained in a tree structure where each promise
19
+ # has zero or more children. Promises are resolved asynchronously
20
+ # in the order they are added to the tree. Parents are guaranteed
21
+ # to be resolved before their children. The result of each promise
22
+ # is passed to each of its children upon resolution. When
23
+ # a promise is rejected all its children will be summarily rejected.
24
+ # A promise that is neither resolved or rejected is pending.
25
+ #
26
+ # @param args [Array] zero or more arguments for the block
27
+ # @param block [Proc] the block to call when attempting fulfillment
28
+ #
29
+ # @see http://wiki.commonjs.org/wiki/Promises/A
30
+ # @see http://promises-aplus.github.io/promises-spec/
31
+ def initialize(*args, &block)
32
+ if args.first.is_a?(Promise)
33
+ @parent = args.first
34
+ else
35
+ @parent = nil
36
+ @chain = [self]
37
+ end
38
+
39
+ @lock = Mutex.new
40
+ @handler = block || Proc.new{|result| result }
41
+ @state = :pending
42
+ @value = nil
43
+ @reason = nil
44
+ @rescued = false
45
+ @children = []
46
+ @rescuers = []
47
+
48
+ realize(*args) if root?
49
+ end
50
+
51
+ def rescued?
52
+ return @rescued
53
+ end
54
+
55
+ # Create a new child Promise. The block argument for the child will
56
+ # be the result of fulfilling its parent. If the child will
57
+ # immediately be rejected if the parent has already been rejected.
58
+ #
59
+ # @param block [Proc] the block to call when attempting fulfillment
60
+ #
61
+ # @return [Promise] the new promise
62
+ def then(&block)
63
+ child = @lock.synchronize do
64
+ block = Proc.new{|result| result } unless block_given?
65
+ @children << Promise.new(self, &block)
66
+ @children.last.on_reject(@reason) if rejected?
67
+ push(@children.last)
68
+ @children.last
69
+ end
70
+ return child
71
+ end
72
+
73
+ # Add a rescue handler to be run if the promise is rejected (via raised
74
+ # exception). Multiple rescue handlers may be added to a Promise.
75
+ # Rescue blocks will be checked in order and the first one with a
76
+ # matching Exception class will be processed. The block argument
77
+ # will be the exception that caused the rejection.
78
+ #
79
+ # @param clazz [Class] The class of exception to rescue
80
+ # @param block [Proc] the block to call if the rescue is matched
81
+ #
82
+ # @return [self] so that additional chaining can occur
83
+ 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
92
+ end
93
+ return self
94
+ end
95
+ alias_method :catch, :rescue
96
+ alias_method :on_error, :rescue
97
+
98
+ protected
99
+
100
+ attr_reader :parent
101
+ attr_reader :handler
102
+ attr_reader :rescuers
103
+
104
+ # @private
105
+ Rescuer = Struct.new(:clazz, :block)
106
+
107
+ # @private
108
+ def root? # :nodoc:
109
+ @parent.nil?
110
+ end
111
+
112
+ # @private
113
+ def push(promise) # :nodoc:
114
+ if root?
115
+ @chain << promise
116
+ else
117
+ @parent.push(promise)
118
+ end
119
+ end
120
+
121
+ # @private
122
+ def on_fulfill(value) # :nodoc:
123
+ @lock.synchronize do
124
+ @value = @handler.call(value)
125
+ @state = :fulfilled
126
+ @reason = nil
127
+ end
128
+ return @value
129
+ end
130
+
131
+ # @private
132
+ 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) }
138
+ end
139
+
140
+ # @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
148
+ rescue Exception => e
149
+ # supress
150
+ end
151
+
152
+ # @private
153
+ def realize(*args) # :nodoc:
154
+ Promise.thread_pool.post(@chain, @lock, args) do |chain, lock, args|
155
+ result = args.length == 1 ? args.first : args
156
+ index = 0
157
+ loop do
158
+ current = lock.synchronize{ chain[index] }
159
+ unless current.rejected?
160
+ current.mutex.synchronize do
161
+ begin
162
+ result = current.on_fulfill(result)
163
+ rescue Exception => ex
164
+ current.on_reject(ex)
165
+ end
166
+ end
167
+ end
168
+ index += 1
169
+ Thread.pass while index >= chain.length
170
+ end
171
+ end
172
+ end
173
+ end
174
+ end