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.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -1
  3. data/lib/concurrent/agent.rb +5 -1
  4. data/lib/concurrent/async.rb +77 -38
  5. data/lib/concurrent/atom.rb +2 -1
  6. data/lib/concurrent/atomic/atomic_boolean.rb +1 -1
  7. data/lib/concurrent/atomic/atomic_fixnum.rb +1 -1
  8. data/lib/concurrent/atomic/atomic_reference.rb +1 -1
  9. data/lib/concurrent/atomic/semaphore.rb +1 -1
  10. data/lib/concurrent/atomic_reference/jruby+truffle.rb +1 -0
  11. data/lib/concurrent/atomic_reference/jruby.rb +1 -1
  12. data/lib/concurrent/atomic_reference/ruby.rb +1 -1
  13. data/lib/concurrent/concern/dereferenceable.rb +9 -24
  14. data/lib/concurrent/concern/obligation.rb +11 -8
  15. data/lib/concurrent/delay.rb +1 -1
  16. data/lib/concurrent/exchanger.rb +30 -17
  17. data/lib/concurrent/executor/ruby_thread_pool_executor.rb +6 -1
  18. data/lib/concurrent/ivar.rb +1 -1
  19. data/lib/concurrent/lazy_register.rb +11 -8
  20. data/lib/concurrent/map.rb +1 -1
  21. data/lib/concurrent/maybe.rb +6 -3
  22. data/lib/concurrent/mvar.rb +26 -2
  23. data/lib/concurrent/promise.rb +1 -1
  24. data/lib/concurrent/synchronization.rb +3 -0
  25. data/lib/concurrent/synchronization/abstract_lockable_object.rb +1 -20
  26. data/lib/concurrent/synchronization/abstract_object.rb +1 -27
  27. data/lib/concurrent/synchronization/jruby_object.rb +26 -18
  28. data/lib/concurrent/synchronization/lockable_object.rb +29 -16
  29. data/lib/concurrent/synchronization/mri_object.rb +27 -19
  30. data/lib/concurrent/synchronization/object.rb +48 -53
  31. data/lib/concurrent/synchronization/rbx_lockable_object.rb +9 -8
  32. data/lib/concurrent/synchronization/rbx_object.rb +29 -21
  33. data/lib/concurrent/synchronization/volatile.rb +34 -0
  34. data/lib/concurrent/timer_task.rb +0 -1
  35. data/lib/concurrent/tvar.rb +3 -1
  36. data/lib/concurrent/utility/engine.rb +4 -0
  37. data/lib/concurrent/utility/native_extension_loader.rb +6 -3
  38. data/lib/concurrent/version.rb +2 -2
  39. metadata +4 -2
@@ -155,7 +155,7 @@ module Concurrent
155
155
  protected
156
156
 
157
157
  def ns_initialize(opts, &block)
158
- init_obligation(self)
158
+ init_obligation
159
159
  set_deref_options(opts)
160
160
  @executor = opts[:executor]
161
161
 
@@ -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
- raise NotImplementedError
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
- # @!visibility private
144
- class Node < Concurrent::AtomicReference
145
- attr_reader :item, :latch
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
- @item = item
148
- @latch = Concurrent::CountDownLatch.new
149
- super(nil)
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
- @slot = Concurrent::AtomicReference.new
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.get
256
- if other && slot.compare_and_set(other, nil)
268
+ other = slot
269
+ if other && compare_and_set_slot(other, nil)
257
270
  # try to fulfill
258
- if other.compare_and_set(nil, value)
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? && slot.compare_and_set(nil, me)
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 slot.compare_and_set(me, nil)
284
+ if compare_and_set_slot(me, nil)
272
285
  break CANCEL
273
- elsif !me.compare_and_set(nil, CANCEL)
286
+ elsif !me.compare_and_set_value(nil, CANCEL)
274
287
  # I've failed to block the fulfiller
275
- break me.get
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
@@ -153,7 +153,7 @@ module Concurrent
153
153
  # @!visibility private
154
154
  def ns_initialize(value, opts)
155
155
  value = yield if block_given?
156
- init_obligation(self)
156
+ init_obligation
157
157
  self.observers = Collection::CopyOnWriteObserverSet.new
158
158
  set_deref_options(opts)
159
159
 
@@ -7,19 +7,22 @@ module Concurrent
7
7
  #
8
8
  # @example
9
9
  # register = Concurrent::LazyRegister.new
10
- # #=> #<Concurrent::LazyRegister:0x007fd7ecd5e230 @data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
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 @data=#<Concurrent::AtomicReference:0x007fd7ecd5e1e0>>
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
- @data = AtomicReference.new(Hash.new)
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 = @data.get[key]
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
- @data.get.key?(key)
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
- @data.update { |h| h.merge(key => delay) }
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
- @data.update { |h| h.dup.tap { |j| j.delete(key) } }
74
+ update_data { |h| h.dup.tap { |j| j.delete(key) } }
72
75
  self
73
76
  end
74
77
 
@@ -1,6 +1,6 @@
1
1
  require 'thread'
2
2
  require 'concurrent/constants'
3
- require 'concurrent/utility/native_extension_loader'
3
+ require 'concurrent/synchronization'
4
4
 
5
5
  module Concurrent
6
6
  # @!visibility private
@@ -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
@@ -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?
@@ -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 } if block.nil?
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
- # @!macro synchronization_object
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
- # @!visibility private
7
- # @!macro internal_implementation_note
8
- class JRubyObject < AbstractObject
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
- def self.attr_volatile(*names)
15
- names.each do |name|
11
+ module ClassMethods
12
+ def attr_volatile(*names)
13
+ names.each do |name|
16
14
 
17
- ivar = :"@volatile_#{name}"
15
+ ivar = :"@volatile_#{name}"
18
16
 
19
- class_eval <<-RUBY, __FILE__, __LINE__ + 1
20
- def #{name}
21
- instance_variable_get_volatile(:#{ivar})
22
- end
17
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
18
+ def #{name}
19
+ instance_variable_get_volatile(:#{ivar})
20
+ end
23
21
 
24
- def #{name}=(value)
25
- instance_variable_set_volatile(:#{ivar}, value)
26
- end
27
- RUBY
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