concurrent-ruby 1.1.9 → 1.2.0

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 +24 -0
  3. data/Gemfile +2 -8
  4. data/README.md +34 -37
  5. data/Rakefile +48 -69
  6. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +0 -0
  7. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +52 -22
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +10 -25
  9. data/lib/concurrent-ruby/concurrent/agent.rb +2 -1
  10. data/lib/concurrent-ruby/concurrent/array.rb +0 -10
  11. data/lib/concurrent-ruby/concurrent/async.rb +1 -0
  12. data/lib/concurrent-ruby/concurrent/atom.rb +1 -1
  13. data/lib/concurrent-ruby/concurrent/atomic/atomic_boolean.rb +5 -4
  14. data/lib/concurrent-ruby/concurrent/atomic/atomic_fixnum.rb +5 -4
  15. data/lib/concurrent-ruby/concurrent/atomic/atomic_markable_reference.rb +3 -0
  16. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +82 -151
  17. data/lib/concurrent-ruby/concurrent/atomic/cyclic_barrier.rb +1 -1
  18. data/lib/concurrent-ruby/concurrent/atomic/event.rb +3 -3
  19. data/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb +109 -0
  20. data/lib/concurrent-ruby/concurrent/atomic/java_count_down_latch.rb +1 -0
  21. data/lib/concurrent-ruby/concurrent/atomic/locals.rb +188 -0
  22. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +28 -0
  23. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_boolean.rb +11 -5
  24. data/lib/concurrent-ruby/concurrent/atomic/mutex_atomic_fixnum.rb +11 -5
  25. data/lib/concurrent-ruby/concurrent/atomic/mutex_count_down_latch.rb +1 -1
  26. data/lib/concurrent-ruby/concurrent/atomic/mutex_semaphore.rb +19 -3
  27. data/lib/concurrent-ruby/concurrent/atomic/read_write_lock.rb +2 -1
  28. data/lib/concurrent-ruby/concurrent/atomic/reentrant_read_write_lock.rb +9 -9
  29. data/lib/concurrent-ruby/concurrent/atomic/semaphore.rb +32 -14
  30. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +96 -89
  31. data/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb +37 -0
  32. data/lib/concurrent-ruby/concurrent/atomic_reference/mutex_atomic.rb +15 -4
  33. data/lib/concurrent-ruby/concurrent/collection/copy_on_notify_observer_set.rb +1 -1
  34. data/lib/concurrent-ruby/concurrent/collection/copy_on_write_observer_set.rb +1 -1
  35. data/lib/concurrent-ruby/concurrent/collection/lock_free_stack.rb +2 -0
  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 +17 -14
  43. data/lib/concurrent-ruby/concurrent/executor/fixed_thread_pool.rb +13 -3
  44. data/lib/concurrent-ruby/concurrent/executor/java_executor_service.rb +3 -3
  45. data/lib/concurrent-ruby/concurrent/executor/java_thread_pool_executor.rb +4 -0
  46. data/lib/concurrent-ruby/concurrent/executor/ruby_executor_service.rb +10 -4
  47. data/lib/concurrent-ruby/concurrent/executor/ruby_thread_pool_executor.rb +26 -37
  48. data/lib/concurrent-ruby/concurrent/executor/safe_task_executor.rb +6 -6
  49. data/lib/concurrent-ruby/concurrent/executor/serialized_execution.rb +1 -1
  50. data/lib/concurrent-ruby/concurrent/executor/simple_executor_service.rb +4 -1
  51. data/lib/concurrent-ruby/concurrent/hash.rb +0 -9
  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 +9 -9
  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 +7 -6
  60. data/lib/concurrent-ruby/concurrent/re_include.rb +2 -0
  61. data/lib/concurrent-ruby/concurrent/scheduled_task.rb +30 -17
  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 +6 -5
  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/cheap_lockable.rb +2 -39
  78. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +1 -37
  79. data/lib/concurrent-ruby/concurrent/timer_task.rb +11 -33
  80. data/lib/concurrent-ruby/concurrent/tuple.rb +1 -5
  81. data/lib/concurrent-ruby/concurrent/tvar.rb +22 -61
  82. data/lib/concurrent-ruby/concurrent/utility/engine.rb +5 -16
  83. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +7 -46
  84. data/lib/concurrent-ruby/concurrent/utility/native_extension_loader.rb +8 -10
  85. data/lib/concurrent-ruby/concurrent/utility/native_integer.rb +1 -0
  86. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +36 -89
  87. data/lib/concurrent-ruby/concurrent/version.rb +1 -1
  88. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -1
  89. metadata +10 -12
  90. data/lib/concurrent-ruby/concurrent/atomic/abstract_thread_local_var.rb +0 -66
  91. data/lib/concurrent-ruby/concurrent/atomic/java_thread_local_var.rb +0 -37
  92. data/lib/concurrent-ruby/concurrent/atomic/ruby_thread_local_var.rb +0 -181
  93. data/lib/concurrent-ruby/concurrent/synchronization/jruby_object.rb +0 -45
  94. data/lib/concurrent-ruby/concurrent/synchronization/mri_object.rb +0 -44
  95. data/lib/concurrent-ruby/concurrent/synchronization/rbx_lockable_object.rb +0 -71
  96. data/lib/concurrent-ruby/concurrent/synchronization/rbx_object.rb +0 -49
  97. data/lib/concurrent-ruby/concurrent/synchronization/truffleruby_object.rb +0 -47
