concurrent-ruby 0.1.0 → 0.1.1.pre.1

Sign up to get free protection for your applications and to get access to all the features.
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