concurrent-ruby 1.1.10 → 1.3.3

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 (97) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +47 -1
  3. data/Gemfile +1 -2
  4. data/README.md +23 -20
  5. data/Rakefile +75 -65
  6. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +10 -25
  7. data/lib/concurrent-ruby/concurrent/agent.rb +2 -1
  8. data/lib/concurrent-ruby/concurrent/array.rb +3 -13
  9. data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
  10. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +5 -4
  11. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +5 -4
  12. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +3 -0
  13. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +81 -151
  14. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +1 -1
  15. data/lib/concurrent-ruby/concurrent/atomic/event.rb +1 -1
  16. data/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb +109 -0
  17. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +1 -0
  18. data/lib/concurrent-ruby/concurrent/atomic/locals.rb +189 -0
  19. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +28 -0
  20. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +11 -5
  21. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +11 -5
  22. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +1 -1
  23. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +1 -1
  24. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +2 -1
  25. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +5 -3
  26. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +6 -9
  27. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +96 -89
  28. data/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb +37 -0
  29. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +15 -4
  30. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +1 -1
  31. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +1 -1
  32. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +2 -0
  33. data/lib/concurrent-ruby/concurrent/collection/map/mri_map_backend.rb +2 -2
  34. data/lib/concurrent-ruby/concurrent/collection/map/non_concurrent_map_backend.rb +16 -8
  35. data/lib/concurrent-ruby/concurrent/collection/map/synchronized_map_backend.rb +23 -20
  36. data/lib/concurrent-ruby/concurrent/concern/logging.rb +86 -2
  37. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  38. data/lib/concurrent-ruby/concurrent/configuration.rb +4 -87
  39. data/lib/concurrent-ruby/concurrent/delay.rb +2 -2
  40. data/lib/concurrent-ruby/concurrent/errors.rb +5 -0
  41. data/lib/concurrent-ruby/concurrent/exchanger.rb +1 -0
  42. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +1 -1
  43. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +4 -0
  44. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +6 -9
  45. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +5 -0
  46. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +7 -0
  47. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +1 -1
  48. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
  49. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
  50. data/lib/concurrent-ruby/concurrent/executor/timer_set.rb +6 -2
  51. data/lib/concurrent-ruby/concurrent/hash.rb +5 -12
  52. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +1 -1
  53. data/lib/concurrent-ruby/concurrent/ivar.rb +2 -1
  54. data/lib/concurrent-ruby/concurrent/map.rb +43 -39
  55. data/lib/concurrent-ruby/concurrent/maybe.rb +1 -1
  56. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +1 -1
  57. data/lib/concurrent-ruby/concurrent/mvar.rb +1 -1
  58. data/lib/concurrent-ruby/concurrent/promise.rb +1 -1
  59. data/lib/concurrent-ruby/concurrent/promises.rb +40 -29
  60. data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
  61. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +1 -1
  62. data/lib/concurrent-ruby/concurrent/set.rb +0 -10
  63. data/lib/concurrent-ruby/concurrent/settable_struct.rb +2 -2
  64. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +5 -1
  65. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +1 -3
  66. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +2 -0
  67. data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
  68. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +3 -1
  69. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +2 -0
  70. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +5 -2
  71. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +6 -5
  72. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +12 -44
  73. data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
  74. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +77 -12
  75. data/lib/concurrent-ruby/concurrent/synchronization.rb +1 -18
  76. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +36 -39
  77. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +1 -37
  78. data/lib/concurrent-ruby/concurrent/timer_task.rb +59 -9
  79. data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
  80. data/lib/concurrent-ruby/concurrent/tvar.rb +2 -1
  81. data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
  82. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +3 -74
  83. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
  84. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
  85. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +118 -58
  86. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  87. metadata +13 -17
  88. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
  89. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
  90. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
  91. data/lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb +0 -927
  92. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
  93. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
  94. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -71
  95. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
  96. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
  97. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +0 -118
