concurrent-ruby 1.1.5 → 1.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (151) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +84 -1
  3. data/Gemfile +5 -10
  4. data/{LICENSE.md → LICENSE.txt} +18 -20
  5. data/README.md +55 -31
  6. data/Rakefile +84 -92
  7. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaAtomicFixnumLibrary.java +0 -0
  8. data/ext/concurrent-ruby/com/concurrent_ruby/ext/JavaSemaphoreLibrary.java +52 -22
  9. data/ext/concurrent-ruby/com/concurrent_ruby/ext/SynchronizationLibrary.java +10 -25
  10. data/lib/{concurrent → concurrent-ruby/concurrent}/agent.rb +2 -1
  11. data/lib/{concurrent → concurrent-ruby/concurrent}/array.rb +6 -16
  12. data/lib/{concurrent → concurrent-ruby/concurrent}/async.rb +10 -20
  13. data/lib/{concurrent → concurrent-ruby/concurrent}/atom.rb +2 -2
  14. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/atomic_boolean.rb +7 -6
  15. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/atomic_fixnum.rb +5 -4
  16. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/atomic_markable_reference.rb +3 -0
  17. data/lib/concurrent-ruby/concurrent/atomic/atomic_reference.rb +135 -0
  18. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/cyclic_barrier.rb +1 -1
  19. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/event.rb +3 -3
  20. data/lib/concurrent-ruby/concurrent/atomic/fiber_local_var.rb +109 -0
  21. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/java_count_down_latch.rb +1 -0
  22. data/lib/concurrent-ruby/concurrent/atomic/locals.rb +189 -0
  23. data/lib/concurrent-ruby/concurrent/atomic/lock_local_var.rb +28 -0
  24. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_atomic_boolean.rb +11 -5
  25. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_atomic_fixnum.rb +11 -5
  26. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_count_down_latch.rb +1 -1
  27. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/mutex_semaphore.rb +19 -3
  28. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/read_write_lock.rb +2 -1
  29. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/reentrant_read_write_lock.rb +9 -9
  30. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/semaphore.rb +32 -14
  31. data/lib/concurrent-ruby/concurrent/atomic/thread_local_var.rb +111 -0
  32. data/lib/concurrent-ruby/concurrent/atomic_reference/atomic_direct_update.rb +37 -0
  33. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic_reference/mutex_atomic.rb +15 -4
  34. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/copy_on_notify_observer_set.rb +1 -1
  35. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/copy_on_write_observer_set.rb +1 -1
  36. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/lock_free_stack.rb +2 -0
  37. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/mri_map_backend.rb +3 -3
  38. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/non_concurrent_map_backend.rb +16 -8
  39. data/lib/concurrent-ruby/concurrent/collection/map/truffleruby_map_backend.rb +14 -0
  40. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/ruby_non_concurrent_priority_queue.rb +11 -1
  41. data/lib/{concurrent → concurrent-ruby/concurrent}/concern/dereferenceable.rb +2 -2
  42. data/lib/concurrent-ruby/concurrent/concern/logging.rb +116 -0
  43. data/lib/concurrent-ruby/concurrent/concurrent_ruby.jar +0 -0
  44. data/lib/concurrent-ruby/concurrent/configuration.rb +105 -0
  45. data/lib/{concurrent → concurrent-ruby/concurrent}/delay.rb +2 -2
  46. data/lib/{concurrent → concurrent-ruby/concurrent}/errors.rb +5 -0
  47. data/lib/{concurrent → concurrent-ruby/concurrent}/exchanger.rb +1 -0
  48. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/abstract_executor_service.rb +34 -37
  49. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/cached_thread_pool.rb +4 -4
  50. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/executor_service.rb +2 -2
  51. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/fixed_thread_pool.rb +29 -15
  52. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/java_executor_service.rb +21 -9
  53. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/java_single_thread_executor.rb +4 -3
  54. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/java_thread_pool_executor.rb +19 -2
  55. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/ruby_executor_service.rb +10 -6
  56. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/ruby_single_thread_executor.rb +0 -1
  57. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/ruby_thread_pool_executor.rb +46 -42
  58. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/safe_task_executor.rb +6 -6
  59. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/serialized_execution.rb +1 -1
  60. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/simple_executor_service.rb +5 -2
  61. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/single_thread_executor.rb +1 -0
  62. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/thread_pool_executor.rb +2 -1
  63. data/lib/{concurrent → concurrent-ruby/concurrent}/executor/timer_set.rb +0 -1
  64. data/lib/{concurrent → concurrent-ruby/concurrent}/hash.rb +1 -10
  65. data/lib/{concurrent → concurrent-ruby/concurrent}/immutable_struct.rb +10 -2
  66. data/lib/{concurrent → concurrent-ruby/concurrent}/ivar.rb +2 -1
  67. data/lib/{concurrent → concurrent-ruby/concurrent}/map.rb +44 -31
  68. data/lib/{concurrent → concurrent-ruby/concurrent}/maybe.rb +1 -1
  69. data/lib/{concurrent → concurrent-ruby/concurrent}/mutable_struct.rb +13 -3
  70. data/lib/{concurrent → concurrent-ruby/concurrent}/mvar.rb +1 -1
  71. data/lib/{concurrent → concurrent-ruby/concurrent}/promise.rb +2 -1
  72. data/lib/{concurrent → concurrent-ruby/concurrent}/promises.rb +7 -6
  73. data/lib/{concurrent → concurrent-ruby/concurrent}/re_include.rb +2 -0
  74. data/lib/{concurrent → concurrent-ruby/concurrent}/scheduled_task.rb +30 -17
  75. data/lib/{concurrent → concurrent-ruby/concurrent}/set.rb +17 -19
  76. data/lib/{concurrent → concurrent-ruby/concurrent}/settable_struct.rb +13 -3
  77. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/abstract_lockable_object.rb +5 -1
  78. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/abstract_object.rb +1 -3
  79. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/abstract_struct.rb +11 -0
  80. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/condition.rb +2 -0
  81. data/lib/concurrent-ruby/concurrent/synchronization/full_memory_barrier.rb +29 -0
  82. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/jruby_lockable_object.rb +3 -1
  83. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/lock.rb +2 -0
  84. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/lockable_object.rb +8 -7
  85. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/mutex_lockable_object.rb +18 -5
  86. data/lib/{concurrent → concurrent-ruby/concurrent}/synchronization/object.rb +12 -44
  87. data/lib/concurrent-ruby/concurrent/synchronization/safe_initialization.rb +36 -0
  88. data/lib/concurrent-ruby/concurrent/synchronization/volatile.rb +101 -0
  89. data/lib/concurrent-ruby/concurrent/synchronization.rb +13 -0
  90. data/lib/concurrent-ruby/concurrent/thread_safe/synchronized_delegator.rb +47 -0
  91. data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/cheap_lockable.rb +2 -39
  92. data/lib/concurrent-ruby/concurrent/thread_safe/util/data_structures.rb +52 -0
  93. data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/striped64.rb +1 -1
  94. data/lib/{concurrent → concurrent-ruby/concurrent}/timer_task.rb +11 -34
  95. data/lib/{concurrent → concurrent-ruby/concurrent}/tuple.rb +1 -5
  96. data/lib/{concurrent → concurrent-ruby/concurrent}/tvar.rb +21 -57
  97. data/lib/{concurrent → concurrent-ruby/concurrent}/utility/engine.rb +5 -16
  98. data/lib/concurrent-ruby/concurrent/utility/monotonic_time.rb +19 -0
  99. data/lib/{concurrent → concurrent-ruby/concurrent}/utility/native_extension_loader.rb +8 -10
  100. data/lib/{concurrent → concurrent-ruby/concurrent}/utility/native_integer.rb +1 -0
  101. data/lib/concurrent-ruby/concurrent/utility/processor_counter.rb +110 -0
  102. data/lib/concurrent-ruby/concurrent/version.rb +3 -0
  103. data/lib/concurrent-ruby/concurrent-ruby.rb +5 -0
  104. metadata +127 -129
  105. data/lib/concurrent/atomic/abstract_thread_local_var.rb +0 -66
  106. data/lib/concurrent/atomic/atomic_reference.rb +0 -204
  107. data/lib/concurrent/atomic/java_thread_local_var.rb +0 -37
  108. data/lib/concurrent/atomic/ruby_thread_local_var.rb +0 -161
  109. data/lib/concurrent/atomic/thread_local_var.rb +0 -104
  110. data/lib/concurrent/concern/logging.rb +0 -32
  111. data/lib/concurrent/concurrent_ruby.jar +0 -0
  112. data/lib/concurrent/configuration.rb +0 -184
  113. data/lib/concurrent/synchronization/jruby_object.rb +0 -45
  114. data/lib/concurrent/synchronization/mri_object.rb +0 -44
  115. data/lib/concurrent/synchronization/rbx_lockable_object.rb +0 -65
  116. data/lib/concurrent/synchronization/rbx_object.rb +0 -49
  117. data/lib/concurrent/synchronization/truffleruby_object.rb +0 -47
  118. data/lib/concurrent/synchronization/volatile.rb +0 -36
  119. data/lib/concurrent/synchronization.rb +0 -30
  120. data/lib/concurrent/thread_safe/synchronized_delegator.rb +0 -50
  121. data/lib/concurrent/thread_safe/util/data_structures.rb +0 -63
  122. data/lib/concurrent/utility/at_exit.rb +0 -97
  123. data/lib/concurrent/utility/monotonic_time.rb +0 -58
  124. data/lib/concurrent/utility/processor_counter.rb +0 -158
  125. data/lib/concurrent/version.rb +0 -3
  126. data/lib/concurrent-ruby.rb +0 -1
  127. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic/count_down_latch.rb +1 -1
  128. data/lib/{concurrent → concurrent-ruby/concurrent}/atomic_reference/numeric_cas_wrapper.rb +0 -0
  129. data/lib/{concurrent → concurrent-ruby/concurrent}/atomics.rb +0 -0
  130. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/java_non_concurrent_priority_queue.rb +0 -0
  131. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/atomic_reference_map_backend.rb +0 -0
  132. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/map/synchronized_map_backend.rb +0 -0
  133. data/lib/{concurrent → concurrent-ruby/concurrent}/collection/non_concurrent_priority_queue.rb +1 -1
  134. /data/lib/{concurrent → concurrent-ruby/concurrent}/concern/deprecation.rb +0 -0
  135. /data/lib/{concurrent → concurrent-ruby/concurrent}/concern/obligation.rb +0 -0
  136. /data/lib/{concurrent → concurrent-ruby/concurrent}/concern/observable.rb +0 -0
  137. /data/lib/{concurrent → concurrent-ruby/concurrent}/constants.rb +0 -0
  138. /data/lib/{concurrent → concurrent-ruby/concurrent}/dataflow.rb +0 -0
  139. /data/lib/{concurrent → concurrent-ruby/concurrent}/executor/immediate_executor.rb +0 -0
  140. /data/lib/{concurrent → concurrent-ruby/concurrent}/executor/indirect_immediate_executor.rb +0 -0
  141. /data/lib/{concurrent → concurrent-ruby/concurrent}/executor/serial_executor_service.rb +0 -0
  142. /data/lib/{concurrent → concurrent-ruby/concurrent}/executor/serialized_execution_delegator.rb +0 -0
  143. /data/lib/{concurrent → concurrent-ruby/concurrent}/executors.rb +0 -0
  144. /data/lib/{concurrent → concurrent-ruby/concurrent}/future.rb +0 -0
  145. /data/lib/{concurrent → concurrent-ruby/concurrent}/options.rb +0 -0
  146. /data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/adder.rb +0 -0
  147. /data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/power_of_two_tuple.rb +0 -0
  148. /data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/volatile.rb +0 -0
  149. /data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util/xor_shift_random.rb +0 -0
  150. /data/lib/{concurrent → concurrent-ruby/concurrent}/thread_safe/util.rb +0 -0
  151. /data/lib/{concurrent.rb → concurrent-ruby/concurrent.rb} +0 -0
