concurrent-ruby 0.1.1.pre.3 → 0.1.1.pre.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (57) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE +21 -21
  3. data/README.md +275 -279
  4. data/lib/concurrent.rb +27 -28
  5. data/lib/concurrent/agent.rb +114 -108
  6. data/lib/concurrent/cached_thread_pool.rb +129 -130
  7. data/lib/concurrent/defer.rb +65 -67
  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 +93 -95
  11. data/lib/concurrent/fixed_thread_pool.rb +95 -89
  12. data/lib/concurrent/functions.rb +120 -120
  13. data/lib/concurrent/future.rb +42 -47
  14. data/lib/concurrent/global_thread_pool.rb +16 -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 -166
  19. data/lib/concurrent/reactor.rb +161 -162
  20. data/lib/concurrent/reactor/drb_async_demux.rb +74 -74
  21. data/lib/concurrent/reactor/tcp_sync_demux.rb +98 -98
  22. data/lib/concurrent/thread_pool.rb +76 -69
  23. data/lib/concurrent/utilities.rb +32 -34
  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 +176 -176
  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 -380
  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 -253
  39. data/spec/concurrent/event_spec.rb +134 -134
  40. data/spec/concurrent/executor_spec.rb +184 -184
  41. data/spec/concurrent/fixed_thread_pool_spec.rb +83 -84
  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 +54 -54
  47. data/spec/concurrent/obligation_shared.rb +135 -121
  48. data/spec/concurrent/promise_spec.rb +312 -305
  49. data/spec/concurrent/reactor/drb_async_demux_spec.rb +12 -12
  50. data/spec/concurrent/reactor/tcp_sync_demux_spec.rb +12 -12
  51. data/spec/concurrent/reactor_spec.rb +351 -10
  52. data/spec/concurrent/thread_pool_shared.rb +209 -210
  53. data/spec/concurrent/utilities_spec.rb +74 -74
  54. data/spec/spec_helper.rb +44 -30
  55. metadata +11 -22
  56. data/lib/concurrent/smart_mutex.rb +0 -66
  57. data/spec/concurrent/smart_mutex_spec.rb +0 -234
@@ -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)
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
- # 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
+ 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,166 +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
- @mutex = Mutex.new
40
- @handler = block || Proc.new{|result| result }
41
- @state = :pending
42
- @value = nil
43
- @reason = nil
44
- @children = []
45
- @rescuers = []
46
-
47
- realize(*args) if root?
48
- end
49
-
50
- # Create a new child Promise. The block argument for the child will
51
- # be the result of fulfilling its parent. If the child will
52
- # immediately be rejected if the parent has already been rejected.
53
- #
54
- # @param block [Proc] the block to call when attempting fulfillment
55
- #
56
- # @return [Promise] the new promise
57
- def then(&block)
58
- child = @mutex.synchronize do
59
- block = Proc.new{|result| result } unless block_given?
60
- @children << Promise.new(self, &block)
61
- @children.last.on_reject(@reason) if rejected?
62
- push(@children.last)
63
- @children.last
64
- end
65
- return child
66
- end
67
-
68
- # Add a rescue handler to be run if the promise is rejected (via raised
69
- # exception). Multiple rescue handlers may be added to a Promise.
70
- # Rescue blocks will be checked in order and the first one with a
71
- # matching Exception class will be processed. The block argument
72
- # will be the exception that caused the rejection.
73
- #
74
- # @param clazz [Class] The class of exception to rescue
75
- # @param block [Proc] the block to call if the rescue is matched
76
- #
77
- # @return [self] so that additional chaining can occur
78
- def rescue(clazz = Exception, &block)
79
- @mutex.synchronize do
80
- @rescuers << Rescuer.new(clazz, block) if block_given?
81
- end
82
- return self
83
- end
84
- alias_method :catch, :rescue
85
- alias_method :on_error, :rescue
86
-
87
- protected
88
-
89
- attr_reader :parent
90
- attr_reader :handler
91
- attr_reader :rescuers
92
-
93
- # @private
94
- Rescuer = Struct.new(:clazz, :block)
95
-
96
- # @private
97
- def root? # :nodoc:
98
- @parent.nil?
99
- end
100
-
101
- # @private
102
- def push(promise) # :nodoc:
103
- if root?
104
- @chain << promise
105
- else
106
- @parent.push(promise)
107
- end
108
- end
109
-
110
- # @private
111
- def on_fulfill(value) # :nodoc:
112
- @mutex.synchronize do
113
- if pending?
114
- @value = @handler.call(value)
115
- @state = :fulfilled
116
- @reason = nil
117
- end
118
- end
119
- return @value
120
- end
121
-
122
- # @private
123
- def on_reject(reason) # :nodoc:
124
- @mutex.synchronize do
125
- if pending?
126
- @state = :rejected
127
- @reason = reason
128
- self.try_rescue(reason)
129
- @value = nil
130
- end
131
- @children.each{|child| child.on_reject(reason) }
132
- end
133
- end
134
-
135
- # @private
136
- def try_rescue(ex) # :nodoc:
137
- rescuer = @rescuers.find{|r| ex.is_a?(r.clazz) }
138
- rescuer.block.call(ex) if rescuer
139
- rescue Exception => e
140
- # supress
141
- end
142
-
143
- # @private
144
- def realize(*args) # :nodoc:
145
- Promise.thread_pool.post(@chain, @mutex, args) do |chain, mutex, args|
146
- result = args.length == 1 ? args.first : args
147
- index = 0
148
- loop do
149
- Thread.pass
150
- current = mutex.synchronize{ chain[index] }
151
- unless current.rejected?
152
- current.semaphore.synchronize do
153
- begin
154
- result = current.on_fulfill(result)
155
- rescue Exception => ex
156
- current.on_reject(ex)
157
- end
158
- end
159
- end
160
- index += 1
161
- sleep while index >= chain.length
162
- end
163
- end
164
- end
165
- end
166
- 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