concurrent-ruby 1.0.5 → 1.1.1

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.
Files changed (109) hide show
  1. checksums.yaml +5 -5
  2. data/CHANGELOG.md +65 -0
  3. data/Gemfile +39 -0
  4. data/{LICENSE.txt → LICENSE.md} +2 -0
  5. data/README.md +207 -105
  6. data/Rakefile +314 -0
  7. data/ext/concurrent-ruby/ConcurrentRubyService.java +17 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/AtomicReferenceLibrary.java +175 -0
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JRubyMapBackendLibrary.java +248 -0
  10. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicBooleanLibrary.java +93 -0
  11. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +113 -0
  12. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +159 -0
  13. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +306 -0
  14. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMap.java +31 -0
  15. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/ConcurrentHashMapV8.java +3863 -0
  16. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/LongAdder.java +203 -0
  17. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/Striped64.java +342 -0
  18. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/ConcurrentHashMapV8.java +3800 -0
  19. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/LongAdder.java +204 -0
  20. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166e/nounsafe/Striped64.java +291 -0
  21. data/ext/concurrent-ruby/com/concurrent_ruby/ext/jsr166y/ThreadLocalRandom.java +199 -0
  22. data/lib/concurrent/agent.rb +7 -7
  23. data/lib/concurrent/array.rb +59 -32
  24. data/lib/concurrent/async.rb +4 -4
  25. data/lib/concurrent/atom.rb +9 -9
  26. data/lib/concurrent/atomic/atomic_boolean.rb +24 -20
  27. data/lib/concurrent/atomic/atomic_fixnum.rb +27 -23
  28. data/lib/concurrent/atomic/atomic_markable_reference.rb +164 -0
  29. data/lib/concurrent/atomic/atomic_reference.rb +185 -32
  30. data/lib/concurrent/atomic/count_down_latch.rb +6 -6
  31. data/lib/concurrent/atomic/cyclic_barrier.rb +1 -1
  32. data/lib/concurrent/atomic/event.rb +1 -1
  33. data/lib/concurrent/atomic/java_count_down_latch.rb +9 -6
  34. data/lib/concurrent/atomic/mutex_atomic_boolean.rb +2 -0
  35. data/lib/concurrent/atomic/mutex_count_down_latch.rb +1 -0
  36. data/lib/concurrent/atomic/read_write_lock.rb +2 -1
  37. data/lib/concurrent/atomic/reentrant_read_write_lock.rb +3 -1
  38. data/lib/concurrent/atomic/semaphore.rb +8 -8
  39. data/lib/concurrent/atomic/thread_local_var.rb +7 -7
  40. data/lib/concurrent/atomic_reference/mutex_atomic.rb +3 -8
  41. data/lib/concurrent/atomic_reference/numeric_cas_wrapper.rb +1 -1
  42. data/lib/concurrent/atomics.rb +0 -43
  43. data/lib/concurrent/collection/lock_free_stack.rb +158 -0
  44. data/lib/concurrent/collection/map/atomic_reference_map_backend.rb +3 -3
  45. data/lib/concurrent/collection/map/non_concurrent_map_backend.rb +1 -2
  46. data/lib/concurrent/collection/non_concurrent_priority_queue.rb +29 -29
  47. data/lib/concurrent/concern/dereferenceable.rb +1 -1
  48. data/lib/concurrent/concern/logging.rb +6 -1
  49. data/lib/concurrent/concern/observable.rb +7 -7
  50. data/lib/concurrent/concurrent_ruby.jar +0 -0
  51. data/lib/concurrent/configuration.rb +1 -6
  52. data/lib/concurrent/constants.rb +1 -1
  53. data/lib/concurrent/dataflow.rb +2 -1
  54. data/lib/concurrent/delay.rb +9 -7
  55. data/lib/concurrent/exchanger.rb +21 -25
  56. data/lib/concurrent/executor/abstract_executor_service.rb +2 -2
  57. data/lib/concurrent/executor/cached_thread_pool.rb +1 -1
  58. data/lib/concurrent/executor/executor_service.rb +15 -15
  59. data/lib/concurrent/executor/fixed_thread_pool.rb +18 -18
  60. data/lib/concurrent/executor/java_thread_pool_executor.rb +10 -7
  61. data/lib/concurrent/executor/single_thread_executor.rb +2 -2
  62. data/lib/concurrent/executor/thread_pool_executor.rb +6 -6
  63. data/lib/concurrent/executor/timer_set.rb +1 -1
  64. data/lib/concurrent/future.rb +4 -1
  65. data/lib/concurrent/hash.rb +53 -30
  66. data/lib/concurrent/ivar.rb +5 -6
  67. data/lib/concurrent/map.rb +178 -81
  68. data/lib/concurrent/maybe.rb +1 -1
  69. data/lib/concurrent/mutable_struct.rb +15 -14
  70. data/lib/concurrent/mvar.rb +2 -2
  71. data/lib/concurrent/promise.rb +53 -21
  72. data/lib/concurrent/promises.rb +1936 -0
  73. data/lib/concurrent/re_include.rb +58 -0
  74. data/lib/concurrent/set.rb +66 -0
  75. data/lib/concurrent/settable_struct.rb +1 -0
  76. data/lib/concurrent/synchronization/abstract_lockable_object.rb +5 -5
  77. data/lib/concurrent/synchronization/abstract_struct.rb +6 -4
  78. data/lib/concurrent/synchronization/lockable_object.rb +6 -6
  79. data/lib/concurrent/synchronization/{mri_lockable_object.rb → mutex_lockable_object.rb} +19 -14
  80. data/lib/concurrent/synchronization/object.rb +8 -4
  81. data/lib/concurrent/synchronization/truffleruby_object.rb +46 -0
  82. data/lib/concurrent/synchronization/volatile.rb +11 -9
  83. data/lib/concurrent/synchronization.rb +4 -5
  84. data/lib/concurrent/thread_safe/util/data_structures.rb +63 -0
  85. data/lib/concurrent/thread_safe/util/striped64.rb +9 -4
  86. data/lib/concurrent/timer_task.rb +5 -2
  87. data/lib/concurrent/tuple.rb +1 -1
  88. data/lib/concurrent/tvar.rb +2 -2
  89. data/lib/concurrent/utility/193.rb +17 -0
  90. data/lib/concurrent/utility/at_exit.rb +1 -1
  91. data/lib/concurrent/utility/engine.rb +4 -4
  92. data/lib/concurrent/utility/monotonic_time.rb +3 -3
  93. data/lib/concurrent/utility/native_extension_loader.rb +31 -33
  94. data/lib/concurrent/utility/processor_counter.rb +0 -2
  95. data/lib/concurrent/version.rb +2 -2
  96. data/lib/concurrent-ruby.rb +1 -0
  97. data/lib/concurrent.rb +26 -20
  98. metadata +33 -18
  99. data/lib/concurrent/atomic_reference/concurrent_update_error.rb +0 -8
  100. data/lib/concurrent/atomic_reference/direct_update.rb +0 -81
  101. data/lib/concurrent/atomic_reference/jruby+truffle.rb +0 -2
  102. data/lib/concurrent/atomic_reference/jruby.rb +0 -16
  103. data/lib/concurrent/atomic_reference/rbx.rb +0 -22
  104. data/lib/concurrent/atomic_reference/ruby.rb +0 -32
  105. data/lib/concurrent/edge.rb +0 -26
  106. data/lib/concurrent/lazy_register.rb +0 -81
  107. data/lib/concurrent/synchronization/truffle_lockable_object.rb +0 -9
  108. data/lib/concurrent/synchronization/truffle_object.rb +0 -31
  109. data/lib/concurrent/thread_safe/util/array_hash_rbx.rb +0 -30