@@ -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
@@ -23,7 +23,14 @@ module Concurrent
23
23
 
24
24
  synchronize do
25
25
  try_acquire_timed(permits, nil)
26
- nil
26
+ end
27
+
28
+ return unless block_given?
29
+
30
+ begin
31
+ yield
32
+ ensure
33
+ release(permits)
27
34
  end
28
35
  end
29
36
 
@@ -48,13 +55,22 @@ module Concurrent
48
55
  Utility::NativeInteger.ensure_integer_and_bounds permits
49
56
  Utility::NativeInteger.ensure_positive permits
50
57
 
51
- synchronize do
58
+ acquired = synchronize do
52
59
  if timeout.nil?
53
60
  try_acquire_now(permits)
54
61
  else
55
62
  try_acquire_timed(permits, timeout)
56
63
  end
57
64
  end
65
+
66
+ return acquired unless block_given?
67
+ return unless acquired
68
+
69
+ begin
70
+ yield
71
+ ensure
72
+ release(permits)
73
+ end
58
74
  end
59
75
 
60
76
  # @!macro semaphore_method_release
@@ -1,7 +1,8 @@
1
1
  require 'thread'
2
2
  require 'concurrent/atomic/atomic_fixnum'
3
3
  require 'concurrent/errors'
4
- require 'concurrent/synchronization'
4
+ require 'concurrent/synchronization/object'
5
+ require 'concurrent/synchronization/lock'
5
6
 