@@ -1,6 +1,8 @@
1
- require 'concurrent/synchronization'
2
- require 'concurrent/utility/engine'
1
+ require 'concurrent/utility/native_extension_loader' # load native parts first
2
+
3
+ require 'concurrent/atomic_reference/atomic_direct_update'
3
4
  require 'concurrent/atomic_reference/numeric_cas_wrapper'
5
+ require 'concurrent/atomic_reference/mutex_atomic'
4
6
 
5
7
  # Shim for TruffleRuby::AtomicReference
6
8
  if Concurrent.on_truffleruby? && !defined?(TruffleRuby::AtomicReference)
@@ -12,138 +14,6 @@ end
12
14
 
13
15
  module Concurrent
14
16
 
15
- # Define update methods that use direct paths
16
- #
17
- # @!visibility private
18
- # @!macro internal_implementation_note
19
- module AtomicDirectUpdate
20
-
21
- # @!macro atomic_reference_method_update
22
- #
23
- # Pass the current value to the given block, replacing it
24
- # with the block's result. May retry if the value changes
25
- # during the block's execution.
26
- #
27
- # @yield [Object] Calculate a new value for the atomic reference using
28
- # given (old) value
29
- # @yieldparam [Object] old_value the starting value of the atomic reference
30
- # @return [Object] the new value
31
- def update
32
- true until compare_and_set(old_value = get, new_value = yield(old_value))
33
- new_value
34
- end
35
-
36
- # @!macro atomic_reference_method_try_update
37
- #
38
- # Pass the current value to the given block, replacing it
39
- # with the block's result. Return nil if the update fails.
40
- #
41
- # @yield [Object] Calculate a new value for the atomic reference using
42
- # given (old) value
43
- # @yieldparam [Object] old_value the starting value of the atomic reference
44
- # @note This method was altered to avoid raising an exception by default.
45
- # Instead, this method now returns `nil` in case of failure. For more info,
46
- # please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
47
- # @return [Object] the new value, or nil if update failed
48
- def try_update
49
- old_value = get
50
- new_value = yield old_value
51
-
52
- return unless compare_and_set old_value, new_value
53
-
54
- new_value
55
- end
56
-
57
- # @!macro atomic_reference_method_try_update!
58
- #
59
- # Pass the current value to the given block, replacing it
60
- # with the block's result. Raise an exception if the update
61
- # fails.
62
- #
63
- # @yield [Object] Calculate a new value for the atomic reference using
64
- # given (old) value
65
- # @yieldparam [Object] old_value the starting value of the atomic reference
66
- # @note This behavior mimics the behavior of the original
67
- # `AtomicReference#try_update` API. The reason this was changed was to
68
- # avoid raising exceptions (which are inherently slow) by default. For more
69
- # info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
70
- # @return [Object] the new value
71
- # @raise [Concurrent::ConcurrentUpdateError] if the update fails
72
- def try_update!
73
- old_value = get
74
- new_value = yield old_value
75
- unless compare_and_set(old_value, new_value)
76
- if $VERBOSE
77
- raise ConcurrentUpdateError, "Update failed"
78
- else
79
- raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE
80
- end
81
- end
82
- new_value
83
- end
84
- end
85
-
86
- require 'concurrent/atomic_reference/mutex_atomic'
87
-
88
- # @!macro atomic_reference
89
- #
90
- # An object reference that may be updated atomically. All read and write
91
- # operations have java volatile semantic.
92
- #
93
- # @!macro thread_safe_variable_comparison
94
- #
95
- # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
96
- # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
97
- #
98
- # @!method initialize(value = nil)
99
- # @!macro atomic_reference_method_initialize
100
- # @param [Object] value The initial value.
101
- #
102
- # @!method get
103
- # @!macro atomic_reference_method_get
104
- # Gets the current value.
105
- # @return [Object] the current value
106
- #
107
- # @!method set(new_value)
108
- # @!macro atomic_reference_method_set
109
- # Sets to the given value.
110
- # @param [Object] new_value the new value
111
- # @return [Object] the new value
112
- #
113
- # @!method get_and_set(new_value)
114
- # @!macro atomic_reference_method_get_and_set
115
- # Atomically sets to the given value and returns the old value.
116
- # @param [Object] new_value the new value
117
- # @return [Object] the old value
118
- #
119
- # @!method compare_and_set(old_value, new_value)
120
- # @!macro atomic_reference_method_compare_and_set
121
- #
122
- # Atomically sets the value to the given updated value if
123
- # the current value == the expected value.
124
- #
125
- # @param [Object] old_value the expected value
126
- # @param [Object] new_value the new value
127
- #
128
- # @return [Boolean] `true` if successful. A `false` return indicates
129
- # that the actual value was not equal to the expected value.
130
- #
131
- # @!method update
132
- # @!macro atomic_reference_method_update
133
- #
134
- # @!method try_update
135
- # @!macro atomic_reference_method_try_update
136
- #
137
- # @!method try_update!
138
- # @!macro atomic_reference_method_try_update!
139
-
140
-
141
- # @!macro internal_implementation_note
142
- class ConcurrentUpdateError < ThreadError
143
- # frozen pre-allocated backtrace to speed ConcurrentUpdateError
144
- CONC_UP_ERR_BACKTRACE = ['backtrace elided; set verbose to enable'].freeze
145
- end
146
-
147
17
  # @!macro internal_implementation_note