@@ -1,33 +1,27 @@
1
1
  require 'thread'
2
2
  require 'concurrent/constants'
3
3
  require 'concurrent/synchronization'
4
+ require 'concurrent/utility/engine'
4
5
 
5
6
  module Concurrent
6
7
  # @!visibility private
7
8
  module Collection
8
9
 
9
10
  # @!visibility private
10
- MapImplementation = if Concurrent.java_extensions_loaded?
11
+ MapImplementation = case
12
+ when Concurrent.on_jruby?
11
13
  # noinspection RubyResolve
12
14
  JRubyMapBackend
13
- elsif defined?(RUBY_ENGINE)
14
- case RUBY_ENGINE
15
- when 'ruby'
16
- require 'concurrent/collection/map/mri_map_backend'
17
- MriMapBackend
18
- when 'rbx'
19
- require 'concurrent/collection/map/atomic_reference_map_backend'
20
- AtomicReferenceMapBackend
21
- when 'jruby+truffle'
22
- require 'concurrent/collection/map/atomic_reference_map_backend'
23
- AtomicReferenceMapBackend
24
- else
25
- warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation' if $VERBOSE
26
- require 'concurrent/collection/map/synchronized_map_backend'
27
- SynchronizedMapBackend
28
- end
29
- else
15
+ when Concurrent.on_cruby?
16
+ require 'concurrent/collection/map/mri_map_backend'
30
17
  MriMapBackend
