concurrent-ruby 1.1.10 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +11 -1
  3. data/Gemfile +0 -1
  4. data/README.md +21 -20
  5. data/Rakefile +46 -57
  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 +0 -10
  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 +188 -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/concern/logging.rb +86 -2
  34. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  35. data/lib/concurrent-ruby/concurrent/configuration.rb +4 -87
  36. data/lib/concurrent-ruby/concurrent/delay.rb +2 -2
  37. data/lib/concurrent-ruby/concurrent/errors.rb +5 -0
  38. data/lib/concurrent-ruby/concurrent/exchanger.rb +1 -0
  39. data/lib/concurrent-ruby/concurrent/executor/abstract_executor_service.rb +1 -1
  40. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +2 -2
  41. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +1 -1
  42. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
  43. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
  44. data/lib/concurrent-ruby/concurrent/hash.rb +0 -9
  45. data/lib/concurrent-ruby/concurrent/immutable_struct.rb +1 -1
  46. data/lib/concurrent-ruby/concurrent/ivar.rb +2 -1
  47. data/lib/concurrent-ruby/concurrent/map.rb +9 -8
  48. data/lib/concurrent-ruby/concurrent/maybe.rb +1 -1
  49. data/lib/concurrent-ruby/concurrent/mutable_struct.rb +1 -1
  50. data/lib/concurrent-ruby/concurrent/mvar.rb +1 -1
  51. data/lib/concurrent-ruby/concurrent/promise.rb +1 -1
  52. data/lib/concurrent-ruby/concurrent/promises.rb +7 -6
  53. data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
  54. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +1 -1
  55. data/lib/concurrent-ruby/concurrent/set.rb +0 -10
  56. data/lib/concurrent-ruby/concurrent/settable_struct.rb +2 -2
  57. data/lib/concurrent-ruby/concurrent/synchronization/abstract_lockable_object.rb +5 -1
  58. data/lib/concurrent-ruby/concurrent/synchronization/abstract_object.rb +1 -3
  59. data/lib/concurrent-ruby/concurrent/synchronization/condition.rb +2 -0
  60. data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
  61. data/lib/concurrent-ruby/concurrent/synchronization/jruby_lockable_object.rb +3 -1
  62. data/lib/concurrent-ruby/concurrent/synchronization/lock.rb +2 -0
  63. data/lib/concurrent-ruby/concurrent/synchronization/lockable_object.rb +5 -2
  64. data/lib/concurrent-ruby/concurrent/synchronization/mutex_lockable_object.rb +6 -5
  65. data/lib/concurrent-ruby/concurrent/synchronization/object.rb +12 -44
  66. data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
  67. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +77 -12
  68. data/lib/concurrent-ruby/concurrent/synchronization.rb +1 -18
  69. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +36 -39
  70. data/lib/concurrent-ruby/concurrent/thread_safe/util/cheap_lockable.rb +2 -39
  71. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +1 -37
  72. data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
  73. data/lib/concurrent-ruby/concurrent/tvar.rb +2 -1
  74. data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
  75. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +3 -74
  76. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
  77. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
  78. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +34 -54
  79. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  80. metadata +13 -15
  81. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
  82. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
  83. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
  84. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
  85. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
  86. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -71
  87. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
  88. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