148
18
  AtomicReferenceImplementation = case
149
19
  when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
@@ -171,28 +41,88 @@ module Concurrent
171
41
  alias_method :swap, :get_and_set
172
42
  end
173
43
  TruffleRubyAtomicReference
174
- when Concurrent.on_rbx?
175
- # @note Extends `Rubinius::AtomicReference` version adding aliases
176
- # and numeric logic.
177
- #
178
- # @!visibility private
179
- # @!macro internal_implementation_note
180
- class RbxAtomicReference < Rubinius::AtomicReference
181
- alias_method :_compare_and_set, :compare_and_set
182
- include AtomicDirectUpdate
183
- include AtomicNumericCompareAndSetWrapper
184
- alias_method :value, :get
185
- alias_method :value=, :set
186
- alias_method :swap, :get_and_set
187
- alias_method :compare_and_swap, :compare_and_set
188
- end
189
- RbxAtomicReference
190
44
  else
191
45
  MutexAtomicReference
192
46
  end
193
47
  private_constant :AtomicReferenceImplementation
194
48
 
195
- # @!macro atomic_reference
49
+ # An object reference that may be updated atomically. All read and write
50
+ # operations have java volatile semantic.
51
+ #
52
+ # @!macro thread_safe_variable_comparison
53
+ #
54
+ # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/AtomicReference.html
55
+ # @see http://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
56
+ #
57
+ # @!method initialize(value = nil)
58
+ # @!macro atomic_reference_method_initialize
59
+ # @param [Object] value The initial value.
60
+ #
61
+ # @!method get
62
+ # @!macro atomic_reference_method_get
63
+ # Gets the current value.
64
+ # @return [Object] the current value
65
+ #
66
+ # @!method set(new_value)
67
+ # @!macro atomic_reference_method_set
68
+ # Sets to the given value.
69
+ # @param [Object] new_value the new value
70
+ # @return [Object] the new value
71
+ #
72
+ # @!method get_and_set(new_value)
73
+ # @!macro atomic_reference_method_get_and_set
74
+ # Atomically sets to the given value and returns the old value.
75
+ # @param [Object] new_value the new value
76
+ # @return [Object] the old value
77
+ #
78
+ # @!method compare_and_set(old_value, new_value)
79
+ # @!macro atomic_reference_method_compare_and_set
80
+ #
81
+ # Atomically sets the value to the given updated value if
82
+ # the current value == the expected value.
83
+ #
84
+ # @param [Object] old_value the expected value
85
+ # @param [Object] new_value the new value
86
+ #
87
+ # @return [Boolean] `true` if successful. A `false` return indicates
88
+ # that the actual value was not equal to the expected value.
89
+ #
90
+ # @!method update
91
+ # Pass the current value to the given block, replacing it
92
+ # with the block's result. May retry if the value changes
93
+ # during the block's execution.
94
+ #
95
+ # @yield [Object] Calculate a new value for the atomic reference using
96
+ # given (old) value
97
+ # @yieldparam [Object] old_value the starting value of the atomic reference
98
+ # @return [Object] the new value
99
+ #
100
+ # @!method try_update
101
+ # Pass the current value to the given block, replacing it
102
+ # with the block's result. Return nil if the update fails.
103
+ #
104
+ # @yield [Object] Calculate a new value for the atomic reference using
105
+ # given (old) value
106
+ # @yieldparam [Object] old_value the starting value of the atomic reference
107
+ # @note This method was altered to avoid raising an exception by default.
108
+ # Instead, this method now returns `nil` in case of failure. For more info,
109
+ # please see: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
110
+ # @return [Object] the new value, or nil if update failed
111
+ #
112
+ # @!method try_update!
113
+ # Pass the current value to the given block, replacing it
114
+ # with the block's result. Raise an exception if the update
115
+ # fails.
116
+ #
117
+ # @yield [Object] Calculate a new value for the atomic reference using
118
+ # given (old) value
119
+ # @yieldparam [Object] old_value the starting value of the atomic reference
120
+ # @note This behavior mimics the behavior of the original
121
+ # `AtomicReference#try_update` API. The reason this was changed was to
122
+ # avoid raising exceptions (which are inherently slow) by default. For more
123
+ # info: https://github.com/ruby-concurrency/concurrent-ruby/pull/336
124
+ # @return [Object] the new value
125
+ # @raise [Concurrent::ConcurrentUpdateError] if the update fails
196
126
  class AtomicReference < AtomicReferenceImplementation