18
+ when Concurrent.on_rbx? || Concurrent.on_truffleruby?
19
+ require 'concurrent/collection/map/atomic_reference_map_backend'
20
+ AtomicReferenceMapBackend
21
+ else
22
+ warn 'Concurrent::Map: unsupported Ruby engine, using a fully synchronized Concurrent::Map implementation'
23
+ require 'concurrent/collection/map/synchronized_map_backend'
24
+ SynchronizedMapBackend
31
25
  end
32
26
  end
33
27
 
@@ -37,46 +31,89 @@ module Concurrent
37
31
  # -- for instance, it does not necessarily retain ordering by insertion time as `Hash`
38
32
  # does. For most uses it should do fine though, and we recommend you consider
39
33
  # `Concurrent::Map` instead of `Concurrent::Hash` for your concurrency-safe hash needs.
40
- #
41
- # > require 'concurrent'
42
- # >
43
- # > map = Concurrent::Map.new
44
34
  class Map < Collection::MapImplementation
45
35
 
46
- # @!macro [new] map_method_is_atomic
47
- # This method is atomic. Atomic methods of `Map` which accept a block
48
- # do not allow the `self` instance to be used within the block. Doing
49
- # so will cause a deadlock.
50
-
51
- # @!method put_if_absent
52
- # @!macro map_method_is_atomic
53
-
54
- # @!method compute_if_absent
55
- # @!macro map_method_is_atomic
56
-
57
- # @!method compute_if_present
58
- # @!macro map_method_is_atomic
59
-
60
- # @!method compute
61
- # @!macro map_method_is_atomic
62
-
63
- # @!method merge_pair
64
- # @!macro map_method_is_atomic
65
-
66
- # @!method replace_pair
67
- # @!macro map_method_is_atomic
68
-
69
- # @!method replace_if_exists
70
- # @!macro map_method_is_atomic
71
-
72
- # @!method get_and_set
73
- # @!macro map_method_is_atomic
36
+ # @!macro map.atomic_method
37
+ # This method is atomic.
38
+
39
+ # @!macro map.atomic_method_with_block
40
+ # This method is atomic.
41
+ # @note Atomic methods taking a block do not allow the `self` instance
42
+ # to be used within the block. Doing so will cause a deadlock.
43
+
44
+ # @!method compute_if_absent(key)
45
+ # Compute and store new value for key if the key is absent.
46
+ # @param [Object] key
47
+ # @yield new value
48
+ # @yieldreturn [Object] new value
49
+ # @return [Object] new value or current value
50
+ # @!macro map.atomic_method_with_block
51
+
52
+ # @!method compute_if_present(key)
53
+ # Compute and store new value for key if the key is present.
54
+ # @param [Object] key
55
+ # @yield new value
56
+ # @yieldparam old_value [Object]
57
+ # @yieldreturn [Object, nil] new value, when nil the key is removed
58
+ # @return [Object, nil] new value or nil
59
+ # @!macro map.atomic_method_with_block
60
+
61
+ # @!method compute(key)
62
+ # Compute and store new value for key.
63
+ # @param [Object] key
64
+ # @yield compute new value from old one
65
+ # @yieldparam old_value [Object, nil] old_value, or nil when key is absent
66
+ # @yieldreturn [Object, nil] new value, when nil the key is removed
67
+ # @return [Object, nil] new value or nil
68
+ # @!macro map.atomic_method_with_block
69
+
70
+ # @!method merge_pair(key, value)
71
+ # If the key is absent, the value is stored, otherwise new value is
72
+ # computed with a block.
73
+ # @param [Object] key
74
+ # @param [Object] value
75
+ # @yield compute new value from old one
76
+ # @yieldparam old_value [Object] old value
77
+ # @yieldreturn [Object, nil] new value, when nil the key is removed
78
+ # @return [Object, nil] new value or nil
79
+ # @!macro map.atomic_method_with_block
80
+
81
+ # @!method replace_pair(key, old_value, new_value)
82
+ # Replaces old_value with new_value if key exists and current value
83
+ # matches old_value
84
+ # @param [Object] key
85
+ # @param [Object] old_value
86
+ # @param [Object] new_value
87
+ # @return [true, false] true if replaced
88
+ # @!macro map.atomic_method
89
+
90
+ # @!method replace_if_exists(key, new_value)
91
+ # Replaces current value with new_value if key exists
92
+ # @param [Object] key
93
+ # @param [Object] new_value
94
+ # @return [Object, nil] old value or nil
95
+ # @!macro map.atomic_method
96
+
97
+ # @!method get_and_set(key, value)
98
+ # Get the current value under key and set new value.
99
+ # @param [Object] key
100
+ # @param [Object] value
101
+ # @return [Object, nil] old value or nil when the key was absent
102
+ # @!macro map.atomic_method
103
+
104
+ # @!method delete(key)
105
+ # Delete key and its value.
106
+ # @param [Object] key
107
+ # @return [Object, nil] old value or nil when the key was absent
108
+ # @!macro map.atomic_method
109
+
110
+ # @!method delete_pair(key, value)
111
+ # Delete pair and its value if current value equals the provided value.
112
+ # @param [Object] key
113
+ # @param [Object] value
114
+ # @return [true, false] true if deleted
115
+ # @!macro map.atomic_method
74
116
 