@@ -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,188 @@
1
+ require 'concurrent/utility/engine'
2
+ require 'concurrent/constants'
3
+
4
+ module Concurrent
5
+ # @!visibility private
6
+ # @!macro internal_implementation_note
7
+ #
8
+ # An abstract implementation of local storage, with sub-classes for
9
+ # per-thread and per-fiber locals.
10
+ #
11
+ # Each execution context (EC, thread or fiber) has a lazily initialized array
12
+ # of local variable values. Each time a new local variable is created, we
13
+ # allocate an "index" for it.
14
+ #
15
+ # For example, if the allocated index is 1, that means slot #1 in EVERY EC's
16
+ # locals array will be used for the value of that variable.
17
+ #
18
+ # The good thing about using a per-EC structure to hold values, rather than
19
+ # a global, is that no synchronization is needed when reading and writing
20
+ # those values (since the structure is only ever accessed by a single
21
+ # thread).
22
+ #
23
+ # Of course, when a local variable is GC'd, 1) we need to recover its index
24
+ # for use by other new local variables (otherwise the locals arrays could
25
+ # get bigger and bigger with time), and 2) we need to null out all the
26
+ # references held in the now-unused slots (both to avoid blocking GC of those
27
+ # objects, and also to prevent "stale" values from being passed on to a new
28
+ # local when the index is reused).
29
+ #
30
+ # Because we need to null out freed slots, we need to keep references to
31
+ # ALL the locals arrays, so we can null out the appropriate slots in all of
32
+ # them. This is why we need to use a finalizer to clean up the locals array
33
+ # when the EC goes out of scope.
34
+ class AbstractLocals
35
+ def initialize
36
+ @free = []
37
+ @lock = Mutex.new
38
+ @all_arrays = {}
39
+ @next = 0
40
+ end
41
+
42
+ def synchronize
43
+ @lock.synchronize { yield }
44
+ end
45
+
46
+ if Concurrent.on_cruby?
47
+ def weak_synchronize
48
+ yield
49
+ end
50
+ else
51
+ alias_method :weak_synchronize, :synchronize
52
+ end
53
+
54
+ def next_index(local)
55
+ index = synchronize do
56
+ if @free.empty?
57
+ @next += 1
58
+ else
59
+ @free.pop
60
+ end
61
+ end
62
+
63
+ # When the local goes out of scope, we should free the associated index
64
+ # and all values stored into it.
65
+ ObjectSpace.define_finalizer(local, local_finalizer(index))
66
+
67
+ index
68
+ end
69
+
70
+ def free_index(index)
71
+ weak_synchronize do
72
+ # The cost of GC'ing a TLV is linear in the number of ECs using local
73
+ # variables. But that is natural! More ECs means more storage is used
74
+ # per local variable. So naturally more CPU time is required to free
75
+ # more storage.
76
+ #
77
+ # DO NOT use each_value which might conflict with new pair assignment
78
+ # into the hash in #set method.
79
+ @all_arrays.values.each do |locals|
80
+ locals[index] = nil
81
+ end
82
+
83
+ # free index has to be published after the arrays are cleared:
84
+ @free << index
85
+ end
86
+ end
87
+
88
+ def fetch(index)
89
+ locals = self.locals
90
+ value = locals ? locals[index] : nil
91
+
92
+ if nil == value
93
+ yield
94
+ elsif NULL.equal?(value)
95
+ nil
96
+ else
97
+ value
98
+ end
99
+ end
100
+
101
+ def set(index, value)
102
+ locals = self.locals!
103
+ locals[index] = (nil == value ? NULL : value)
104
+
105
+ value
106
+ end
107
+
108
+ private
109
+
110
+ # When the local goes out of scope, clean up that slot across all locals currently assigned.
111
+ def local_finalizer(index)
112
+ proc do
113
+ free_index(index)
114
+ end
115
+ end
116
+
117
+ # When a thread/fiber goes out of scope, remove the array from @all_arrays.
118
+ def thread_fiber_finalizer(array_object_id)
119
+ proc do
120
+ weak_synchronize do
121
+ @all_arrays.delete(array_object_id)
122
+ end
123
+ end
124
+ end
125
+
126
+ # Returns the locals for the current scope, or nil if none exist.
127
+ def locals
128
+ raise NotImplementedError
129
+ end
130
+
131
+ # Returns the locals for the current scope, creating them if necessary.
132
+ def locals!
133
+ raise NotImplementedError
134
+ end
135
+ end
136
+
137
+ # @!visibility private
138
+ # @!macro internal_implementation_note
139
+ # An array-backed storage of indexed variables per thread.
140
+ class ThreadLocals < AbstractLocals
141
+ def locals
142
+ Thread.current.thread_variable_get(:concurrent_thread_locals)
143
+ end
144
+
145
+ def locals!
146
+ thread = Thread.current
147
+ locals = thread.thread_variable_get(:concurrent_thread_locals)
148
+
149
+ unless locals
150
+ locals = thread.thread_variable_set(:concurrent_thread_locals, [])
151
+ weak_synchronize do
152
+ @all_arrays[locals.object_id] = locals
153
+ end
154
+ # When the thread goes out of scope, we should delete the associated locals:
155
+ ObjectSpace.define_finalizer(thread, thread_fiber_finalizer(locals.object_id))
156
+ end
157
+
158
+ locals
159
+ end
160
+ end
161
+
162
+ # @!visibility private
163
+ # @!macro internal_implementation_note
164
+ # An array-backed storage of indexed variables per fiber.
165
+ class FiberLocals < AbstractLocals
166
+ def locals
167
+ Thread.current[:concurrent_fiber_locals]
168
+ end
169
+
170
+ def locals!
171
+ thread = Thread.current
172
+ locals = thread[:concurrent_fiber_locals]
173
+
174
+ unless locals
175
+ locals = thread[:concurrent_fiber_locals] = []
176
+ weak_synchronize do
177
+ @all_arrays[locals.object_id] = locals
178
+ end
179
+ # When the fiber goes out of scope, we should delete the associated locals:
180
+ ObjectSpace.define_finalizer(Fiber.current, thread_fiber_finalizer(locals.object_id))
181
+ end
182
+
183
+ locals
184
+ end
185
+ end
186
+
187
+ private_constant :AbstractLocals, :ThreadLocals, :FiberLocals
188
+ 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