@@ -1,5 +1,6 @@
1
+ require 'concurrent/utility/native_extension_loader' # load native parts first
2
+
1
3
  require 'concurrent/atomic/mutex_atomic_boolean'
2
- require 'concurrent/synchronization'
3
4
 
4
5
  module Concurrent
5
6
 
@@ -79,10 +80,10 @@ module Concurrent
79
80
  # @!visibility private
80
81
  # @!macro internal_implementation_note
81
82
  AtomicBooleanImplementation = case
82
- when defined?(JavaAtomicBoolean)
83
- JavaAtomicBoolean
84
- when defined?(CAtomicBoolean)
83
+ when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
85
84
  CAtomicBoolean
85
+ when Concurrent.on_jruby?
86
+ JavaAtomicBoolean
86
87
  else
87
88
  MutexAtomicBoolean
88
89
  end
@@ -1,5 +1,6 @@
1
+ require 'concurrent/utility/native_extension_loader' # load native parts first
2
+
1
3
  require 'concurrent/atomic/mutex_atomic_fixnum'
2
- require 'concurrent/synchronization'
3
4
 
4
5
  module Concurrent
5
6
 
@@ -96,10 +97,10 @@ module Concurrent
96
97
  # @!visibility private
97
98
  # @!macro internal_implementation_note
98
99
  AtomicFixnumImplementation = case
99
- when defined?(JavaAtomicFixnum)
100
- JavaAtomicFixnum
101
- when defined?(CAtomicFixnum)
100
+ when Concurrent.on_cruby? && Concurrent.c_extensions_loaded?
102
101
  CAtomicFixnum
102
+ when Concurrent.on_jruby?
103
+ JavaAtomicFixnum
103
104
  else
104
105
  MutexAtomicFixnum
105
106
  end
@@ -1,3 +1,6 @@
1
+ require 'concurrent/errors'
2
+ require 'concurrent/synchronization/object'
3
+
1
4
  module Concurrent
2
5
  # An atomic reference which maintains an object reference along with a mark bit
3
6
  # that can be updated atomically.
@@ -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?
@@ -170,28 +40,89 @@ module Concurrent
170
40
  alias_method :compare_and_swap, :compare_and_set
171
41
  alias_method :swap, :get_and_set
172
42
  end
173
- when Concurrent.on_rbx?
174
- # @note Extends `Rubinius::AtomicReference` version adding aliases
175
- # and numeric logic.
176
- #
177
- # @!visibility private
178
- # @!macro internal_implementation_note
179
- class RbxAtomicReference < Rubinius::AtomicReference
180
- alias_method :_compare_and_set, :compare_and_set
181
- include AtomicDirectUpdate
182
- include AtomicNumericCompareAndSetWrapper
183
- alias_method :value, :get
184
- alias_method :value=, :set
185
- alias_method :swap, :get_and_set
186
- alias_method :compare_and_swap, :compare_and_set
187
- end
188
- RbxAtomicReference
43
+ TruffleRubyAtomicReference
189
44
  else
190
45
  MutexAtomicReference
191
46
  end
192
47
  private_constant :AtomicReferenceImplementation
193
48
 
194
- # @!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
195
126
  class AtomicReference < AtomicReferenceImplementation
196
127
 
197
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
 
@@ -19,7 +19,7 @@ module Concurrent
19
19
  # t1 = Thread.new do
20
20
  # puts "t1 is waiting"
21
21
  # event.wait(1)
22
- # puts "event ocurred"
22
+ # puts "event occurred"
23
23
  # end
24
24
  #
25
25
  # t2 = Thread.new do
@@ -30,8 +30,8 @@ module Concurrent
30
30
  # [t1, t2].each(&:join)
31
31
  #
32
32
  # # prints:
33
- # # t2 calling set
34
33
  # # t1 is waiting
34
+ # # t2 calling set
35
35
  # # event occurred
36
36
  class Event < Synchronization::LockableObject
37
37
 
@@ -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