197
127
 
198
128
  # @return [String] Short string representation.
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/lockable_object'
2
2
  require 'concurrent/utility/native_integer'
3
3
 
4
4
  module Concurrent
@@ -1,5 +1,5 @@
1
1
  require 'thread'
2
- require 'concurrent/synchronization'
2
+ require 'concurrent/synchronization/lockable_object'
3
3
 
4
4
  module Concurrent
5
5
 
@@ -0,0 +1,109 @@
1
+ require 'concurrent/constants'
2
+ require_relative 'locals'
3
+
4
+ module Concurrent
5
+
6
+ # A `FiberLocalVar` is a variable where the value is different for each fiber.
7
+ # Each variable may have a default value, but when you modify the variable only
8
+ # the current fiber will ever see that change.
9
+ #
10
+ # This is similar to Ruby's built-in fiber-local variables (`Thread.current[:name]`),
11
+ # but with these major advantages:
12
+ # * `FiberLocalVar` has its own identity, it doesn't need a Symbol.
13
+ # * Each Ruby's built-in fiber-local variable leaks some memory forever (it's a Symbol held forever on the fiber),
14
+ # so it's only OK to create a small amount of them.
15
+ # `FiberLocalVar` has no such issue and it is fine to create many of them.
16
+ # * Ruby's built-in fiber-local variables leak forever the value set on each fiber (unless set to nil explicitly).
17
+ # `FiberLocalVar` automatically removes the mapping for each fiber once the `FiberLocalVar` instance is GC'd.
18
+ #
19
+ # @example
20
+ # v = FiberLocalVar.new(14)
21
+ # v.value #=> 14
22
+ # v.value = 2
23
+ # v.value #=> 2
24
+ #
25
+ # @example
26
+ # v = FiberLocalVar.new(14)
27
+ #
28
+ # Fiber.new do
29
+ # v.value #=> 14
30
+ # v.value = 1
31
+ # v.value #=> 1
32
+ # end.resume
33
+ #
34
+ # Fiber.new do
35
+ # v.value #=> 14
36
+ # v.value = 2
37
+ # v.value #=> 2
38
+ # end.resume
39
+ #
40
+ # v.value #=> 14
41
+ class FiberLocalVar
42
+ LOCALS = FiberLocals.new
43
+
44
+ # Creates a fiber local variable.
45
+ #
46
+ # @param [Object] default the default value when otherwise unset
47
+ # @param [Proc] default_block Optional block that gets called to obtain the
48
+ # default value for each fiber
49
+ def initialize(default = nil, &default_block)
50
+ if default && block_given?
51
+ raise ArgumentError, "Cannot use both value and block as default value"
52
+ end
53
+
54
+ if block_given?
55
+ @default_block = default_block
56
+ @default = nil
57
+ else
58
+ @default_block = nil
59
+ @default = default
60
+ end
61
+
62
+ @index = LOCALS.next_index(self)
63
+ end
64
+
65
+ # Returns the value in the current fiber's copy of this fiber-local variable.
66
+ #
67
+ # @return [Object] the current value
68
+ def value
69
+ LOCALS.fetch(@index) { default }
70
+ end
71
+
72
+ # Sets the current fiber's copy of this fiber-local variable to the specified value.
73
+ #
74
+ # @param [Object] value the value to set
75
+ # @return [Object] the new value
76
+ def value=(value)
77
+ LOCALS.set(@index, value)
78
+ end
79
+
80
+ # Bind the given value to fiber local storage during
81
+ # execution of the given block.
82
+ #
83
+ # @param [Object] value the value to bind
84
+ # @yield the operation to be performed with the bound variable
85
+ # @return [Object] the value
86
+ def bind(value)
87
+ if block_given?
88
+ old_value = self.value
89
+ self.value = value
90
+ begin
91
+ yield
92
+ ensure
93
+ self.value = old_value
94
+ end
95
+ end
96
+ end
97
+
98
+ protected
99
+
100
+ # @!visibility private
101
+ def default
102
+ if @default_block
103
+ self.value = @default_block.call
104
+ else
105
+ @default
106
+ end
107
+ end
108
+ end
109
+ end
@@ -1,4 +1,5 @@
1
1
  if Concurrent.on_jruby?