6
7
  module Concurrent
7
8
 
@@ -1,8 +1,10 @@
1
1
  require 'thread'
2
2
  require 'concurrent/atomic/atomic_reference'
3
+ require 'concurrent/atomic/atomic_fixnum'
3
4
  require 'concurrent/errors'
4
- require 'concurrent/synchronization'
5
- require 'concurrent/atomic/thread_local_var'
5
+ require 'concurrent/synchronization/object'
6
+ require 'concurrent/synchronization/lock'
7
+ require 'concurrent/atomic/lock_local_var'
6
8
 
7
9
  module Concurrent
8
10
 
@@ -109,7 +111,7 @@ module Concurrent
109
111
  @Counter = AtomicFixnum.new(0) # single integer which represents lock state
110
112
  @ReadQueue = Synchronization::Lock.new # used to queue waiting readers
111
113
  @WriteQueue = Synchronization::Lock.new # used to queue waiting writers
112
- @HeldCount = ThreadLocalVar.new(0) # indicates # of R & W locks held by this thread
114
+ @HeldCount = LockLocalVar.new(0) # indicates # of R & W locks held by this thread
113
115
  end
114
116
 
115
117
  # Execute a block operation within a read lock.
@@ -267,12 +269,10 @@ module Concurrent
267
269
  # running right now, AND no writers who came before us still waiting to
