concurrent-ruby 0.6.0.pre.2 → 0.6.0
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.
- checksums.yaml +4 -4
- data/README.md +3 -8
- data/lib/concurrent.rb +2 -0
- data/lib/concurrent/actor/actor.rb +3 -3
- data/lib/concurrent/actor/postable.rb +1 -1
- data/lib/concurrent/actors.rb +0 -3
- data/lib/concurrent/actress.rb +75 -0
- data/lib/concurrent/actress/ad_hoc.rb +14 -0
- data/lib/concurrent/actress/context.rb +96 -0
- data/lib/concurrent/actress/core.rb +204 -0
- data/lib/concurrent/actress/core_delegations.rb +37 -0
- data/lib/concurrent/actress/doc.md +53 -0
- data/lib/concurrent/actress/envelope.rb +25 -0
- data/lib/concurrent/actress/errors.rb +14 -0
- data/lib/concurrent/actress/reference.rb +64 -0
- data/lib/concurrent/actress/type_check.rb +48 -0
- data/lib/concurrent/agent.rb +20 -11
- data/lib/concurrent/async.rb +54 -25
- data/lib/concurrent/atomic/atomic.rb +48 -0
- data/lib/concurrent/atomic/atomic_boolean.rb +13 -13
- data/lib/concurrent/atomic/atomic_fixnum.rb +9 -17
- data/lib/concurrent/atomic/copy_on_notify_observer_set.rb +7 -4
- data/lib/concurrent/atomic/copy_on_write_observer_set.rb +16 -14
- data/lib/concurrent/atomic/event.rb +11 -16
- data/lib/concurrent/atomics.rb +1 -0
- data/lib/concurrent/channel/channel.rb +4 -2
- data/lib/concurrent/collection/blocking_ring_buffer.rb +1 -1
- data/lib/concurrent/configuration.rb +59 -47
- data/lib/concurrent/delay.rb +28 -12
- data/lib/concurrent/dereferenceable.rb +6 -6
- data/lib/concurrent/errors.rb +30 -0
- data/lib/concurrent/executor/executor.rb +11 -4
- data/lib/concurrent/executor/immediate_executor.rb +1 -0
- data/lib/concurrent/executor/java_thread_pool_executor.rb +4 -0
- data/lib/concurrent/executor/one_by_one.rb +24 -12
- data/lib/concurrent/executor/per_thread_executor.rb +1 -0
- data/lib/concurrent/executor/ruby_single_thread_executor.rb +2 -1
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +7 -2
- data/lib/concurrent/executor/ruby_thread_pool_worker.rb +3 -0
- data/lib/concurrent/executor/timer_set.rb +1 -1
- data/lib/concurrent/future.rb +0 -2
- data/lib/concurrent/ivar.rb +31 -6
- data/lib/concurrent/logging.rb +17 -0
- data/lib/concurrent/mvar.rb +45 -0
- data/lib/concurrent/obligation.rb +61 -20
- data/lib/concurrent/observable.rb +7 -0
- data/lib/concurrent/promise.rb +1 -0
- data/lib/concurrent/runnable.rb +2 -2
- data/lib/concurrent/supervisor.rb +1 -2
- data/lib/concurrent/timer_task.rb +17 -13
- data/lib/concurrent/tvar.rb +113 -73
- data/lib/concurrent/utility/processor_count.rb +141 -116
- data/lib/concurrent/utility/timeout.rb +4 -5
- data/lib/concurrent/version.rb +1 -1
- data/spec/concurrent/actor/postable_shared.rb +1 -1
- data/spec/concurrent/actress_spec.rb +191 -0
- data/spec/concurrent/async_spec.rb +35 -3
- data/spec/concurrent/atomic/atomic_boolean_spec.rb +1 -1
- data/spec/concurrent/atomic/atomic_fixnum_spec.rb +1 -1
- data/spec/concurrent/atomic/atomic_spec.rb +133 -0
- data/spec/concurrent/atomic/count_down_latch_spec.rb +1 -1
- data/spec/concurrent/collection/priority_queue_spec.rb +1 -1
- data/spec/concurrent/configuration_spec.rb +5 -2
- data/spec/concurrent/delay_spec.rb +14 -0
- data/spec/concurrent/exchanger_spec.rb +4 -9
- data/spec/concurrent/executor/java_cached_thread_pool_spec.rb +1 -1
- data/spec/concurrent/executor/java_fixed_thread_pool_spec.rb +1 -1
- data/spec/concurrent/executor/java_single_thread_executor_spec.rb +1 -1
- data/spec/concurrent/executor/java_thread_pool_executor_spec.rb +1 -1
- data/spec/concurrent/executor/ruby_thread_pool_executor_spec.rb +4 -4
- data/spec/concurrent/ivar_spec.rb +2 -2
- data/spec/concurrent/obligation_spec.rb +113 -24
- data/spec/concurrent/observable_shared.rb +4 -0
- data/spec/concurrent/observable_spec.rb +8 -3
- data/spec/concurrent/runnable_spec.rb +2 -2
- data/spec/concurrent/scheduled_task_spec.rb +1 -0
- data/spec/concurrent/supervisor_spec.rb +26 -11
- data/spec/concurrent/timer_task_spec.rb +36 -35
- data/spec/concurrent/tvar_spec.rb +1 -1
- data/spec/concurrent/utility/timer_spec.rb +8 -8
- data/spec/spec_helper.rb +8 -18
- data/spec/support/example_group_extensions.rb +48 -0
- metadata +23 -16
- data/lib/concurrent/actor/actor_context.rb +0 -77
- data/lib/concurrent/actor/actor_ref.rb +0 -67
- data/lib/concurrent/actor/simple_actor_ref.rb +0 -94
- data/lib/concurrent_ruby_ext.bundle +0 -0
- data/spec/concurrent/actor/actor_context_spec.rb +0 -29
- data/spec/concurrent/actor/actor_ref_shared.rb +0 -263
- data/spec/concurrent/actor/simple_actor_ref_spec.rb +0 -135
- data/spec/support/functions.rb +0 -25
@@ -42,23 +42,59 @@ module Concurrent
|
|
42
42
|
[:unscheduled, :pending].include? state
|
43
43
|
end
|
44
44
|
|
45
|
+
# @return [Object] see Dereferenceable#deref
|
45
46
|
def value(timeout = nil)
|
47
|
+
wait timeout
|
48
|
+
deref
|
49
|
+
end
|
50
|
+
|
51
|
+
# wait until Obligation is #complete?
|
52
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
53
|
+
# @return [Obligation] self
|
54
|
+
def wait(timeout = nil)
|
46
55
|
event.wait(timeout) if timeout != 0 && incomplete?
|
47
|
-
|
56
|
+
self
|
57
|
+
end
|
58
|
+
|
59
|
+
# wait until Obligation is #complete?
|
60
|
+
# @param [Numeric] timeout the maximum time in second to wait.
|
61
|
+
# @return [Obligation] self
|
62
|
+
# @raise [Exception] when #rejected? it raises #reason
|
63
|
+
def no_error!(timeout = nil)
|
64
|
+
wait(timeout).tap { raise self if rejected? }
|
65
|
+
end
|
66
|
+
|
67
|
+
# @raise [Exception] when #rejected? it raises #reason
|
68
|
+
# @return [Object] see Dereferenceable#deref
|
69
|
+
def value!(timeout = nil)
|
70
|
+
wait(timeout)
|
71
|
+
if rejected?
|
72
|
+
raise self
|
73
|
+
else
|
74
|
+
deref
|
75
|
+
end
|
48
76
|
end
|
49
77
|
|
50
78
|
def state
|
51
79
|
mutex.lock
|
52
|
-
|
80
|
+
@state
|
81
|
+
ensure
|
53
82
|
mutex.unlock
|
54
|
-
result
|
55
83
|
end
|
56
84
|
|
57
85
|
def reason
|
58
86
|
mutex.lock
|
59
|
-
|
87
|
+
@reason
|
88
|
+
ensure
|
60
89
|
mutex.unlock
|
61
|
-
|
90
|
+
end
|
91
|
+
|
92
|
+
# @example allows Obligation to be risen
|
93
|
+
# rejected_ivar = Ivar.new.fail
|
94
|
+
# raise rejected_ivar
|
95
|
+
def exception(*args)
|
96
|
+
raise 'obligation is not rejected' unless rejected?
|
97
|
+
reason.exception(*args)
|
62
98
|
end
|
63
99
|
|
64
100
|
protected
|
@@ -81,13 +117,16 @@ module Concurrent
|
|
81
117
|
@state = :fulfilled
|
82
118
|
else
|
83
119
|
@reason = reason
|
84
|
-
@state
|
120
|
+
@state = :rejected
|
85
121
|
end
|
86
122
|
end
|
87
123
|
|
88
124
|
# @!visibility private
|
89
125
|
def state=(value) # :nodoc:
|
90
|
-
mutex.
|
126
|
+
mutex.lock
|
127
|
+
@state = value
|
128
|
+
ensure
|
129
|
+
mutex.unlock
|
91
130
|
end
|
92
131
|
|
93
132
|
# atomic compare and set operation
|
@@ -100,14 +139,15 @@ module Concurrent
|
|
100
139
|
#
|
101
140
|
# @!visibility private
|
102
141
|
def compare_and_set_state(next_state, expected_current) # :nodoc:
|
103
|
-
mutex.
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
end
|
142
|
+
mutex.lock
|
143
|
+
if @state == expected_current
|
144
|
+
@state = next_state
|
145
|
+
true
|
146
|
+
else
|
147
|
+
false
|
110
148
|
end
|
149
|
+
ensure
|
150
|
+
mutex.unlock
|
111
151
|
end
|
112
152
|
|
113
153
|
# executes the block within mutex if current state is included in expected_states
|
@@ -116,15 +156,16 @@ module Concurrent
|
|
116
156
|
#
|
117
157
|
# @!visibility private
|
118
158
|
def if_state(*expected_states) # :nodoc:
|
159
|
+
mutex.lock
|
119
160
|
raise ArgumentError.new('no block given') unless block_given?
|
120
161
|
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
false
|
126
|
-
end
|
162
|
+
if expected_states.include? @state
|
163
|
+
yield
|
164
|
+
else
|
165
|
+
false
|
127
166
|
end
|
167
|
+
ensure
|
168
|
+
mutex.unlock
|
128
169
|
end
|
129
170
|
end
|
130
171
|
end
|
@@ -10,6 +10,13 @@ module Concurrent
|
|
10
10
|
observers.add_observer(*args, &block)
|
11
11
|
end
|
12
12
|
|
13
|
+
# as #add_observer but it can be used for chaning
|
14
|
+
# @return [Observable] self
|
15
|
+
def with_observer(*args, &block)
|
16
|
+
add_observer *args, &block
|
17
|
+
self
|
18
|
+
end
|
19
|
+
|
13
20
|
# @return [Object] the deleted observer
|
14
21
|
def delete_observer(*args)
|
15
22
|
observers.delete_observer(*args)
|
data/lib/concurrent/promise.rb
CHANGED
data/lib/concurrent/runnable.rb
CHANGED
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'thread'
|
2
2
|
|
3
|
+
require 'concurrent/errors'
|
3
4
|
require 'concurrent/runnable'
|
4
5
|
|
5
6
|
module Concurrent
|
@@ -15,8 +16,6 @@ module Concurrent
|
|
15
16
|
CHILD_TYPES = [:worker, :supervisor]
|
16
17
|
CHILD_RESTART_OPTIONS = [:permanent, :transient, :temporary]
|
17
18
|
|
18
|
-
MaxRestartFrequencyError = Class.new(StandardError)
|
19
|
-
|
20
19
|
WorkerContext = Struct.new(:worker, :type, :restart) do
|
21
20
|
attr_accessor :thread
|
22
21
|
attr_accessor :terminated
|
@@ -146,7 +146,7 @@ module Concurrent
|
|
146
146
|
# @see http://docs.oracle.com/javase/7/docs/api/java/util/TimerTask.html
|
147
147
|
class TimerTask
|
148
148
|
include Dereferenceable
|
149
|
-
include
|
149
|
+
include RubyExecutor
|
150
150
|
include Concurrent::Observable
|
151
151
|
|
152
152
|
# Default `:execution_interval` in seconds.
|
@@ -248,9 +248,9 @@ module Concurrent
|
|
248
248
|
# task is performed again.
|
249
249
|
def execution_interval
|
250
250
|
mutex.lock
|
251
|
-
|
251
|
+
@execution_interval
|
252
|
+
ensure
|
252
253
|
mutex.unlock
|
253
|
-
result
|
254
254
|
end
|
255
255
|
|
256
256
|
# @!attribute [rw] execution_interval
|
@@ -260,10 +260,12 @@ module Concurrent
|
|
260
260
|
if (value = value.to_f) <= 0.0
|
261
261
|
raise ArgumentError.new('must be greater than zero')
|
262
262
|
else
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
263
|
+
begin
|
264
|
+
mutex.lock
|
265
|
+
@execution_interval = value
|
266
|
+
ensure
|
267
|
+
mutex.unlock
|
268
|
+
end
|
267
269
|
end
|
268
270
|
end
|
269
271
|
|
@@ -272,9 +274,9 @@ module Concurrent
|
|
272
274
|
# considered to have failed.
|
273
275
|
def timeout_interval
|
274
276
|
mutex.lock
|
275
|
-
|
277
|
+
@timeout_interval
|
278
|
+
ensure
|
276
279
|
mutex.unlock
|
277
|
-
result
|
278
280
|
end
|
279
281
|
|
280
282
|
# @!attribute [rw] timeout_interval
|
@@ -284,10 +286,12 @@ module Concurrent
|
|
284
286
|
if (value = value.to_f) <= 0.0
|
285
287
|
raise ArgumentError.new('must be greater than zero')
|
286
288
|
else
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
289
|
+
begin
|
290
|
+
mutex.lock
|
291
|
+
@timeout_interval = value
|
292
|
+
ensure
|
293
|
+
mutex.unlock
|
294
|
+
end
|
291
295
|
end
|
292
296
|
end
|
293
297
|
|
data/lib/concurrent/tvar.rb
CHANGED
@@ -4,57 +4,158 @@ require 'concurrent/atomic/thread_local_var'
|
|
4
4
|
|
5
5
|
module Concurrent
|
6
6
|
|
7
|
-
|
8
|
-
|
9
|
-
CURRENT_TRANSACTION = ThreadLocalVar.new(nil)
|
10
|
-
|
11
|
-
ReadLogEntry = Struct.new(:tvar, :version)
|
12
|
-
UndoLogEntry = Struct.new(:tvar, :value)
|
13
|
-
|
7
|
+
# A `TVar` is a transactional variable - a single-element container that
|
8
|
+
# is used as part of a transaction - see `Concurrent::atomically`.
|
14
9
|
class TVar
|
15
10
|
|
11
|
+
# Create a new `TVar` with an initial value.
|
16
12
|
def initialize(value)
|
17
13
|
@value = value
|
18
14
|
@version = 0
|
19
15
|
@lock = Mutex.new
|
20
16
|
end
|
21
17
|
|
18
|
+
# Get the value of a `TVar`.
|
22
19
|
def value
|
23
20
|
Concurrent::atomically do
|
24
21
|
Transaction::current.read(self)
|
25
22
|
end
|
26
23
|
end
|
27
24
|
|
25
|
+
# Set the value of a `TVar`.
|
28
26
|
def value=(value)
|
29
27
|
Concurrent::atomically do
|
30
28
|
Transaction::current.write(self, value)
|
31
29
|
end
|
32
30
|
end
|
33
31
|
|
34
|
-
|
32
|
+
# @!visibility private
|
33
|
+
def unsafe_value # :nodoc:
|
35
34
|
@value
|
36
35
|
end
|
37
36
|
|
38
|
-
|
37
|
+
# @!visibility private
|
38
|
+
def unsafe_value=(value) # :nodoc:
|
39
39
|
@value = value
|
40
40
|
end
|
41
41
|
|
42
|
-
|
42
|
+
# @!visibility private
|
43
|
+
def unsafe_version # :nodoc:
|
43
44
|
@version
|
44
45
|
end
|
45
46
|
|
46
|
-
|
47
|
+
# @!visibility private
|
48
|
+
def unsafe_increment_version # :nodoc:
|
47
49
|
@version += 1
|
48
50
|
end
|
49
51
|
|
50
|
-
|
52
|
+
# @!visibility private
|
53
|
+
def unsafe_lock # :nodoc:
|
51
54
|
@lock
|
52
55
|
end
|
53
56
|
|
54
57
|
end
|
55
58
|
|
59
|
+
# Run a block that reads and writes `TVar`s as a single atomic transaction.
|
60
|
+
# With respect to the value of `TVar` objects, the transaction is atomic,
|
61
|
+
# in that it either happens or it does not, consistent, in that the `TVar`
|
62
|
+
# objects involved will never enter an illegal state, and isolated, in that
|
63
|
+
# transactions never interfere with each other. You may recognise these
|
64
|
+
# properties from database transactions.
|
65
|
+
#
|
66
|
+
# There are some very important and unusual semantics that you must be aware of:
|
67
|
+
#
|
68
|
+
# * Most importantly, the block that you pass to atomically may be executed more than once. In most cases your code should be free of side-effects, except for via TVar.
|
69
|
+
#
|
70
|
+
# * If an exception escapes an atomically block it will abort the transaction.
|
71
|
+
#
|
72
|
+
# * It is undefined behaviour to use callcc or Fiber with atomically.
|
73
|
+
#
|
74
|
+
# * If you create a new thread within an atomically, it will not be part of the transaction. Creating a thread counts as a side-effect.
|
75
|
+
#
|
76
|
+
# Transactions within transactions are flattened to a single transaction.
|
77
|
+
#
|
78
|
+
# @example
|
79
|
+
# a = new TVar(100_000)
|
80
|
+
# b = new TVar(100)
|
81
|
+
#
|
82
|
+
# Concurrent::atomically do
|
83
|
+
# a.value -= 10
|
84
|
+
# b.value += 10
|
85
|
+
# end
|
86
|
+
def atomically
|
87
|
+
raise ArgumentError.new('no block given') unless block_given?
|
88
|
+
|
89
|
+
# Get the current transaction
|
90
|
+
|
91
|
+
transaction = Transaction::current
|
92
|
+
|
93
|
+
# Are we not already in a transaction (not nested)?
|
94
|
+
|
95
|
+
if transaction.nil?
|
96
|
+
# New transaction
|
97
|
+
|
98
|
+
begin
|
99
|
+
# Retry loop
|
100
|
+
|
101
|
+
loop do
|
102
|
+
|
103
|
+
# Create a new transaction
|
104
|
+
|
105
|
+
transaction = Transaction.new
|
106
|
+
Transaction::current = transaction
|
107
|
+
|
108
|
+
# Run the block, aborting on exceptions
|
109
|
+
|
110
|
+
begin
|
111
|
+
result = yield
|
112
|
+
rescue Transaction::AbortError => e
|
113
|
+
transaction.abort
|
114
|
+
result = Transaction::ABORTED
|
115
|
+
rescue => e
|
116
|
+
transaction.abort
|
117
|
+
throw e
|
118
|
+
end
|
119
|
+
# If we can commit, break out of the loop
|
120
|
+
|
121
|
+
if result != Transaction::ABORTED
|
122
|
+
if transaction.commit
|
123
|
+
break result
|
124
|
+
end
|
125
|
+
end
|
126
|
+
end
|
127
|
+
ensure
|
128
|
+
# Clear the current transaction
|
129
|
+
|
130
|
+
Transaction::current = nil
|
131
|
+
end
|
132
|
+
else
|
133
|
+
# Nested transaction - flatten it and just run the block
|
134
|
+
|
135
|
+
yield
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
# Abort a currently running transaction - see `Concurrent::atomically`.
|
140
|
+
def abort_transaction
|
141
|
+
raise Transaction::AbortError.new
|
142
|
+
end
|
143
|
+
|
144
|
+
module_function :atomically, :abort_transaction
|
145
|
+
|
146
|
+
private
|
147
|
+
|
56
148
|
class Transaction
|
57
149
|
|
150
|
+
ABORTED = Object.new
|
151
|
+
|
152
|
+
CURRENT_TRANSACTION = ThreadLocalVar.new(nil)
|
153
|
+
|
154
|
+
ReadLogEntry = Struct.new(:tvar, :version)
|
155
|
+
UndoLogEntry = Struct.new(:tvar, :value)
|
156
|
+
|
157
|
+
AbortError = Class.new(StandardError)
|
158
|
+
|
58
159
|
def initialize
|
59
160
|
@write_set = Set.new
|
60
161
|
@read_log = []
|
@@ -148,65 +249,4 @@ module Concurrent
|
|
148
249
|
|
149
250
|
end
|
150
251
|
|
151
|
-
AbortError = Class.new(StandardError)
|
152
|
-
|
153
|
-
def atomically
|
154
|
-
raise ArgumentError.new('no block given') unless block_given?
|
155
|
-
|
156
|
-
# Get the current transaction
|
157
|
-
|
158
|
-
transaction = Transaction::current
|
159
|
-
|
160
|
-
# Are we not already in a transaction (not nested)?
|
161
|
-
|
162
|
-
if transaction.nil?
|
163
|
-
# New transaction
|
164
|
-
|
165
|
-
begin
|
166
|
-
# Retry loop
|
167
|
-
|
168
|
-
loop do
|
169
|
-
|
170
|
-
# Create a new transaction
|
171
|
-
|
172
|
-
transaction = Transaction.new
|
173
|
-
Transaction::current = transaction
|
174
|
-
|
175
|
-
# Run the block, aborting on exceptions
|
176
|
-
|
177
|
-
begin
|
178
|
-
result = yield
|
179
|
-
rescue AbortError => e
|
180
|
-
transaction.abort
|
181
|
-
result = ABORTED
|
182
|
-
rescue => e
|
183
|
-
transaction.abort
|
184
|
-
throw e
|
185
|
-
end
|
186
|
-
# If we can commit, break out of the loop
|
187
|
-
|
188
|
-
if result != ABORTED
|
189
|
-
if transaction.commit
|
190
|
-
break result
|
191
|
-
end
|
192
|
-
end
|
193
|
-
end
|
194
|
-
ensure
|
195
|
-
# Clear the current transaction
|
196
|
-
|
197
|
-
Transaction::current = nil
|
198
|
-
end
|
199
|
-
else
|
200
|
-
# Nested transaction - flatten it and just run the block
|
201
|
-
|
202
|
-
yield
|
203
|
-
end
|
204
|
-
end
|
205
|
-
|
206
|
-
def abort_transaction
|
207
|
-
raise AbortError.new
|
208
|
-
end
|
209
|
-
|
210
|
-
module_function :atomically, :abort_transaction
|
211
|
-
|
212
252
|
end
|