75
- # @!method delete
76
- # @!macro map_method_is_atomic
77
-
78
- # @!method delete_pair
79
- # @!macro map_method_is_atomic
80
117
 
81
118
  def initialize(options = nil, &block)
82
119
  if options.kind_of?(::Hash)
@@ -89,6 +126,9 @@ module Concurrent
89
126
  @default_proc = block
90
127
  end
91
128
 
129
+ # Get a value with key
130
+ # @param [Object] key
131
+ # @return [Object] the value
92
132
  def [](key)
93
133
  if value = super # non-falsy value is an existing mapping, return it right away
94
134
  value
@@ -104,17 +144,27 @@ module Concurrent
104
144
  end
105
145
 
106
146
  alias_method :get, :[]
147
+ # TODO (pitr-ch 30-Oct-2018): doc
107
148
  alias_method :put, :[]=
108
149
 
109
- # @!macro [attach] map_method_not_atomic
110
- # The "fetch-then-act" methods of `Map` are not atomic. `Map` is intended
111
- # to be use as a concurrency primitive with strong happens-before
112
- # guarantees. It is not intended to be used as a high-level abstraction
113
- # supporting complex operations. All read and write operations are
114
- # thread safe, but no guarantees are made regarding race conditions
115
- # between the fetch operation and yielding to the block. Additionally,
116
- # this method does not support recursion. This is due to internal
117
- # constraints that are very unlikely to change in the near future.
150
+ # Get a value with key, or default_value when key is absent,
151
+ # or fail when no default_value is given.
152
+ # @param [Object] key
153
+ # @param [Object] default_value
154
+ # @yield default value for a key
155
+ # @yieldparam key [Object]
156
+ # @yieldreturn [Object] default value
157
+ # @return [Object] the value or default value
158
+ # @raise [KeyError] when key is missing and no default_value is provided
159
+ # @!macro map_method_not_atomic
160
+ # @note The "fetch-then-act" methods of `Map` are not atomic. `Map` is intended
161
+ # to be use as a concurrency primitive with strong happens-before
162
+ # guarantees. It is not intended to be used as a high-level abstraction
163
+ # supporting complex operations. All read and write operations are
164
+ # thread safe, but no guarantees are made regarding race conditions
165
+ # between the fetch operation and yielding to the block. Additionally,
166
+ # this method does not support recursion. This is due to internal
167
+ # constraints that are very unlikely to change in the near future.
118
168
  def fetch(key, default_value = NULL)
119
169
  if NULL != (value = get_or_default(key, NULL))
120
170
  value
@@ -127,23 +177,39 @@ module Concurrent
127
177
  end
128
178
  end
129
179
 
130
- # @!macro map_method_not_atomic
180
+ # Fetch value with key, or store default value when key is absent,
181
+ # or fail when no default_value is given. This is a two step operation,
182
+ # therefore not atomic. The store can overwrite other concurrently
183
+ # stored value.
184
+ # @param [Object] key
185
+ # @param [Object] default_value
186
+ # @yield default value for a key
187
+ # @yieldparam key [Object]
188
+ # @yieldreturn [Object] default value
189
+ # @return [Object] the value or default value
190
+ # @!macro map.atomic_method_with_block
131
191
  def fetch_or_store(key, default_value = NULL)
