concurrent-ruby 0.1.0 → 0.1.1.pre.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 (51) hide show
  1. data/LICENSE +21 -21
  2. data/README.md +279 -224
  3. data/lib/concurrent.rb +27 -20
  4. data/lib/concurrent/agent.rb +106 -130
  5. data/lib/concurrent/cached_thread_pool.rb +130 -122
  6. data/lib/concurrent/defer.rb +67 -69
  7. data/lib/concurrent/drb_async_demux.rb +72 -0
  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 +87 -0
  11. data/lib/concurrent/fixed_thread_pool.rb +89 -89
  12. data/lib/concurrent/functions.rb +120 -0
  13. data/lib/concurrent/future.rb +52 -42
  14. data/lib/concurrent/global_thread_pool.rb +3 -3
  15. data/lib/concurrent/goroutine.rb +29 -25
  16. data/lib/concurrent/obligation.rb +67 -121
  17. data/lib/concurrent/promise.rb +172 -194
  18. data/lib/concurrent/reactor.rb +162 -0
  19. data/lib/concurrent/smart_mutex.rb +66 -0
  20. data/lib/concurrent/tcp_sync_demux.rb +96 -0
  21. data/lib/concurrent/thread_pool.rb +65 -61
  22. data/lib/concurrent/utilities.rb +34 -0
  23. data/lib/concurrent/version.rb +3 -3
  24. data/lib/concurrent_ruby.rb +1 -1
  25. data/md/agent.md +123 -123
  26. data/md/defer.md +174 -174
  27. data/md/event.md +32 -32
  28. data/md/executor.md +176 -0
  29. data/md/future.md +83 -83
  30. data/md/goroutine.md +52 -52
  31. data/md/obligation.md +32 -32
  32. data/md/promise.md +225 -225
  33. data/md/thread_pool.md +197 -197
  34. data/spec/concurrent/agent_spec.rb +376 -405
  35. data/spec/concurrent/cached_thread_pool_spec.rb +112 -112
  36. data/spec/concurrent/defer_spec.rb +209 -199
  37. data/spec/concurrent/event_machine_defer_proxy_spec.rb +250 -246
  38. data/spec/concurrent/event_spec.rb +134 -134
  39. data/spec/concurrent/executor_spec.rb +146 -0
  40. data/spec/concurrent/fixed_thread_pool_spec.rb +84 -84
  41. data/spec/concurrent/functions_spec.rb +57 -0
  42. data/spec/concurrent/future_spec.rb +125 -115
  43. data/spec/concurrent/goroutine_spec.rb +67 -52
  44. data/spec/concurrent/obligation_shared.rb +121 -121
  45. data/spec/concurrent/promise_spec.rb +299 -310
  46. data/spec/concurrent/smart_mutex_spec.rb +234 -0
  47. data/spec/concurrent/thread_pool_shared.rb +209 -209
  48. data/spec/concurrent/utilities_spec.rb +74 -0
  49. data/spec/spec_helper.rb +21 -19
  50. metadata +38 -14
  51. checksums.yaml +0 -7
