concurrent-ruby 1.0.0.pre4 → 1.0.0.pre5
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +11 -1
- data/lib/concurrent/agent.rb +5 -1
- data/lib/concurrent/async.rb +77 -38
- data/lib/concurrent/atom.rb +2 -1
- data/lib/concurrent/atomic/atomic_boolean.rb +1 -1
- data/lib/concurrent/atomic/atomic_fixnum.rb +1 -1
- data/lib/concurrent/atomic/atomic_reference.rb +1 -1
- data/lib/concurrent/atomic/semaphore.rb +1 -1
- data/lib/concurrent/atomic_reference/jruby+truffle.rb +1 -0
- data/lib/concurrent/atomic_reference/jruby.rb +1 -1
- data/lib/concurrent/atomic_reference/ruby.rb +1 -1
- data/lib/concurrent/concern/dereferenceable.rb +9 -24
- data/lib/concurrent/concern/obligation.rb +11 -8
- data/lib/concurrent/delay.rb +1 -1
- data/lib/concurrent/exchanger.rb +30 -17
- data/lib/concurrent/executor/ruby_thread_pool_executor.rb +6 -1
- data/lib/concurrent/ivar.rb +1 -1
- data/lib/concurrent/lazy_register.rb +11 -8
- data/lib/concurrent/map.rb +1 -1
- data/lib/concurrent/maybe.rb +6 -3
- data/lib/concurrent/mvar.rb +26 -2
- data/lib/concurrent/promise.rb +1 -1
- data/lib/concurrent/synchronization.rb +3 -0
- data/lib/concurrent/synchronization/abstract_lockable_object.rb +1 -20
- data/lib/concurrent/synchronization/abstract_object.rb +1 -27
- data/lib/concurrent/synchronization/jruby_object.rb +26 -18
- data/lib/concurrent/synchronization/lockable_object.rb +29 -16
- data/lib/concurrent/synchronization/mri_object.rb +27 -19
- data/lib/concurrent/synchronization/object.rb +48 -53
- data/lib/concurrent/synchronization/rbx_lockable_object.rb +9 -8
- data/lib/concurrent/synchronization/rbx_object.rb +29 -21
- data/lib/concurrent/synchronization/volatile.rb +34 -0
- data/lib/concurrent/timer_task.rb +0 -1
- data/lib/concurrent/tvar.rb +3 -1
- data/lib/concurrent/utility/engine.rb +4 -0
- data/lib/concurrent/utility/native_extension_loader.rb +6 -3
- data/lib/concurrent/version.rb +2 -2
- metadata +4 -2
data/lib/concurrent/delay.rb
CHANGED
data/lib/concurrent/exchanger.rb
CHANGED
@@ -38,7 +38,7 @@ module Concurrent
|
|
38
38
|
# threads.each {|t| t.join(2) }
|
39
39
|
#
|
40
40
|
# @!visibility private
|
41
|
-
class AbstractExchanger
|
41
|
+
class AbstractExchanger < Synchronization::Object
|
42
42
|
|
43
43
|
# @!visibility private
|
44
44
|
CANCEL = Object.new
|
@@ -46,7 +46,7 @@ module Concurrent
|
|
46
46
|
|
47
47
|
# @!macro [attach] exchanger_method_initialize
|
48
48
|
def initialize
|
49
|
-
|
49
|
+
super
|
50
50
|
end
|
51
51
|
|
52
52
|
# @!macro [attach] exchanger_method_do_exchange
|
@@ -140,24 +140,38 @@ module Concurrent
|
|
140
140
|
# not include the arena or the multi-processor spin loops.
|
141
141
|
# http://grepcode.com/file/repository.grepcode.com/java/root/jdk/openjdk/6-b14/java/util/concurrent/Exchanger.java
|
142
142
|
|
143
|
-
|
144
|
-
|
145
|
-
|
143
|
+
safe_initialization!
|
144
|
+
|
145
|
+
class Node < Concurrent::Synchronization::Object
|
146
|
+
attr_volatile_with_cas :value
|
147
|
+
safe_initialization!
|
148
|
+
|
146
149
|
def initialize(item)
|
147
|
-
|
148
|
-
@
|
149
|
-
|
150
|
+
super()
|
151
|
+
@Item = item
|
152
|
+
@Latch = Concurrent::CountDownLatch.new
|
153
|
+
self.value = nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def latch
|
157
|
+
@Latch
|
158
|
+
end
|
159
|
+
|
160
|
+
def item
|
161
|
+
@Item
|
150
162
|
end
|
151
163
|
end
|
152
164
|
private_constant :Node
|
153
165
|
|
154
166
|
# @!macro exchanger_method_initialize
|
155
167
|
def initialize
|
156
|
-
|
168
|
+
super
|
157
169
|
end
|
158
170
|
|
159
171
|
private
|
160
172
|
|
173
|
+
attr_volatile_with_cas(:slot)
|
174
|
+
|
161
175
|
# @!macro exchanger_method_do_exchange
|
162
176
|
#
|
163
177
|
# @return [Object, CANCEL] the value exchanged by the other thread; {CANCEL} on timeout
|
@@ -247,20 +261,19 @@ module Concurrent
|
|
247
261
|
# - Return the occupier's item
|
248
262
|
|
249
263
|
value = NULL if value.nil? # The sentinel allows nil to be a valid value
|
250
|
-
slot = @slot # Convenience to minimize typing @
|
251
264
|
me = Node.new(value) # create my node in case I need to occupy
|
252
265
|
end_at = Concurrent.monotonic_time + timeout.to_f # The time to give up
|
253
266
|
|
254
267
|
result = loop do
|
255
|
-
other = slot
|
256
|
-
if other &&
|
268
|
+
other = slot
|
269
|
+
if other && compare_and_set_slot(other, nil)
|
257
270
|
# try to fulfill
|
258
|
-
if other.
|
271
|
+
if other.compare_and_set_value(nil, value)
|
259
272
|
# happy path
|
260
273
|
other.latch.count_down
|
261
274
|
break other.item
|
262
275
|
end
|
263
|
-
elsif other.nil? &&
|
276
|
+
elsif other.nil? && compare_and_set_slot(nil, me)
|
264
277
|
# try to occupy
|
265
278
|
timeout = end_at - Concurrent.monotonic_time if timeout
|
266
279
|
if me.latch.wait(timeout)
|
@@ -268,11 +281,11 @@ module Concurrent
|
|
268
281
|
break me.value
|
269
282
|
else
|
270
283
|
# attempt to remove myself from the slot
|
271
|
-
if
|
284
|
+
if compare_and_set_slot(me, nil)
|
272
285
|
break CANCEL
|
273
|
-
elsif !me.
|
286
|
+
elsif !me.compare_and_set_value(nil, CANCEL)
|
274
287
|
# I've failed to block the fulfiller
|
275
|
-
break me.
|
288
|
+
break me.value
|
276
289
|
end
|
277
290
|
end
|
278
291
|
end
|
@@ -101,6 +101,11 @@ module Concurrent
|
|
101
101
|
synchronize { ns_worker_died worker }
|
102
102
|
end
|
103
103
|
|
104
|
+
# @!visibility private
|
105
|
+
def worker_task_completed
|
106
|
+
synchronize { @completed_task_count += 1 }
|
107
|
+
end
|
108
|
+
|
104
109
|
private
|
105
110
|
|
106
111
|
# @!visibility private
|
@@ -180,7 +185,6 @@ module Concurrent
|
|
180
185
|
worker = (@ready.pop if @pool.size >= @min_length) || ns_add_busy_worker
|
181
186
|
if worker
|
182
187
|
worker << [task, args]
|
183
|
-
@completed_task_count += 1
|
184
188
|
true
|
185
189
|
else
|
186
190
|
false
|
@@ -327,6 +331,7 @@ module Concurrent
|
|
327
331
|
|
328
332
|
def run_task(pool, task, args)
|
329
333
|
task.call(*args)
|
334
|
+
pool.worker_task_completed
|
330
335
|
rescue => ex
|
331
336
|
# let it fail
|
332
337
|
log DEBUG, ex
|
data/lib/concurrent/ivar.rb
CHANGED
@@ -7,19 +7,22 @@ module Concurrent
|
|
7
7
|
#
|
8
8
|
# @example
|
9
9
|
# register = Concurrent::LazyRegister.new
|
10
|
-
# #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @
|
10
|
+
# #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @Data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
|
11
11
|
# register[:key]
|
12
12
|
# #=> nil
|
13
13
|
# register.add(:key) { Concurrent::Actor.spawn!(Actor::AdHoc, :ping) { -> message { message } } }
|
14
|
-
# #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @
|
14
|
+
# #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @Data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
|
15
15
|
# register[:key]
|
16
16
|
# #=> #<Concurrent::Actor::Reference /ping (Concurrent::Actor::AdHoc)>
|
17
17
|
#
|
18
18
|
# @!macro edge_warning
|
19
|
-
class LazyRegister
|
19
|
+
class LazyRegister < Synchronization::Object
|
20
|
+
|
21
|
+
private(*attr_volatile_with_cas(:data))
|
20
22
|
|
21
23
|
def initialize
|
22
|
-
|
24
|
+
super
|
25
|
+
self.data = {}
|
23
26
|
end
|
24
27
|
|
25
28
|
# Element reference. Retrieves the value object corresponding to the
|
@@ -31,7 +34,7 @@ module Concurrent
|
|
31
34
|
#
|
32
35
|
# @raise Exception when the initialization block fails
|
33
36
|
def [](key)
|
34
|
-
delay =
|
37
|
+
delay = data[key]
|
35
38
|
delay ? delay.value! : nil
|
36
39
|
end
|
37
40
|
|
@@ -40,7 +43,7 @@ module Concurrent
|
|
40
43
|
# @param [Object] key
|
41
44
|
# @return [true, false] if the key is registered
|
42
45
|
def registered?(key)
|
43
|
-
|
46
|
+
data.key?(key)
|
44
47
|
end
|
45
48
|
|
46
49
|
alias_method :key?, :registered?
|
@@ -55,7 +58,7 @@ module Concurrent
|
|
55
58
|
# @return [LazyRegister] self
|
56
59
|
def register(key, &block)
|
57
60
|
delay = Delay.new(executor: :immediate, &block)
|
58
|
-
|
61
|
+
update_data { |h| h.merge(key => delay) }
|
59
62
|
self
|
60
63
|
end
|
61
64
|
|
@@ -68,7 +71,7 @@ module Concurrent
|
|
68
71
|
#
|
69
72
|
# @return [LazyRegister] self
|
70
73
|
def unregister(key)
|
71
|
-
|
74
|
+
update_data { |h| h.dup.tap { |j| j.delete(key) } }
|
72
75
|
self
|
73
76
|
end
|
74
77
|
|
data/lib/concurrent/map.rb
CHANGED
data/lib/concurrent/maybe.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'concurrent/synchronization'
|
2
|
+
|
1
3
|
module Concurrent
|
2
4
|
|
3
5
|
# A `Maybe` encapsulates an optional value. A `Maybe` either contains a value
|
@@ -99,8 +101,9 @@ module Concurrent
|
|
99
101
|
#
|
100
102
|
# @see https://hackage.haskell.org/package/base-4.2.0.1/docs/Data-Maybe.html Haskell Data.Maybe
|
101
103
|
# @see https://github.com/purescript/purescript-maybe/blob/master/docs/Data.Maybe.md PureScript Data.Maybe
|
102
|
-
class Maybe
|
104
|
+
class Maybe < Synchronization::Object
|
103
105
|
include Comparable
|
106
|
+
safe_initialization!
|
104
107
|
|
105
108
|
# Indicates that the given attribute has not been set.
|
106
109
|
# When `Just` the {#nothing} getter will return `NONE`.
|
@@ -168,7 +171,7 @@ module Concurrent
|
|
168
171
|
end
|
169
172
|
|
170
173
|
# Is this `Maybe` a `Just` (successfully fulfilled with a value)?
|
171
|
-
#
|
174
|
+
#
|
172
175
|
# @return [Boolean] True if `Just` or false if `Nothing`.
|
173
176
|
def just?
|
174
177
|
! nothing?
|
@@ -176,7 +179,7 @@ module Concurrent
|
|
176
179
|
alias :fulfilled? :just?
|
177
180
|
|
178
181
|
# Is this `Maybe` a `nothing` (rejected with an exception upon fulfillment)?
|
179
|
-
#
|
182
|
+
#
|
180
183
|
# @return [Boolean] True if `Nothing` or false if `Just`.
|
181
184
|
def nothing?
|
182
185
|
@nothing != NONE
|
data/lib/concurrent/mvar.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'concurrent/concern/dereferenceable'
|
2
|
+
require 'concurrent/synchronization'
|
2
3
|
|
3
4
|
module Concurrent
|
4
5
|
|
@@ -34,9 +35,9 @@ module Concurrent
|
|
34
35
|
# 2. S. Peyton Jones, A. Gordon, and S. Finne. [Concurrent Haskell](http://dl.acm.org/citation.cfm?id=237794).
|
35
36
|
# In Proceedings of the 23rd Symposium on Principles of Programming Languages
|
36
37
|
# (PoPL), 1996.
|
37
|
-
class MVar
|
38
|
-
|
38
|
+
class MVar < Synchronization::Object
|
39
39
|
include Concern::Dereferenceable
|
40
|
+
safe_initialization!
|
40
41
|
|
41
42
|
# Unique value that represents that an `MVar` was empty
|
42
43
|
EMPTY = Object.new
|
@@ -78,6 +79,23 @@ module Concurrent
|
|
78
79
|
end
|
79
80
|
end
|
80
81
|
|
82
|
+
# acquires lock on the from an `MVAR`, yields the value to provided block,
|
83
|
+
# and release lock. A timeout can be set to limit the time spent blocked,
|
84
|
+
# in which case it returns `TIMEOUT` if the time is exceeded.
|
85
|
+
# @return [Object] the value returned by the block, or `TIMEOUT`
|
86
|
+
def borrow(timeout = nil)
|
87
|
+
@mutex.synchronize do
|
88
|
+
wait_for_full(timeout)
|
89
|
+
|
90
|
+
# if we timeoud out we'll still be empty
|
91
|
+
if unlocked_full?
|
92
|
+
yield @value
|
93
|
+
else
|
94
|
+
TIMEOUT
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
81
99
|
# Put a value into an `MVar`, blocking if there is already a value until
|
82
100
|
# it is empty. A timeout can be set to limit the time spent blocked, in
|
83
101
|
# which case it returns `TIMEOUT` if the time is exceeded.
|
@@ -183,6 +201,12 @@ module Concurrent
|
|
183
201
|
!empty?
|
184
202
|
end
|
185
203
|
|
204
|
+
protected
|
205
|
+
|
206
|
+
def synchronize(&block)
|
207
|
+
@mutex.synchronize(&block)
|
208
|
+
end
|
209
|
+
|
186
210
|
private
|
187
211
|
|
188
212
|
def unlocked_empty?
|
data/lib/concurrent/promise.rb
CHANGED
@@ -304,7 +304,7 @@ module Concurrent
|
|
304
304
|
# @return [Promise] the new promise
|
305
305
|
def then(rescuer = nil, &block)
|
306
306
|
raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
|
307
|
-
block = Proc.new { |result| result }
|
307
|
+
block = Proc.new { |result| result } unless block_given?
|
308
308
|
child = Promise.new(
|
309
309
|
parent: self,
|
310
310
|
executor: @executor,
|
@@ -2,11 +2,13 @@ require 'concurrent/utility/engine'
|
|
2
2
|
|
3
3
|
require 'concurrent/synchronization/abstract_object'
|
4
4
|
require 'concurrent/utility/native_extension_loader' # load native parts first
|
5
|
+
Concurrent.load_native_extensions
|
5
6
|
|
6
7
|
require 'concurrent/synchronization/mri_object'
|
7
8
|
require 'concurrent/synchronization/jruby_object'
|
8
9
|
require 'concurrent/synchronization/rbx_object'
|
9
10
|
require 'concurrent/synchronization/object'
|
11
|
+
require 'concurrent/synchronization/volatile'
|
10
12
|
|
11
13
|
require 'concurrent/synchronization/abstract_lockable_object'
|
12
14
|
require 'concurrent/synchronization/mri_lockable_object'
|
@@ -20,6 +22,7 @@ require 'concurrent/synchronization/lock'
|
|
20
22
|
|
21
23
|
module Concurrent
|
22
24
|
# {include:file:doc/synchronization.md}
|
25
|
+
# {include:file:doc/synchronization-notes.md}
|
23
26
|
module Synchronization
|
24
27
|
end
|
25
28
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
module Concurrent
|
2
2
|
module Synchronization
|
3
|
-
|
3
|
+
|
4
4
|
# @!visibility private
|
5
5
|
class AbstractLockableObject < Object
|
6
6
|
|
@@ -15,25 +15,6 @@ module Concurrent
|
|
15
15
|
raise NotImplementedError
|
16
16
|
end
|
17
17
|
|
18
|
-
# @!macro [attach] synchronization_object_method_ns_initialize
|
19
|
-
#
|
20
|
-
# initialization of the object called inside synchronize block
|
21
|
-
# @note has to be called manually when required in children of this class
|
22
|
-
# @example
|
23
|
-
# class Child < Concurrent::Synchornization::Object
|
24
|
-
# def initialize(*args, &block)
|
25
|
-
# super(&nil)
|
26
|
-
# synchronize { ns_initialize(*args, &block) }
|
27
|
-
# end
|
28
|
-
#
|
29
|
-
# def ns_initialize(*args, &block)
|
30
|
-
# @args = args
|
31
|
-
# end
|
32
|
-
# end
|
33
|
-
# TODO (pitr 12-Sep-2015): remove
|
34
|
-
def ns_initialize(*args, &block)
|
35
|
-
end
|
36
|
-
|
37
18
|
# @!macro [attach] synchronization_object_method_ns_wait_until
|
38
19
|
#
|
39
20
|
# Wait until condition is met or timeout passes,
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module Concurrent
|
2
2
|
module Synchronization
|
3
3
|
|
4
|
-
# @!macro synchronization_object
|
5
4
|
# @!visibility private
|
5
|
+
# @!macro internal_implementation_note
|
6
6
|
class AbstractObject
|
7
7
|
|
8
8
|
# @abstract has to be implemented based on Ruby runtime
|
@@ -10,38 +10,12 @@ module Concurrent
|
|
10
10
|
raise NotImplementedError
|
11
11
|
end
|
12
12
|
|
13
|
-
# @!macro [attach] synchronization_object_method_ensure_ivar_visibility
|
14
|
-
#
|
15
|
-
# Allows to construct immutable objects where all fields are visible after initialization, not requiring
|
16
|
-
# further synchronization on access.
|
17
|
-
# @example
|
18
|
-
# class AClass
|
19
|
-
# attr_reader :val
|
20
|
-
# def initialize(val)
|
21
|
-
# @val = val # final value, after assignment it's not changed (just convention, not enforced)
|
22
|
-
# ensure_ivar_visibility!
|
23
|
-
# # now it can be shared as Java's final field
|
24
|
-
# end
|
25
|
-
# end
|
26
|
-
# @!visibility private
|
27
|
-
def ensure_ivar_visibility!
|
28
|
-
# We have to prevent ivar writes to reordered with storing of the final instance reference
|
29
|
-
# Therefore wee need a fullFence to prevent reordering in both directions.
|
30
|
-
full_memory_barrier
|
31
|
-
end
|
32
|
-
|
33
|
-
protected
|
34
|
-
|
35
13
|
# @!visibility private
|
36
14
|
# @abstract
|
37
15
|
def full_memory_barrier
|
38
16
|
raise NotImplementedError
|
39
17
|
end
|
40
18
|
|
41
|
-
# @!macro [attach] synchronization_object_method_self_attr_volatile
|
42
|
-
#
|
43
|
-
# creates methods for reading and writing to a instance variable with volatile (Java semantic) instance variable
|
44
|
-
# return [Array<Symbol>] names of defined method names
|
45
19
|
def self.attr_volatile(*names)
|
46
20
|
raise NotImplementedError
|
47
21
|
end
|
@@ -3,33 +3,41 @@ module Concurrent
|
|
3
3
|
|
4
4
|
if Concurrent.on_jruby? && Concurrent.java_extensions_loaded?
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
def initialize
|
11
|
-
# nothing to do
|
6
|
+
module JRubyAttrVolatile
|
7
|
+
def self.included(base)
|
8
|
+
base.extend(ClassMethods)
|
12
9
|
end
|
13
10
|
|
14
|
-
|
15
|
-
names
|
11
|
+
module ClassMethods
|
12
|
+
def attr_volatile(*names)
|
13
|
+
names.each do |name|
|
16
14
|
|
17
|
-
|
15
|
+
ivar = :"@volatile_#{name}"
|
18
16
|
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
17
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
18
|
+
def #{name}
|
19
|
+
instance_variable_get_volatile(:#{ivar})
|
20
|
+
end
|
23
21
|
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
22
|
+
def #{name}=(value)
|
23
|
+
instance_variable_set_volatile(:#{ivar}, value)
|
24
|
+
end
|
25
|
+
RUBY
|
28
26
|
|
27
|
+
end
|
28
|
+
names.map { |n| [n, :"#{n}="] }.flatten
|
29
29
|
end
|
30
|
-
names.map { |n| [n, :"#{n}="] }.flatten
|
31
30
|
end
|
31
|
+
end
|
32
32
|
|
33
|
+
# @!visibility private
|
34
|
+
# @!macro internal_implementation_note
|
35
|
+
class JRubyObject < AbstractObject
|
36
|
+
include JRubyAttrVolatile
|
37
|
+
|
38
|
+
def initialize
|
39
|
+
# nothing to do
|
40
|
+
end
|
33
41
|
end
|
34
42
|
end
|
35
43
|
end
|