132
192
  fetch(key) do
133
193
  put(key, block_given? ? yield(key) : (NULL == default_value ? raise_fetch_no_key : default_value))
134
194
  end
135
195
  end
136
196
 
137
- # @!macro map_method_is_atomic
197
+ # Insert value into map with key if key is absent in one atomic step.
198
+ # @param [Object] key
199
+ # @param [Object] value
200
+ # @return [Object, nil] the value or nil when key was present
138
201
  def put_if_absent(key, value)
139
202
  computed = false
140
- result = compute_if_absent(key) do
203
+ result = compute_if_absent(key) do
141
204
  computed = true
142
205
  value
143
206
  end
144
207
  computed ? nil : result
145
208
  end unless method_defined?(:put_if_absent)
146
209
 
210
+ # Is the value stored in the map. Iterates over all values.
211
+ # @param [Object] value
212
+ # @return [true, false]
147
213
  def value?(value)
148
214
  each_value do |v|
149
215
  return true if value.equal?(v)
@@ -151,52 +217,86 @@ module Concurrent
151
217
  false
152
218
  end
153
219
 
220
+ # All keys
221
+ # @return [::Array<Object>] keys
154
222
  def keys
155
223
  arr = []
156
- each_pair {|k, v| arr << k}
224
+ each_pair { |k, v| arr << k }
157
225
  arr
158
226
  end unless method_defined?(:keys)
159
227
 
228
+ # All values
229
+ # @return [::Array<Object>] values
160
230
  def values
161
231
  arr = []
162
- each_pair {|k, v| arr << v}
232
+ each_pair { |k, v| arr << v }
163
233
  arr
164
234
  end unless method_defined?(:values)
165
235
 
236
+ # Iterates over each key.
237
+ # @yield for each key in the map
238
+ # @yieldparam key [Object]
239
+ # @return [self]
240
+ # @!macro map.atomic_method_with_block
166
241
  def each_key
167
- each_pair {|k, v| yield k}
242
+ each_pair { |k, v| yield k }
168
243
  end unless method_defined?(:each_key)
169
244
 
245
+ # Iterates over each value.
246
+ # @yield for each value in the map
247
+ # @yieldparam value [Object]
248
+ # @return [self]
249
+ # @!macro map.atomic_method_with_block
170
250
  def each_value
171
- each_pair {|k, v| yield v}
251
+ each_pair { |k, v| yield v }
172
252
  end unless method_defined?(:each_value)
173
253
 
254
+ # Iterates over each key value pair.
255
+ # @yield for each key value pair in the map
256
+ # @yieldparam key [Object]
257
+ # @yieldparam value [Object]
258
+ # @return [self]
259
+ # @!macro map.atomic_method_with_block
260
+ def each_pair
261
+ return enum_for :each_pair unless block_given?
262
+ super
263
+ end
264
+
174
265
  alias_method :each, :each_pair unless method_defined?(:each)
175
266
 
267
+ # Find key of a value.
268
+ # @param [Object] value
269
+ # @return [Object, nil] key or nil when not found
176
270
  def key(value)
177
- each_pair {|k, v| return k if v == value}
271
+ each_pair { |k, v| return k if v == value }
178
272
  nil
179
273
  end unless method_defined?(:key)
180
274
  alias_method :index, :key if RUBY_VERSION < '1.9'
181
275
 
276
+ # Is map empty?
277
+ # @return [true, false]
182
278
  def empty?
183
- each_pair {|k, v| return false}
279
+ each_pair { |k, v| return false }
184
280
  true
185
281
  end unless method_defined?(:empty?)
186
282
 
283
+ # The size of map.
284
+ # @return [Integer] size
187
285
  def size
188
286
  count = 0
189
- each_pair {|k, v| count += 1}
287
+ each_pair { |k, v| count += 1 }
190
288
  count
191
289
  end unless method_defined?(:size)
192
290
 
291
+ # @!visibility private
193
292
  def marshal_dump
194
293
  raise TypeError, "can't dump hash with default proc" if @default_proc
195
294
  h = {}
196
- each_pair {|k, v| h[k] = v}
295
+ each_pair { |k, v| h[k] = v }
197
296
  h
198
297
  end
199
298
 