268
270
  # acquire the lock
269
271
  # Additionally, if any read locks have been taken, we must hold all of them
270
- if c == held
271
- # If we successfully swap the RUNNING_WRITER bit on, then we can go ahead
272
- if @Counter.compare_and_set(c, c+RUNNING_WRITER)
273
- @HeldCount.value = held + WRITE_LOCK_HELD
274
- return true
275
- end
272
+ if held > 0 && @Counter.compare_and_set(1, c+RUNNING_WRITER)
273
+ # If we are the only one reader and successfully swap the RUNNING_WRITER bit on, then we can go ahead
274
+ @HeldCount.value = held + WRITE_LOCK_HELD
275
+ return true
276
276
  elsif @Counter.compare_and_set(c, c+WAITING_WRITER)
277
277
  while true
278
278
  # Now we have successfully incremented, so no more readers will be able to increment
@@ -1,5 +1,4 @@
1
1
  require 'concurrent/atomic/mutex_semaphore'
2
- require 'concurrent/synchronization'
3
2
 
4
3
  module Concurrent
5
4
 
@@ -11,19 +10,20 @@ module Concurrent
11
10
  #
12
11
  # @param [Fixnum] count the initial count
13
12
  #
14
- # @raise [ArgumentError] if `count` is not an integer or is less than zero
13
+ # @raise [ArgumentError] if `count` is not an integer
15
14
 
16
15
  # @!macro semaphore_method_acquire
17
16
  #
18
17
  # Acquires the given number of permits from this semaphore,
19
- # blocking until all are available.
18
+ # blocking until all are available. If a block is given,
19
+ # yields to it and releases the permits afterwards.
20
20
  #
21
21
  # @param [Fixnum] permits Number of permits to acquire
22
22
  #
23
- # @raise [ArgumentError] if `permits` is not an integer or is less than
24
- # one
23
+ # @raise [ArgumentError] if `permits` is not an integer or is less than zero
25
24
  #
26
- # @return [nil]
25
+ # @return [nil, BasicObject] Without a block, `nil` is returned. If a block
26
+ # is given, its return value is returned.
27
27
 
28
28
  # @!macro semaphore_method_available_permits
29
29
  #
@@ -41,18 +41,21 @@ module Concurrent
41
41
  #
42
42
  # Acquires the given number of permits from this semaphore,
43
43
  # only if all are available at the time of invocation or within
44
- # `timeout` interval
44
+ # `timeout` interval. If a block is given, yields to it if the permits
45
+ # were successfully acquired, and releases them afterward, returning the
46
+ # block's return value.
45
47
  #
46
48
  # @param [Fixnum] permits the number of permits to acquire
47
49
  #
48
50
  # @param [Fixnum] timeout the number of seconds to wait for the counter
49
51
  # or `nil` to return immediately
50
52
  #
51
- # @raise [ArgumentError] if `permits` is not an integer or is less than
52
- # one
53
+ # @raise [ArgumentError] if `permits` is not an integer or is less than zero
53
54
  #
54
- # @return [Boolean] `false` if no permits are available, `true` when
55
- # acquired a permit
55
+ # @return [true, false, nil, BasicObject] `false` if no permits are
56
+ # available, `true` when acquired a permit. If a block is given, the
57
+ # block's return value is returned if the permits were acquired; if not,
58
+ # `nil` is returned.
56
59
 
