concurrent-ruby 0.2.0 → 0.2.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 (57) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +275 -275
  3. data/lib/concurrent.rb +28 -28
  4. data/lib/concurrent/agent.rb +114 -114
  5. data/lib/concurrent/cached_thread_pool.rb +131 -129
  6. data/lib/concurrent/defer.rb +65 -65
  7. data/lib/concurrent/event.rb +60 -60
  8. data/lib/concurrent/event_machine_defer_proxy.rb +23 -23
  9. data/lib/concurrent/executor.rb +96 -95
  10. data/lib/concurrent/fixed_thread_pool.rb +99 -95
  11. data/lib/concurrent/functions.rb +120 -120
  12. data/lib/concurrent/future.rb +42 -42
  13. data/lib/concurrent/global_thread_pool.rb +16 -16
  14. data/lib/concurrent/goroutine.rb +29 -29
  15. data/lib/concurrent/null_thread_pool.rb +22 -22
  16. data/lib/concurrent/obligation.rb +67 -67
  17. data/lib/concurrent/promise.rb +174 -174
  18. data/lib/concurrent/reactor.rb +166 -166
  19. data/lib/concurrent/reactor/drb_async_demux.rb +83 -83
  20. data/lib/concurrent/reactor/tcp_sync_demux.rb +131 -131
  21. data/lib/concurrent/supervisor.rb +105 -100
  22. data/lib/concurrent/thread_pool.rb +76 -76
  23. data/lib/concurrent/utilities.rb +32 -32
  24. data/lib/concurrent/version.rb +3 -3
  25. data/lib/concurrent_ruby.rb +1 -1
  26. data/md/agent.md +123 -123
  27. data/md/defer.md +174 -174
  28. data/md/event.md +32 -32
  29. data/md/executor.md +187 -187
  30. data/md/future.md +83 -83
  31. data/md/goroutine.md +52 -52
  32. data/md/obligation.md +32 -32
  33. data/md/promise.md +227 -227
  34. data/md/thread_pool.md +224 -224
  35. data/spec/concurrent/agent_spec.rb +386 -386
  36. data/spec/concurrent/cached_thread_pool_spec.rb +125 -125
  37. data/spec/concurrent/defer_spec.rb +195 -195
  38. data/spec/concurrent/event_machine_defer_proxy_spec.rb +256 -256
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +200 -200
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -83
  42. data/spec/concurrent/functions_spec.rb +217 -217
  43. data/spec/concurrent/future_spec.rb +108 -108
  44. data/spec/concurrent/global_thread_pool_spec.rb +38 -38
  45. data/spec/concurrent/goroutine_spec.rb +67 -67
  46. data/spec/concurrent/null_thread_pool_spec.rb +57 -54
  47. data/spec/concurrent/obligation_shared.rb +132 -132
  48. data/spec/concurrent/promise_spec.rb +312 -312
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +196 -196
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +410 -410
  51. data/spec/concurrent/reactor_spec.rb +364 -364
  52. data/spec/concurrent/supervisor_spec.rb +269 -258
  53. data/spec/concurrent/thread_pool_shared.rb +204 -204
  54. data/spec/concurrent/utilities_spec.rb +74 -74
  55. data/spec/spec_helper.rb +32 -32
  56. metadata +20 -16
  57. checksums.yaml +0 -7
@@ -1,16 +1,16 @@
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
+ attr_accessor :thread_pool
12
+ end
13
+ base.thread_pool = $GLOBAL_THREAD_POOL
14
+ end
15
+ end
16
+ 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)
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