299
+ # @!visibility private
200
300
  def marshal_load(hash)
201
301
  initialize
202
302
  populate_from(hash)
@@ -205,15 +305,12 @@ module Concurrent
205
305
  undef :freeze
206
306
 
207
307
  # @!visibility private
208
- DEFAULT_OBJ_ID_STR_WIDTH = 0.size == 4 ? 7 : 14 # we want to look "native", 7 for 32-bit, 14 for 64-bit
209
- # override default #inspect() method: firstly, we don't want to be spilling our guts (i-vars), secondly, MRI backend's
210
- # #inspect() call on its @backend i-var will bump @backend's iter level while possibly yielding GVL
211
308
  def inspect
212
- id_str = (object_id << 1).to_s(16).rjust(DEFAULT_OBJ_ID_STR_WIDTH, '0')
213
- "#<#{self.class.name}:0x#{id_str} entries=#{size} default_proc=#{@default_proc.inspect}>"
309
+ format '%s entries=%d default_proc=%s>', to_s[0..-2], size.to_s, @default_proc.inspect
214
310
  end
215
311
 
216
312
  private
313
+
217
314
  def raise_fetch_no_key
218
315
  raise KeyError, 'key not found'
219
316
  end
@@ -224,7 +321,7 @@ module Concurrent
224
321
  end
225
322
 
226
323
  def populate_from(hash)
227
- hash.each_pair {|k, v| self[k] = v}
324
+ hash.each_pair { |k, v| self[k] = v }
228
325
  self
229
326
  end
230
327
 
@@ -108,7 +108,7 @@ module Concurrent
108
108
  # Indicates that the given attribute has not been set.
109
109
  # When `Just` the {#nothing} getter will return `NONE`.
110
110
  # When `Nothing` the {#just} getter will return `NONE`.
111
- NONE = Object.new.freeze
111
+ NONE = ::Object.new.freeze
112
112
 
113
113
  # The value of a `Maybe` when `Just`. Will be `NONE` when `Nothing`.
114
114
  attr_reader :just
@@ -10,7 +10,7 @@ module Concurrent
10
10
  module MutableStruct
11
11
  include Synchronization::AbstractStruct
12
12
 
13
- # @!macro [new] struct_new
13
+ # @!macro struct_new
14
14
  #
15
15
  # Factory for creating new struct classes.
16
16
  #
@@ -42,7 +42,7 @@ module Concurrent
42
42
  #
43
43
  # @see http://ruby-doc.org/core-2.2.0/Struct.html#method-c-new Ruby standard library `Struct#new`
44
44
 
45
- # @!macro [attach] struct_values
45
+ # @!macro struct_values
46
46
  #
47
47
  # Returns the values for this struct as an Array.
48
48
  #
@@ -53,7 +53,7 @@ module Concurrent
53
53
  end
54
54
  alias_method :to_a, :values
55
55
 
56
- # @!macro [attach] struct_values_at
56
+ # @!macro struct_values_at
57
57
  #
58
58
  # Returns the struct member values for each selector as an Array.
59
59
  #
@@ -64,7 +64,7 @@ module Concurrent
64
64
  synchronize { ns_values_at(indexes) }
65
65
  end
66
66
 
67
- # @!macro [attach] struct_inspect
67
+ # @!macro struct_inspect
68
68
  #
69
69
  # Describe the contents of this struct in a string.
70
70
  #
@@ -74,7 +74,7 @@ module Concurrent
74
74
  end
75
75
  alias_method :to_s, :inspect
76
76
 
77
- # @!macro [attach] struct_merge
77
+ # @!macro struct_merge
78
78
  #
79
79
  # Returns a new struct containing the contents of `other` and the contents
80
80
  # of `self`. If no block is specified, the value for entries with duplicate
@@ -95,7 +95,7 @@ module Concurrent
95
95
  synchronize { ns_merge(other, &block) }
96
96
  end
97
97
 
98
- # @!macro [attach] struct_to_h
98
+ # @!macro struct_to_h
99
99
  #
100
100
  # Returns a hash containing the names and values for the struct’s members.
101
101
  #
@@ -104,11 +104,11 @@ module Concurrent
104
104
  synchronize { ns_to_h }
105
105
  end
106
106
 
107
- # @!macro [attach] struct_get
107
+ # @!macro struct_get
108
108
  #
109
109
  # Attribute Reference
110
110
  #