@@ -1,121 +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
- # Is obligation completion still pending?
37
- # @return [Boolean]
38
- def pending?() return(!(fulfilled? || rejected?)); end
39
-
40
- def value(timeout = nil)
41
- if !pending? || timeout == 0
42
- return @value
43
- elsif timeout.nil?
44
- return semaphore.synchronize { @value }
45
- else
46
- begin
47
- return Timeout::timeout(timeout.to_f) {
48
- semaphore.synchronize { @value }
49
- }
50
- rescue Timeout::Error => ex
51
- return nil
52
- end
53
- end
54
- end
55
- alias_method :deref, :value
56
-
57
- # Has the promise been rejected?
58
- # @return [Boolean]
59
- def rejected?() return(@state == :rejected); end
60
-
61
- protected
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
118
- end
119
- end
120
- module_function :rejected?
121
- 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
+ # Is obligation completion still pending?
37
+ # @return [Boolean]
38
+ def pending?() return(!(fulfilled? || rejected?)); end
39
+
40
+ def value(timeout = nil)
41
+ if !pending? || timeout == 0
42
+ return @value
43
+ elsif timeout.nil?
44
+ return semaphore.synchronize { value = @value }
45
+ else
46
+ begin
47
+ return Timeout::timeout(timeout.to_f) {
48
+ semaphore.synchronize { value = @value }
49
+ }
50
+ rescue Timeout::Error => ex
51
+ return nil
52
+ end
53
+ end
54
+ end
55
+ alias_method :deref, :value
56
+
57
+ # Has the promise been rejected?
58
+ # @return [Boolean]
59
+ def rejected?() return(@state == :rejected); end
60
+
61
+ protected
62
+
63
+ def semaphore
64
+ @semaphore ||= Mutex.new
65
+ end
66
+ end
67
+ end
@@ -1,194 +1,172 @@
1
- require 'thread'
2
-
3
- require 'concurrent/obligation'
4
- require 'concurrent/global_thread_pool'
5
-
6
- module Concurrent
7
-
8
- class Promise
9
- include Obligation
10
- behavior(:future)
11
- behavior(:promise)
12
-
13
- # Creates a new promise object. "A promise represents the eventual
14
- # value returned from the single completion of an operation."
15
- # Promises can be chained in a tree structure where each promise
16
- # has zero or more children. Promises are resolved asynchronously
17
- # in the order they are added to the tree. Parents are guaranteed
18
- # to be resolved before their children. The result of each promise
19
- # is passed to each of its children upon resolution. When
20
- # a promise is rejected all its children will be summarily rejected.
21
- # A promise that is neither resolved or rejected is pending.
22
- #
23
- # @param args [Array] zero or more arguments for the block
24
- # @param block [Proc] the block to call when attempting fulfillment
25
- #
26
- # @see http://wiki.commonjs.org/wiki/Promises/A
27
- # @see http://promises-aplus.github.io/promises-spec/
28
- def initialize(*args, &block)
29
- if args.first.is_a?(Promise)
30
- @parent = args.first
31
- else
32
- @parent = nil
33
- @chain = [self]
34
- end
35
-
36
- @mutex = Mutex.new
37
- @handler = block || Proc.new{|result| result }
38
- @state = :pending
39
- @value = nil
40
- @reason = nil
41
- @children = []
42
- @rescuers = []
43
-
44
- realize(*args) if root?
45
- end
46
-
47
- # Create a new child Promise. The block argument for the child will
48
- # be the result of fulfilling its parent. If the child will
49
- # immediately be rejected if the parent has already been rejected.
50
- #
51
- # @param block [Proc] the block to call when attempting fulfillment
52
- #
53
- # @return [Promise] the new promise
54
- def then(&block)
55
- child = @mutex.synchronize do
56
- block = Proc.new{|result| result } unless block_given?
57
- @children << Promise.new(self, &block)
58
- @children.last.on_reject(@reason) if rejected?
59
- push(@children.last)
60
- @children.last
61
- end
62
- return child
63
- end
64
-
65
- # Add a rescue handler to be run if the promise is rejected (via raised
66
- # exception). Multiple rescue handlers may be added to a Promise.
67
- # Rescue blocks will be checked in order and the first one with a
68
- # matching Exception class will be processed. The block argument
69
- # will be the exception that caused the rejection.
70
- #
71
- # @param clazz [Class] The class of exception to rescue
72
- # @param block [Proc] the block to call if the rescue is matched
73
- #
74
- # @return [self] so that additional chaining can occur
75
- def rescue(clazz = Exception, &block)
76
- @mutex.synchronize do
77
- @rescuers << Rescuer.new(clazz, block) if block_given?
78
- end
79
- return self
80
- end
81
- alias_method :catch, :rescue
82
- alias_method :on_error, :rescue
83
-
84
- protected
85
-
86
- attr_reader :parent
87
- attr_reader :handler
88
- attr_reader :rescuers
89
-
90
- # @private
91
- Rescuer = Struct.new(:clazz, :block)
92
-
93
- # @private
94
- def root # :nodoc:
95
- current = self
96
- current = current.parent until current.root?
97
- return current
98
- end
99
-
100
- # @private
101
- def root? # :nodoc:
102
- @parent.nil?
103
- end
104
-
105
- # @private
106
- def push(promise) # :nodoc:
107
- if root?
108
- @chain << promise
109
- else
110
- @parent.push(promise)
111
- end
112
- end
113
-
114
- # @private
115
- def on_fulfill(value) # :nodoc:
116
- @mutex.synchronize do
117
- if pending?
118
- @value = @handler.call(value)
119
- @state = :fulfilled
120
- @reason = nil
121
- end
122
- end
123
- return @value
124
- end
125
-
126
- # @private
127
- def on_reject(reason) # :nodoc:
128
- @mutex.synchronize do
129
- if pending?
130
- @state = :rejected
131
- @reason = reason
132
- self.try_rescue(reason)
133
- @value = nil
134
- end
135
- @children.each{|child| child.on_reject(reason) }
136
- end
137
- end
138
-
139
- # @private
140
- def try_rescue(ex) # :nodoc:
141
- rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
142
- rescuer.block.call(ex) if rescuer
143
- rescue Exception => e
144
- # supress
145
- end
146
-
147
- # @private
148
- def realize(*args) # :nodoc:
149
- $GLOBAL_THREAD_POOL.post(@chain, @mutex, args) do |chain, mutex, args|
150
- result = args.length == 1 ? args.first : args
151
- index = 0
152
- loop do
153
- Thread.pass
154
- current = mutex.synchronize{ chain[index] }
155
- unless current.rejected?
156
- current.semaphore.synchronize do
157
- begin
158
- result = current.on_fulfill(result)
159
- rescue Exception => ex
160
- current.on_reject(ex)
161
- end
162
- end
163
- end
164
- index += 1
165
- sleep while index >= chain.length
166
- end
167
- end
168
- end
169
- end
170
- end
171
-
172
- module Kernel
173
-
174
- # Creates a new promise object. "A promise represents the eventual
175
- # value returned from the single completion of an operation."
176
- # Promises can be chained in a tree structure where each promise
177
- # has zero or more children. Promises are resolved asynchronously
178
- # in the order they are added to the tree. Parents are guaranteed
179
- # to be resolved before their children. The result of each promise
180
- # is passes to each of its children when the child resolves. When
181
- # a promise is rejected all its children will be summarily rejected.
182
- # A promise added to a rejected promise will immediately be rejected.
183
- # A promise that is neither resolved or rejected is pending.
184
- #
185
- # @param args [Array] zero or more arguments for the block
186
- # @param block [Proc] the block to call when attempting fulfillment
187
- #
188
- # @see Promise
189
- # @see http://wiki.commonjs.org/wiki/Promises/A
190
- def promise(*args, &block)
191
- return Concurrent::Promise.new(*args, &block)
192
- end
193
- module_function :promise
194
- end
1
+ require 'thread'
2
+
3
+ require 'concurrent/obligation'
4
+ require 'concurrent/utilities'
5
+
6
+ module Concurrent
7
+
8
+ class Promise
9
+ include Obligation
10
+ behavior(:future)
11
+ behavior(:promise)
12
+
13
+ # Creates a new promise object. "A promise represents the eventual
14
+ # value returned from the single completion of an operation."
15
+ # Promises can be chained in a tree structure where each promise
16
+ # has zero or more children. Promises are resolved asynchronously
17
+ # in the order they are added to the tree. Parents are guaranteed
18
+ # to be resolved before their children. The result of each promise
19
+ # is passed to each of its children upon resolution. When
20
+ # a promise is rejected all its children will be summarily rejected.
21
+ # A promise that is neither resolved or rejected is pending.
22
+ #
23
+ # @param args [Array] zero or more arguments for the block
24
+ # @param block [Proc] the block to call when attempting fulfillment
25
+ #
26
+ # @see http://wiki.commonjs.org/wiki/Promises/A
27
+ # @see http://promises-aplus.github.io/promises-spec/
28
+ def initialize(*args, &block)
29
+ if args.first.is_a?(Promise)
30
+ @parent = args.first
31
+ else
32
+ @parent = nil
33
+ @chain = [self]
34
+ end
35
+
36
+ @mutex = Mutex.new
37
+ @handler = block || Proc.new{|result| result }
38
+ @state = :pending
39
+ @value = nil
40
+ @reason = nil
41
+ @children = []
42
+ @rescuers = []
43
+
44
+ realize(*args) if root?
45
+ end
46
+
47
+ # Create a new child Promise. The block argument for the child will
48
+ # be the result of fulfilling its parent. If the child will
49
+ # immediately be rejected if the parent has already been rejected.
50
+ #
51
+ # @param block [Proc] the block to call when attempting fulfillment
52
+ #
53
+ # @return [Promise] the new promise
54
+ def then(&block)
55
+ child = @mutex.synchronize do
56
+ block = Proc.new{|result| result } unless block_given?
57
+ @children << Promise.new(self, &block)
58
+ @children.last.on_reject(@reason) if rejected?
59
+ push(@children.last)
60
+ @children.last
61
+ end
62
+ return child
63
+ end
64
+
65
+ # Add a rescue handler to be run if the promise is rejected (via raised
66
+ # exception). Multiple rescue handlers may be added to a Promise.
67
+ # Rescue blocks will be checked in order and the first one with a
68
+ # matching Exception class will be processed. The block argument
69
+ # will be the exception that caused the rejection.
70
+ #
71
+ # @param clazz [Class] The class of exception to rescue
72
+ # @param block [Proc] the block to call if the rescue is matched
73
+ #
74
+ # @return [self] so that additional chaining can occur
75
+ def rescue(clazz = Exception, &block)
76
+ @mutex.synchronize do
77
+ @rescuers << Rescuer.new(clazz, block) if block_given?
78
+ end
79
+ return self
80
+ end
81
+ alias_method :catch, :rescue
82
+ alias_method :on_error, :rescue
83
+
84
+ protected
85
+
86
+ attr_reader :parent
87
+ attr_reader :handler
88
+ attr_reader :rescuers
89
+
90
+ # @private
91
+ Rescuer = Struct.new(:clazz, :block)
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
+
102
+ # @private
103
+ def root? # :nodoc:
104
+ @parent.nil?
105
+ end
106
+
107
+ # @private
108
+ def push(promise) # :nodoc:
109
+ if root?
110
+ @chain << promise
111
+ else
112
+ @parent.push(promise)
113
+ end
114
+ end
115
+
116
+ # @private
117
+ 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
124
+ end
125
+ return @value
126
+ end
127
+
128
+ # @private
129
+ 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
139
+ end
140
+
141
+ # @private
142
+ def try_rescue(ex) # :nodoc:
143
+ rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
144
+ rescuer.block.call(ex) if rescuer
145
+ rescue Exception => e
146
+ # supress
147
+ end
148
+
149
+ # @private
150
+ def realize(*args) # :nodoc:
151
+ Thread.new(@chain, @mutex, args) do |chain, mutex, args|
152
+ result = args.length == 1 ? args.first : args
153
+ index = 0
154
+ loop do
155
+ Thread.pass
156
+ current = mutex.synchronize{ chain[index] }
157
+ unless current.rejected?
158
+ current.semaphore.synchronize do
159
+ begin
160
+ result = current.on_fulfill(result)
161
+ rescue Exception => ex
162
+ current.on_reject(ex)
163
+ end
164
+ end
165
+ end
166
+ index += 1
167
+ sleep while index >= chain.length
168
+ end
169
+ end
170
+ end
171
+ end
172
+ end