57
60
  # @!macro semaphore_method_release
58
61
  #
@@ -60,7 +63,7 @@ module Concurrent
60
63
  #
61
64
  # @param [Fixnum] permits Number of permits to return to the semaphore.
62
65
  #
63
- # @raise [ArgumentError] if `permits` is not a number or is less than one
66
+ # @raise [ArgumentError] if `permits` is not a number or is less than zero
64
67
  #
65
68
  # @return [nil]
66
69
 
@@ -90,8 +93,8 @@ module Concurrent
90
93
 
91
94
  # @!visibility private
92
95
  # @!macro internal_implementation_note
93
- SemaphoreImplementation = case
94
- when defined?(JavaSemaphore)
96
+ SemaphoreImplementation = if Concurrent.on_jruby?
97
+ require 'concurrent/utility/native_extension_loader'
95
98
  JavaSemaphore
96
99
  else
97
100
  MutexSemaphore
@@ -106,6 +109,8 @@ module Concurrent
106
109
  # releasing a blocking acquirer.
107
110
  # However, no actual permit objects are used; the Semaphore just keeps a
108
111
  # count of the number available and acts accordingly.
112
+ # Alternatively, permits may be acquired within a block, and automatically
113
+ # released after the block finishes executing.
109
114
  #
110
115
  # @!macro semaphore_public_api
111
116
  # @example
@@ -140,6 +145,19 @@ module Concurrent
140
145
  # # Thread 4 releasing semaphore
141
146
  # # Thread 1 acquired semaphore
142
147
  #
148
+ # @example
149
+ # semaphore = Concurrent::Semaphore.new(1)
150
+ #
151
+ # puts semaphore.available_permits
152
+ # semaphore.acquire do
153
+ # puts semaphore.available_permits
154
+ # end
155
+ # puts semaphore.available_permits
156
+ #
157
+ # # prints:
158
+ # # 1
159
+ # # 0
160
+ # # 1
143
161
  class Semaphore < SemaphoreImplementation
144
162
  end
145
163
  end