111
- # @param [Symbol, String, Integer] member the string or symbol name of the memeber
111
+ # @param [Symbol, String, Integer] member the string or symbol name of the member
112
112
  # for which to obtain the value or the member's index
113
113
  #
114
114
  # @return [Object] the value of the given struct member or the member at the given index.
@@ -119,7 +119,7 @@ module Concurrent
119
119
  synchronize { ns_get(member) }
120
120
  end
121
121
 
122
- # @!macro [attach] struct_equality
122
+ # @!macro struct_equality
123
123
  #
124
124
  # Equality
125
125
  #
@@ -129,7 +129,7 @@ module Concurrent
129
129
  synchronize { ns_equality(other) }
130
130
  end
131
131
 
132
- # @!macro [attach] struct_each
132
+ # @!macro struct_each
133
133
  #
134
134
  # Yields the value of each struct member in order. If no block is given
135
135
  # an enumerator is returned.
@@ -141,7 +141,7 @@ module Concurrent
141
141
  synchronize { ns_each(&block) }
142
142
  end
143
143
 
144
- # @!macro [attach] struct_each_pair
144
+ # @!macro struct_each_pair
145
145
  #
146
146
  # Yields the name and value of each struct member in order. If no block is
147
147
  # given an enumerator is returned.
@@ -154,7 +154,7 @@ module Concurrent
154
154
  synchronize { ns_each_pair(&block) }
155
155
  end
156
156
 
157
- # @!macro [attach] struct_select
157
+ # @!macro struct_select
158
158
  #
159
159
  # Yields each member value from the struct to the block and returns an Array
160
160
  # containing the member values from the struct for which the given block
@@ -169,13 +169,13 @@ module Concurrent
169
169
  synchronize { ns_select(&block) }
170
170
  end
171
171
 
172
- # @!macro [new] struct_set
172
+ # @!macro struct_set
173
173
  #
174
174
  # Attribute Assignment
175
175
  #
176
176
  # Sets the value of the given struct member or the member at the given index.
177
177
  #
178
- # @param [Symbol, String, Integer] member the string or symbol name of the memeber
178
+ # @param [Symbol, String, Integer] member the string or symbol name of the member
179
179
  # for which to obtain the value or the member's index
180
180
  #
181
181
  # @return [Object] the value of the given struct member or the member at the given index.
@@ -212,6 +212,7 @@ module Concurrent
212
212
  synchronize do
213
213
  clazz = Synchronization::AbstractStruct.define_struct_class(MutableStruct, Synchronization::LockableObject, name, members, &block)
214
214
  members.each_with_index do |member, index|
215
+ clazz.send :remove_method, member
215
216
  clazz.send(:define_method, member) do
216
217
  synchronize { @values[index] }
217
218
  end
@@ -40,11 +40,11 @@ module Concurrent
40
40
  safe_initialization!
41
41
 
42
42
  # Unique value that represents that an `MVar` was empty
43
- EMPTY = Object.new
43
+ EMPTY = ::Object.new
44
44
 
45
45
  # Unique value that represents that an `MVar` timed out before it was able
46
46
  # to produce a value.
47
- TIMEOUT = Object.new
47
+ TIMEOUT = ::Object.new
48
48
 
49
49
  # Create a new `MVar`, either empty or with an initial value.
50
50
  #
@@ -161,15 +161,15 @@ module Concurrent
161
161
  # receive the rejection `reason` as the rejection callable parameter:
162
162
  #
163
163
  # ```ruby
164
- # p = [ Concurrent::Promise.execute{ Thread.pass; raise StandardError } ]
164
+ # p = Concurrent::Promise.execute { Thread.pass; raise StandardError }
165
165
  #
166
- # c1 = p.then(Proc.new{ |reason| 42 })
167
- # c2 = p.then(Proc.new{ |reason| raise 'Boom!' })
166
+ # c1 = p.then(-> reason { 42 })
167
+ # c2 = p.then(-> reason { raise 'Boom!' })
168
168
  #
169
- # sleep(0.1)
170
- #
171
- # c1.state #=> :rejected
172
- # c2.state #=> :rejected
169
+ # c1.wait.state #=> :fulfilled
170
+ # c1.value #=> 45
171
+ # c2.wait.state #=> :rejected
172
+ # c2.reason #=> #<RuntimeError: Boom!>
173
173
  # ```
174
174
  #
175
175
  # Once a promise is rejected it will continue to accept children that will
@@ -193,7 +193,7 @@ module Concurrent
193
193
  #