2
+ require 'concurrent/utility/native_extension_loader'
2
3
 
3
4
  module Concurrent
4
5
 
@@ -0,0 +1,189 @@
1
+ require 'fiber'
2
+ require 'concurrent/utility/engine'
3
+ require 'concurrent/constants'
4
+
5
+ module Concurrent
6
+ # @!visibility private
7
+ # @!macro internal_implementation_note
8
+ #
9
+ # An abstract implementation of local storage, with sub-classes for
10
+ # per-thread and per-fiber locals.
11
+ #
12
+ # Each execution context (EC, thread or fiber) has a lazily initialized array
13
+ # of local variable values. Each time a new local variable is created, we
14
+ # allocate an "index" for it.
15
+ #
16
+ # For example, if the allocated index is 1, that means slot #1 in EVERY EC's
17
+ # locals array will be used for the value of that variable.
18
+ #
19
+ # The good thing about using a per-EC structure to hold values, rather than
20
+ # a global, is that no synchronization is needed when reading and writing
21
+ # those values (since the structure is only ever accessed by a single
22
+ # thread).
23
+ #
24
+ # Of course, when a local variable is GC'd, 1) we need to recover its index
25
+ # for use by other new local variables (otherwise the locals arrays could
26
+ # get bigger and bigger with time), and 2) we need to null out all the
27
+ # references held in the now-unused slots (both to avoid blocking GC of those
28
+ # objects, and also to prevent "stale" values from being passed on to a new
29
+ # local when the index is reused).
30
+ #
31
+ # Because we need to null out freed slots, we need to keep references to
32
+ # ALL the locals arrays, so we can null out the appropriate slots in all of
33
+ # them. This is why we need to use a finalizer to clean up the locals array
34
+ # when the EC goes out of scope.
35
+ class AbstractLocals
36
+ def initialize
37
+ @free = []
38
+ @lock = Mutex.new
39
+ @all_arrays = {}
40
+ @next = 0
41
+ end
42
+
43
+ def synchronize
44
+ @lock.synchronize { yield }
45
+ end
46
+
47
+ if Concurrent.on_cruby?
48
+ def weak_synchronize
49
+ yield
50
+ end
51
+ else
52
+ alias_method :weak_synchronize, :synchronize
53
+ end
54
+
55
+ def next_index(local)
56
+ index = synchronize do
57
+ if @free.empty?
58
+ @next += 1
59
+ else
60
+ @free.pop
61
+ end
62
+ end
63
+
64
+ # When the local goes out of scope, we should free the associated index
65
+ # and all values stored into it.
66
+ ObjectSpace.define_finalizer(local, local_finalizer(index))
67
+
68
+ index
69
+ end
70
+
71
+ def free_index(index)
72
+ weak_synchronize do
73
+ # The cost of GC'ing a TLV is linear in the number of ECs using local
74
+ # variables. But that is natural! More ECs means more storage is used
75
+ # per local variable. So naturally more CPU time is required to free
76
+ # more storage.
77
+ #
78
+ # DO NOT use each_value which might conflict with new pair assignment
79
+ # into the hash in #set method.
80
+ @all_arrays.values.each do |locals|
81
+ locals[index] = nil
82
+ end
83
+
84
+ # free index has to be published after the arrays are cleared:
85
+ @free << index
86
+ end
87
+ end
88
+
89
+ def fetch(index)
90
+ locals = self.locals
91
+ value = locals ? locals[index] : nil
92
+
93
+ if nil == value
94
+ yield
95
+ elsif NULL.equal?(value)
96
+ nil
97
+ else
98
+ value
99
+ end
100
+ end
101
+
102
+ def set(index, value)
103
+ locals = self.locals!
104
+ locals[index] = (nil == value ? NULL : value)
105
+
106
+ value
107
+ end
108
+
109
+ private
110
+
111
+ # When the local goes out of scope, clean up that slot across all locals currently assigned.
112
+ def local_finalizer(index)
113
+ proc do
114
+ free_index(index)
115
+ end
116
+ end
117
+
118
+ # When a thread/fiber goes out of scope, remove the array from @all_arrays.
119
+ def thread_fiber_finalizer(array_object_id)
120
+ proc do
121
+ weak_synchronize do
122
+ @all_arrays.delete(array_object_id)
123
+ end
124
+ end
125
+ end
126
+
127
+ # Returns the locals for the current scope, or nil if none exist.
128
+ def locals
129
+ raise NotImplementedError
130
+ end
131
+
132
+ # Returns the locals for the current scope, creating them if necessary.
133
+ def locals!
134
+ raise NotImplementedError
135
+ end
136
+ end
137
+
138
+ # @!visibility private
139
+ # @!macro internal_implementation_note
140
+ # An array-backed storage of indexed variables per thread.
141
+ class ThreadLocals < AbstractLocals
142
+ def locals
143
+ Thread.current.thread_variable_get(:concurrent_thread_locals)
144
+ end
145
+
146
+ def locals!
147
+ thread = Thread.current
148
+ locals = thread.thread_variable_get(:concurrent_thread_locals)
149
+
150
+ unless locals
151
+ locals = thread.thread_variable_set(:concurrent_thread_locals, [])
152
+ weak_synchronize do
153
+ @all_arrays[locals.object_id] = locals
154
+ end
155
+ # When the thread goes out of scope, we should delete the associated locals:
156
+ ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id))
157
+ end
158
+
159
+ locals
160
+ end
161
+ end
162
+
163
+ # @!visibility private
164
+ # @!macro internal_implementation_note
165
+ # An array-backed storage of indexed variables per fiber.
166
+ class FiberLocals < AbstractLocals
167
+ def locals
168
+ Thread.current[:concurrent_fiber_locals]
169
+ end
170
+
171
+ def locals!
172
+ thread = Thread.current
173
+ locals = thread[:concurrent_fiber_locals]
174
+
175
+ unless locals
176
+ locals = thread[:concurrent_fiber_locals] = []
177
+ weak_synchronize do
178
+ @all_arrays[locals.object_id] = locals
179
+ end
180
+ # When the fiber goes out of scope, we should delete the associated locals:
181
+ ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id))
182
+ end
183
+
184
+ locals
185
+ end
186
+ end
187
+
188
+ private_constant :AbstractLocals, :ThreadLocals, :FiberLocals
189
+ end
@@ -0,0 +1,28 @@
1
+ require 'concurrent/utility/engine'
2
+ require_relative 'fiber_local_var'
3
+ require_relative 'thread_local_var'
4
+
5
+ module Concurrent
6
+ # @!visibility private
7
+ def self.mutex_owned_per_thread?
8
+ return false if Concurrent.on_jruby? || Concurrent.on_truffleruby?
9
+
10
+ mutex = Mutex.new
11
+ # Lock the mutex:
12
+ mutex.synchronize do
13
+ # Check if the mutex is still owned in a child fiber:
14
+ Fiber.new { mutex.owned? }.resume
15
+ end
16
+ end
17
+
18
+ if mutex_owned_per_thread?
19
+ LockLocalVar = ThreadLocalVar
20
+ else
21
+ LockLocalVar = FiberLocalVar
22
+ end
23
+
24
+ # Either {FiberLocalVar} or {ThreadLocalVar} depending on whether Mutex (and Monitor)
25
+ # are held, respectively, per Fiber or per Thread.
26
+ class LockLocalVar
27
+ end
28
+ end
@@ -1,16 +1,18 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/safe_initialization'
2
2
 