@@ -0,0 +1,111 @@
1
+ require 'concurrent/constants'
2
+ require_relative 'locals'
3
+
4
+ module Concurrent
5
+
6
+ # A `ThreadLocalVar` is a variable where the value is different for each thread.
7
+ # Each variable may have a default value, but when you modify the variable only
8
+ # the current thread will ever see that change.
9
+ #
10
+ # This is similar to Ruby's built-in thread-local variables (`Thread#thread_variable_get`),
11
+ # but with these major advantages:
12
+ # * `ThreadLocalVar` has its own identity, it doesn't need a Symbol.
13
+ # * Each Ruby's built-in thread-local variable leaks some memory forever (it's a Symbol held forever on the thread),
14
+ # so it's only OK to create a small amount of them.
15
+ # `ThreadLocalVar` has no such issue and it is fine to create many of them.
16
+ # * Ruby's built-in thread-local variables leak forever the value set on each thread (unless set to nil explicitly).
17
+ # `ThreadLocalVar` automatically removes the mapping for each thread once the `ThreadLocalVar` instance is GC'd.
18
+ #
19
+ # @!macro thread_safe_variable_comparison
20
+ #
21
+ # @example
22
+ # v = ThreadLocalVar.new(14)
23
+ # v.value #=> 14
24
+ # v.value = 2
25
+ # v.value #=> 2
26
+ #
27
+ # @example
28
+ # v = ThreadLocalVar.new(14)
29
+ #
30
+ # t1 = Thread.new do
31
+ # v.value #=> 14
32
+ # v.value = 1
33
+ # v.value #=> 1
34
+ # end
35
+ #
36
+ # t2 = Thread.new do
37
+ # v.value #=> 14
38
+ # v.value = 2
39
+ # v.value #=> 2
40
+ # end
41
+ #
42
+ # v.value #=> 14
43
+ class ThreadLocalVar
44
+ LOCALS = ThreadLocals.new
45
+
46
+ # Creates a thread local variable.
47
+ #
48
+ # @param [Object] default the default value when otherwise unset
49
+ # @param [Proc] default_block Optional block that gets called to obtain the
50
+ # default value for each thread
51
+ def initialize(default = nil, &default_block)
52
+ if default && block_given?
53
+ raise ArgumentError, "Cannot use both value and block as default value"
54
+ end
55
+
56
+ if block_given?
57
+ @default_block = default_block
58
+ @default = nil
59
+ else
60
+ @default_block = nil
61
+ @default = default
62
+ end
63
+
64
+ @index = LOCALS.next_index(self)
65
+ end
66
+
67
+ # Returns the value in the current thread's copy of this thread-local variable.
68
+ #
69
+ # @return [Object] the current value
70
+ def value
71
+ LOCALS.fetch(@index) { default }
72
+ end
73
+
74
+ # Sets the current thread's copy of this thread-local variable to the specified value.
75
+ #
76
+ # @param [Object] value the value to set
77
+ # @return [Object] the new value
78
+ def value=(value)
79
+ LOCALS.set(@index, value)
80
+ end
81
+
82
+ # Bind the given value to thread local storage during
83
+ # execution of the given block.
84
+ #
85
+ # @param [Object] value the value to bind
86
+ # @yield the operation to be performed with the bound variable
87
+ # @return [Object] the value
88
+ def bind(value)
89
+ if block_given?
90
+ old_value = self.value
91
+ self.value = value
92
+ begin
93
+ yield
94
+ ensure
95
+ self.value = old_value
96
+ end
97
+ end
98
+ end
99
+
100
+ protected
101
+
102
+ # @!visibility private
103
+ def default
104
+ if @default_block
105
+ self.value = @default_block.call
106
+ else
107
+ @default
108
+ end
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,37 @@
1
+ require 'concurrent/errors'
2
+
3
+ module Concurrent
4
+
5
+ # Define update methods that use direct paths
6
+ #
7
+ # @!visibility private
8
+ # @!macro internal_implementation_note
9
+ module AtomicDirectUpdate
10
+ def update
11
+ true until compare_and_set(old_value = get, new_value = yield(old_value))
12
+ new_value
13
+ end
14
+
15
+ def try_update
16
+ old_value = get
17
+ new_value = yield old_value
18
+
19
+ return unless compare_and_set old_value, new_value
20
+
21
+ new_value
22
+ end
23
+
24
+ def try_update!
25
+ old_value = get
26
+ new_value = yield old_value
27
+ unless compare_and_set(old_value, new_value)
28
+ if $VERBOSE
29
+ raise ConcurrentUpdateError, "Update failed"
30
+ else
31
+ raise ConcurrentUpdateError, "Update failed", ConcurrentUpdateError::CONC_UP_ERR_BACKTRACE
32
+ end
33
+ end
34
+ new_value
35
+ end
36
+ end
37
+ end
@@ -1,8 +1,13 @@
1
+ require 'concurrent/atomic_reference/atomic_direct_update'
2
+ require 'concurrent/atomic_reference/numeric_cas_wrapper'
3
+ require 'concurrent/synchronization/safe_initialization'
4
+
1
5
  module Concurrent
2
6
 
3
7
  # @!visibility private
4
8
  # @!macro internal_implementation_note
5
- class MutexAtomicReference < Synchronization::LockableObject
9
+ class MutexAtomicReference
10
+ extend Concurrent::Synchronization::SafeInitialization
6
11
  include AtomicDirectUpdate
7
12
  include AtomicNumericCompareAndSetWrapper
8
13
  alias_method :compare_and_swap, :compare_and_set
@@ -10,7 +15,8 @@ module Concurrent
10
15
  # @!macro atomic_reference_method_initialize
11
16
  def initialize(value = nil)
12
17
  super()
13
- synchronize { ns_initialize(value) }
18
+ @Lock = ::Mutex.new
19
+ @value = value
14
20
  end
15
21
 
16
22
  # @!macro atomic_reference_method_get
@@ -49,8 +55,13 @@ module Concurrent
49
55
 
50
56
  protected
51
57
 
52
- def ns_initialize(value)
53
- @value = value
58
+ # @!visibility private
59
+ def synchronize
60
+ if @Lock.owned?
61
+ yield
62
+ else
63
+ @Lock.synchronize { yield }
64
+ end
54
65
  end
55
66
  end
56
67
  end
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/lockable_object'
2
2
 
3
3
  module Concurrent
4
4
  module Collection
@@ -1,4 +1,4 @@
1
- require 'concurrent/synchronization'
1
+ require 'concurrent/synchronization/lockable_object'
2
2
 
3
3
  module Concurrent
4
4
  module Collection
@@ -1,3 +1,5 @@
1
+ require 'concurrent/synchronization/object'
2
+
1
3
  module Concurrent
2
4
 
3
5
  # @!macro warn.edge
@@ -9,8 +9,8 @@ module Concurrent
9
9
  # @!visibility private
10
10
  class MriMapBackend < NonConcurrentMapBackend
11
11
 