194
194
  # @!macro executor_and_deref_options
195
195
  #
196
- # @!macro [attach] promise_init_options
196
+ # @!macro promise_init_options
197
197
  #
198
198
  # @option opts [Promise] :parent the parent `Promise` when building a chain/tree
199
199
  # @option opts [Proc] :on_fulfill fulfillment handler
@@ -298,16 +298,28 @@ module Concurrent
298
298
 
299
299
  # Chain a new promise off the current promise.
300
300
  #
301
- # @param [Proc] rescuer An optional rescue block to be executed if the
302
- # promise is rejected.
303
- #
304
- # @param [ThreadPool] executor An optional thread pool executor to be used
305
- # in the new Promise
306
- #
307
- # @yield The block operation to be performed asynchronously.
308
- #
309
301
  # @return [Promise] the new promise
310
- def then(rescuer = nil, executor = @executor, &block)
302
+ # @yield The block operation to be performed asynchronously.
303
+ # @overload then(rescuer, executor, &block)
304
+ # @param [Proc] rescuer An optional rescue block to be executed if the
305
+ # promise is rejected.
306
+ # @param [ThreadPool] executor An optional thread pool executor to be used
307
+ # in the new Promise
308
+ # @overload then(rescuer, executor: executor, &block)
309
+ # @param [Proc] rescuer An optional rescue block to be executed if the
310
+ # promise is rejected.
311
+ # @param [ThreadPool] executor An optional thread pool executor to be used
312
+ # in the new Promise
313
+ def then(*args, &block)
314
+ if args.last.is_a?(::Hash)
315
+ executor = args.pop[:executor]
316
+ rescuer = args.first
317
+ else
318
+ rescuer, executor = args
319
+ end
320
+
321
+ executor ||= @executor
322
+
311
323
  raise ArgumentError.new('rescuers and block are both missing') if rescuer.nil? && !block_given?
312
324
  block = Proc.new { |result| result } unless block_given?
313
325
  child = Promise.new(
@@ -383,11 +395,24 @@ module Concurrent
383
395
  # Builds a promise that produces the result of promises in an Array
384
396
  # and fails if any of them fails.
385
397
  #
386
- # @param [Array<Promise>] promises
398
+ # @overload zip(*promises)
399
+ # @param [Array<Promise>] promises
400
+ #
401
+ # @overload zip(*promises, opts)
402
+ # @param [Array<Promise>] promises
403
+ # @param [Hash] opts the configuration options
404
+ # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance.
405
+ # @option opts [Boolean] :execute (true) execute promise before returning
387
406
  #
388
407
  # @return [Promise<Array>]
389
408
  def self.zip(*promises)
390
- zero = fulfill([], executor: ImmediateExecutor.new)
409
+ opts = promises.last.is_a?(::Hash) ? promises.pop.dup : {}
410
+ opts[:executor] ||= ImmediateExecutor.new
411
+ zero = if !opts.key?(:execute) || opts.delete(:execute)
412
+ fulfill([], opts)
413
+ else
414
+ Promise.new(opts) { [] }
415
+ end
391
416
 
392
417
  promises.reduce(zero) do |p1, p2|
393
418
  p1.flat_map do |results|
@@ -401,7 +426,14 @@ module Concurrent
401
426
  # Builds a promise that produces the result of self and others in an Array
402
427
  # and fails if any of them fails.
403
428
  #
404
- # @param [Array<Promise>] others
429
+ # @overload zip(*promises)
430
+ # @param [Array<Promise>] others
431
+ #
432
+ # @overload zip(*promises, opts)
433
+ # @param [Array<Promise>] others
434
+ # @param [Hash] opts the configuration options
435
+ # @option opts [Executor] :executor (ImmediateExecutor.new) when set use the given `Executor` instance.
436
+ # @option opts [Boolean] :execute (true) execute promise before returning
405
437
  #
406
438
  # @return [Promise<Array>]
407
439
  def zip(*others)
@@ -414,7 +446,7 @@ module Concurrent
414
446
  # fail. Upon execution will execute any of the aggregate promises that
415
447
  # were not already executed.
416
448
  #
417
- # @!macro [attach] promise_self_aggregate
449
+ # @!macro promise_self_aggregate
418
450
  #
419
451
  # The returned promise will not yet have been executed. Additional `#then`
420
452
  # and `#rescue` handlers may still be provided. Once the returned promise