3
3
  module Concurrent
4
4
 
5
5
  # @!macro atomic_boolean
6
6
  # @!visibility private
7
7
  # @!macro internal_implementation_note
8
- class MutexAtomicBoolean < Synchronization::LockableObject
8
+ class MutexAtomicBoolean
9
+ extend Concurrent::Synchronization::SafeInitialization
9
10
 
10
11
  # @!macro atomic_boolean_method_initialize
11
12
  def initialize(initial = false)
12
13
  super()
13
- synchronize { ns_initialize(initial) }
14
+ @Lock = ::Mutex.new
15
+ @value = !!initial
14
16
  end
15
17
 
16
18
  # @!macro atomic_boolean_method_value_get
@@ -46,8 +48,12 @@ module Concurrent
46
48
  protected
47
49
 
48
50
  # @!visibility private
49
- def ns_initialize(initial)
50
- @value = !!initial
51
+ def synchronize
52
+ if @Lock.owned?
53
+ yield
54
+ else
55
+ @Lock.synchronize { yield }
56
+ end
51
57
  end
52
58
 
53
59
  private
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/safe_initialization'
2
2
  require 'concurrent/utility/native_integer'
3
3
 
4
4
  module Concurrent
@@ -6,12 +6,14 @@ module Concurrent
6
6
  # @!macro atomic_fixnum
7
7
  # @!visibility private
8
8
  # @!macro internal_implementation_note
9
- class MutexAtomicFixnum < Synchronization::LockableObject
9
+ class MutexAtomicFixnum
10
+ extend Concurrent::Synchronization::SafeInitialization
10
11
 
11
12
  # @!macro atomic_fixnum_method_initialize
12
13
  def initialize(initial = 0)
13
14
  super()
14
- synchronize { ns_initialize(initial) }
15
+ @Lock = ::Mutex.new
16
+ ns_set(initial)
15
17
  end
16
18
 
17
19
  # @!macro atomic_fixnum_method_value_get
@@ -60,8 +62,12 @@ module Concurrent
60
62
  protected
61
63
 
62
64
  # @!visibility private
63
- def ns_initialize(initial)
64
- ns_set(initial)
65
+ def synchronize
66
+ if @Lock.owned?
67
+ yield
68
+ else
69
+ @Lock.synchronize { yield }
70
+ end
65
71
  end
66
72
 
67
73
  private
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/lockable_object'
2
2
  require 'concurrent/utility/native_integer'
3
3
 
4
4
  module Concurrent
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/lockable_object'
2
2
  require 'concurrent/utility/native_integer'
3
3
 
4
4
  module Concurrent