12
- def initialize(options = nil)
13
- super(options)
12
+ def initialize(options = nil, &default_proc)
13
+ super(options, &default_proc)
14
14
  @write_lock = Mutex.new
15
15
  end
16
16
 
@@ -19,7 +19,7 @@ module Concurrent
19
19
  end
20
20
 
21
21
  def compute_if_absent(key)
22
- if stored_value = _get(key) # fast non-blocking path for the most likely case
22
+ if NULL != (stored_value = @backend.fetch(key, NULL)) # fast non-blocking path for the most likely case
23
23
  stored_value
24
24
  else
25
25
  @write_lock.synchronize { super }
@@ -12,8 +12,10 @@ module Concurrent
12
12
  # directly without calling each other. This is important because of the
13
13
  # SynchronizedMapBackend which uses a non-reentrant mutex for performance
14
14
  # reasons.
15
- def initialize(options = nil)
16
- @backend = {}
15
+ def initialize(options = nil, &default_proc)
16
+ validate_options_hash!(options) if options.kind_of?(::Hash)
17
+ set_backend(default_proc)
18
+ @default_proc = default_proc
17
19
  end
18
20
 
19
21
  def [](key)
@@ -55,7 +57,7 @@ module Concurrent
55
57
  end
56
58
 
57
59
  def compute(key)
58
- store_computed_value(key, yield(@backend[key]))
60
+ store_computed_value(key, yield(get_or_default(key, nil)))
59
61
  end
60
62
 
61
63
  def merge_pair(key, value)
@@ -67,7 +69,7 @@ module Concurrent
67
69
  end
68
70
 
69
71
  def get_and_set(key, value)
70
- stored_value = @backend[key]
72
+ stored_value = get_or_default(key, nil)
71
73
  @backend[key] = value
72
74
  stored_value
73
75
  end
@@ -109,13 +111,19 @@ module Concurrent
109
111
  @backend.fetch(key, default_value)
110
112
  end
111
113
 
112
- alias_method :_get, :[]
113
- alias_method :_set, :[]=
114
- private :_get, :_set
115
114
  private
115
+
116
+ def set_backend(default_proc)
117
+ if default_proc
118
+ @backend = ::Hash.new { |_h, key| default_proc.call(self, key) }
119
+ else
120
+ @backend = {}
121
+ end
122
+ end
123
+
116
124
  def initialize_copy(other)
117
125
  super
118
- @backend = {}
126
+ set_backend(@default_proc)
119
127
  self
120
128
  end
121
129
 
@@ -0,0 +1,14 @@
1
+ module Concurrent
2
+
3
+ # @!visibility private
4
+ module Collection
5
+
6
+ # @!visibility private
7
+ class TruffleRubyMapBackend < TruffleRuby::ConcurrentMap
8
+ def initialize(options = nil)
9
+ options ||= {}
10
+ super(initial_capacity: options[:initial_capacity], load_factor: options[:load_factor])
11
+ end
12
+ end
13
+ end
14
+ end
@@ -30,7 +30,7 @@ module Concurrent
30
30
  if @queue[k] == item
31
31
  swap(k, @length)
32
32
  @length -= 1
33
- sink(k)
33
+ sink(k) || swim(k)
34
34
  @queue.pop
35
35
  else
36
36
  k += 1
@@ -126,12 +126,17 @@ module Concurrent
126
126
  #
127
127
  # @!visibility private
128
128
  def sink(k)
129
+ success = false
130
+
129
131
  while (j = (2 * k)) <= @length do
130
132
  j += 1 if j < @length && ! ordered?(j, j+1)
131
133
  break if ordered?(k, j)
132
134
  swap(k, j)
135
+ success = true
133
136
  k = j
134
137
  end
138
+
139
+ success
135
140
  end
136
141
 
137
142
  # Percolate up to maintain heap invariant.
@@ -140,10 +145,15 @@ module Concurrent
140
145
  #
141
146
  # @!visibility private
142
147
  def swim(k)
148
+ success = false
149
+
143
150
  while k > 1 && ! ordered?(k/2, k) do
144
151
  swap(k, k/2)
145
152
  k = k/2
153
+ success = true
146
154
  end
155
+
156
+ success
147
157
  end
148
158
  end
149
159
  end
@@ -37,8 +37,8 @@ module Concurrent
37
37
  # returning data to the caller (dereferencing).
38
38
  #
39
39
  # @note Most classes that include this module will call `#set_deref_options`
40
- # from within the constructor, thus allowing these options to be set at
41
- # object creation.
40
+ # from within the constructor, thus allowing these options to be set at
41
+ # object creation.
42
42
  #
43
43
  # @param [Hash] opts the options defining dereference behavior.
44
44
  # @option opts [String] :dup_on_deref (false) call `#dup` before returning the data
@@ -0,0 +1,116 @@
1
+ require 'logger'
2
+ require 'concurrent/atomic/atomic_reference'
3
+
4
+ module Concurrent
5
+ module Concern
6
+
7
+ # Include where logging is needed
8
+ #
9
+ # @!visibility private
10
+ module Logging
11
+ include Logger::Severity
12
+
13
+ # Logs through {Concurrent.global_logger}, it can be overridden by setting @logger
14
+ # @param [Integer] level one of Logger::Severity constants
15
+ # @param [String] progname e.g. a path of an Actor
16
+ # @param [String, nil] message when nil block is used to generate the message
17
+ # @yieldreturn [String] a message
18
+ def log(level, progname, message = nil, &block)
19
+ logger = if defined?(@logger) && @logger
20
+ @logger
21
+ else
22
+ Concurrent.global_logger
23
+ end
24
+ logger.call level, progname, message, &block
25
+ rescue => error
26
+ $stderr.puts "`Concurrent.configuration.logger` failed to log #{[level, progname, message, block]}\n" +
27
+ "#{error.message} (#{error.class})\n#{error.backtrace.join "\n"}"
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ module Concurrent
34
+ extend Concern::Logging
35
+
36
+ # @return [Logger] Logger with provided level and output.
37
+ def self.create_simple_logger(level = Logger::FATAL, output = $stderr)
38
+ # TODO (pitr-ch 24-Dec-2016): figure out why it had to be replaced, stdlogger was deadlocking
39
+ lambda do |severity, progname, message = nil, &block|
40
+ return false if severity < level
41
+
42
+ message = block ? block.call : message
43
+ formatted_message = case message
44
+ when String
45
+ message
46
+ when Exception
47
+ format "%s (%s)\n%s",
48
+ message.message, message.class, (message.backtrace || []).join("\n")
49
+ else
50
+ message.inspect
51
+ end
52
+
53
+ output.print format "[%s] %5s -- %s: %s\n",
54
+ Time.now.strftime('%Y-%m-%d %H:%M:%S.%L'),
55
+ Logger::SEV_LABEL[severity],
56
+ progname,
57
+ formatted_message
58
+ true
59
+ end
60
+ end
61
+
62
+ # Use logger created by #create_simple_logger to log concurrent-ruby messages.
63
+ def self.use_simple_logger(level = Logger::FATAL, output = $stderr)
64
+ Concurrent.global_logger = create_simple_logger level, output
65
+ end
66
+
67
+ # @return [Logger] Logger with provided level and output.
68
+ # @deprecated
69
+ def self.create_stdlib_logger(level = Logger::FATAL, output = $stderr)
70
+ logger = Logger.new(output)
71
+ logger.level = level
72
+ logger.formatter = lambda do |severity, datetime, progname, msg|
73
+ formatted_message = case msg
74
+ when String
75
+ msg
76
+ when Exception
77
+ format "%s (%s)\n%s",
78
+ msg.message, msg.class, (msg.backtrace || []).join("\n")
79
+ else
80
+ msg.inspect
81
+ end
82
+ format "[%s] %5s -- %s: %s\n",
83
+ datetime.strftime('%Y-%m-%d %H:%M:%S.%L'),
84
+ severity,
85
+ progname,
86
+ formatted_message
87
+ end
88
+
89
+ lambda do |loglevel, progname, message = nil, &block|
90
+ logger.add loglevel, message, progname, &block
91
+ end
92
+ end
93
+
94
+ # Use logger created by #create_stdlib_logger to log concurrent-ruby messages.
95
+ # @deprecated
96
+ def self.use_stdlib_logger(level = Logger::FATAL, output = $stderr)
97
+ Concurrent.global_logger = create_stdlib_logger level, output
98
+ end
99
+
100
+ # TODO (pitr-ch 27-Dec-2016): remove deadlocking stdlib_logger methods
101
+
102
+ # Suppresses all output when used for logging.
103
+ NULL_LOGGER = lambda { |level, progname, message = nil, &block| }
104
+
105
+ # @!visibility private
106
+ GLOBAL_LOGGER = AtomicReference.new(create_simple_logger(Logger::WARN))
107
+ private_constant :GLOBAL_LOGGER
108
+
109
+ def self.global_logger
110
+ GLOBAL_LOGGER.value
111
+ end
112
+
113
+ def self.global_logger=(value)
114
+ GLOBAL_LOGGER.value = value
115
+ end
116
+ end