Module: Farce::Abstract::Counter Abstract
- Includes:
- Value, Internal::Copyable, Internal::ValueSerialization, Internal::Inspect, Internal::MarshalSupport::Counter
- Included in:
- Counter, Local::Counter
- Defined in:
- lib/farce/abstract/counter.rb,
lib/farce/integrations/psych.rb
Overview
Common numeric interface and atomic operations for counters.
This is a module rather than a class so Counter can inherit directly from the native counter and avoid delegation overhead.
Class Method Summary collapse
Instance Method Summary collapse
- #add(by = 1) ⇒ BasicObject
-
#decrement(by = 1) ⇒ Integer
Atomically decrement the counter by the given amount (default is 1).
-
#decrement_if_above(floor) ⇒ Boolean
Decrement the counter by one if its current value is above the floor.
-
#increment(by = 1) ⇒ Integer
Atomically increment the counter by the given amount (default is 1).
-
#increment_if_below(limit) ⇒ Boolean
Increment the counter by one if its current value is below the limit.
-
#initial ⇒ Integer
The initial value retained by reset and copies.
-
#reset ⇒ self
Reset the counter to its initial value.
-
#update {|count| ... } ⇒ Integer
Pass the current value to the given block, replacing it with the block's result.
-
#value ⇒ Integer
The current value of the counter.
-
#wait_until(timeout: nil) {|value| ... } ⇒ Integer?
Wait until a block condition matches the current value.
-
#wait_until_above(floor, timeout: nil) ⇒ Integer?
Wait until the value is strictly above the floor.
-
#wait_until_below(limit, timeout: nil) ⇒ Integer?
Wait until the value is strictly below the limit.
-
#wait_until_changed(expected, timeout: nil) { ... } ⇒ Integer, ...
Wait until the current value differs from the expected value.
-
#wait_until_match(object, timeout: nil) ⇒ Integer?
Wait until
object === valueis true. -
#wait_until_value(object, timeout: nil) ⇒ Integer?
Wait until
object == valueis true. -
#wait_while(timeout: nil) {|value| ... } ⇒ Integer?
Wait while the block returns a truthy value.
-
#wait_while_above(limit, timeout: nil) ⇒ Integer?
Wait while the value is strictly above the limit.
-
#wait_while_below(floor, timeout: nil) ⇒ Integer?
Wait while the value is strictly below the floor.
-
#wait_while_match(object, timeout: nil) ⇒ Integer?
Wait while
object === valueis true. -
#wait_while_value { ... } ⇒ Integer, ...
Wait until the current value differs from the expected value.
Methods included from Internal::ValueSerialization
#as_extended_json, #as_json, #to_bson, #to_bson_normalized_value, #to_cbor, #to_json, #to_msgpack
Methods included from Value
Methods included from Internal::Copyable
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing ⇒ BasicObject (private)
224 |
# File 'lib/farce/abstract/counter.rb', line 224 def method_missing(...) = value.public_send(...) |
Class Method Details
.included(base) ⇒ BasicObject
32 33 34 35 |
# File 'lib/farce/abstract/counter.rb', line 32 def self.included(base) Internal.prepare_mutable_numeric(base) super end |
Instance Method Details
#add(by = 1) ⇒ BasicObject
207 |
# File 'lib/farce/abstract/counter.rb', line 207 def add(...) = increment(...) |
#decrement(by = 1) ⇒ Integer
Atomically decrement the counter by the given amount (default is 1).
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/farce/abstract/counter.rb', line 27 module Counter include Internal::MarshalSupport::Counter include Internal::Copyable include Internal::Inspect def self.included(base) Internal.prepare_mutable_numeric(base) super end # Numeric's default copying returns self, which is unsuitable for mutable counters. define_method(:dup, Kernel.instance_method(:dup)) define_method(:clone, Kernel.instance_method(:clone)) include Value include Internal::ValueSerialization # Pass the current value to the given block, replacing it with the block's result. # May retry if the value changes during the block's execution. # # Provided for compatibility with `Concurrent::AtomicFixnum`. # # @yield [count] Calculate the new value based on the current value. # @yieldparam count [Integer] the current value of the atomic fixnum. # @yieldreturn [Integer] the new value to replace the current value. # @return [Integer] the new value after the block has been applied. def update raise LocalJumpError, "no block given" unless block_given? while true current = value new_value = yield(current) return new_value if compare_and_set(current, new_value) end end # Wait until the current value differs from the expected value. # @param expected [Integer] the value to wait to change from # @param timeout [Numeric, nil] the total seconds available # @yield called when the timeout expires # @return [Integer, BasicObject, nil] the changed value or timeout fallback def wait_until_changed(expected, timeout: nil) signal = change_signal Internal.with_timeout(timeout) do |_, deadline| observed = signal.generation current = value return current unless expected == current break unless signal.wait(observed, timeout: Clock.remaining(deadline)) end yield if block_given? end # Wait until a block condition matches the current value. # One timeout budget covers all checks and waits. The block is not interrupted. # @yieldparam value [Integer] the current value # @yieldreturn [Boolean] whether the value matches # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_until(timeout: nil, &) = Internal.wait_until(self, timeout:, &) # Wait while the block returns a truthy value. # @yieldparam value [Integer] the current value # @yieldreturn [BasicObject] a truthy value to keep waiting, or nil or false to stop # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the value when the condition becomes false, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_while(timeout: nil) raise LocalJumpError, "no block given" unless block_given? wait_until(timeout:) { |value| !yield(value) } end # Wait while `object === value` is true. # @param object [#===] the pattern to stop matching # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first nonmatching value, or nil on timeout def wait_while_match(object, timeout: nil) wait_while(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # (see #wait_until_changed) def wait_while_value(...) = wait_until_changed(...) # Wait until `object == value` is true. # Counters compare integer values by equality. # @param object [BasicObject] the value to compare with the current value # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_value(object, timeout: nil) wait_until(timeout:) { |value| object == value } end # Wait until `object === value` is true. # @param object [#===] the pattern to match # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_match(object, timeout: nil) wait_until(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # Wait until the value is strictly below the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_below(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value < limit } end # Wait until the value is strictly above the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_above(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value > floor } end # Wait while the value is strictly above the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or below the limit, or nil on timeout def wait_while_above(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value <= limit } end # Wait while the value is strictly below the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or above the floor, or nil on timeout def wait_while_below(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value >= floor } end # Reset the counter to its initial value. # @return [self] Returns self for chaining. def reset self.value = initial self end # Increment the counter by one if its current value is below the limit. # The check and increment happen atomically. # @param [Numeric, String, #to_int] limit The upper bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def increment_if_below(limit) # rubocop:disable Naming/PredicateMethod limit = Integer(limit) current = value Internal::Freeze.check(self) while current < limit return true if compare_and_set(current, current + 1) current = value end false end # Decrement the counter by one if its current value is above the floor. # The check and decrement happen atomically. # @param [Numeric, String, #to_int] floor The lower bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def decrement_if_above(floor) # rubocop:disable Naming/PredicateMethod floor = Integer(floor) current = value Internal::Freeze.check(self) while current > floor return true if compare_and_set(current, current - 1) current = value end false end # @overload add(by = 1) # (see #increment) def add(...) = increment(...) methods = Integer.public_instance_methods - Object.public_instance_methods - [:singleton_method_added] methods += %i[+ - / * ** <=> coerce rationalize to_i to_int to_f to_c to_r to_s] methods.uniq! Internal.delegate(self, :value, *methods) # @api private def inspect_with(inspector) super do inspector.breakable inspector.object(value) end end private def method_missing(...) = value.public_send(...) def respond_to_missing?(method, ...) = value.respond_to?(method) end |
#decrement_if_above(floor) ⇒ Boolean
Decrement the counter by one if its current value is above the floor. The check and decrement happen atomically.
191 192 193 194 195 196 197 198 199 200 201 202 203 |
# File 'lib/farce/abstract/counter.rb', line 191 def decrement_if_above(floor) # rubocop:disable Naming/PredicateMethod floor = Integer(floor) current = value Internal::Freeze.check(self) while current > floor return true if compare_and_set(current, current - 1) current = value end false end |
#increment(by = 1) ⇒ Integer
Atomically increment the counter by the given amount (default is 1). Use the returned integer to allocate a sequence number without a separate read.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/farce/abstract/counter.rb', line 27 module Counter include Internal::MarshalSupport::Counter include Internal::Copyable include Internal::Inspect def self.included(base) Internal.prepare_mutable_numeric(base) super end # Numeric's default copying returns self, which is unsuitable for mutable counters. define_method(:dup, Kernel.instance_method(:dup)) define_method(:clone, Kernel.instance_method(:clone)) include Value include Internal::ValueSerialization # Pass the current value to the given block, replacing it with the block's result. # May retry if the value changes during the block's execution. # # Provided for compatibility with `Concurrent::AtomicFixnum`. # # @yield [count] Calculate the new value based on the current value. # @yieldparam count [Integer] the current value of the atomic fixnum. # @yieldreturn [Integer] the new value to replace the current value. # @return [Integer] the new value after the block has been applied. def update raise LocalJumpError, "no block given" unless block_given? while true current = value new_value = yield(current) return new_value if compare_and_set(current, new_value) end end # Wait until the current value differs from the expected value. # @param expected [Integer] the value to wait to change from # @param timeout [Numeric, nil] the total seconds available # @yield called when the timeout expires # @return [Integer, BasicObject, nil] the changed value or timeout fallback def wait_until_changed(expected, timeout: nil) signal = change_signal Internal.with_timeout(timeout) do |_, deadline| observed = signal.generation current = value return current unless expected == current break unless signal.wait(observed, timeout: Clock.remaining(deadline)) end yield if block_given? end # Wait until a block condition matches the current value. # One timeout budget covers all checks and waits. The block is not interrupted. # @yieldparam value [Integer] the current value # @yieldreturn [Boolean] whether the value matches # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_until(timeout: nil, &) = Internal.wait_until(self, timeout:, &) # Wait while the block returns a truthy value. # @yieldparam value [Integer] the current value # @yieldreturn [BasicObject] a truthy value to keep waiting, or nil or false to stop # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the value when the condition becomes false, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_while(timeout: nil) raise LocalJumpError, "no block given" unless block_given? wait_until(timeout:) { |value| !yield(value) } end # Wait while `object === value` is true. # @param object [#===] the pattern to stop matching # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first nonmatching value, or nil on timeout def wait_while_match(object, timeout: nil) wait_while(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # (see #wait_until_changed) def wait_while_value(...) = wait_until_changed(...) # Wait until `object == value` is true. # Counters compare integer values by equality. # @param object [BasicObject] the value to compare with the current value # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_value(object, timeout: nil) wait_until(timeout:) { |value| object == value } end # Wait until `object === value` is true. # @param object [#===] the pattern to match # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_match(object, timeout: nil) wait_until(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # Wait until the value is strictly below the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_below(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value < limit } end # Wait until the value is strictly above the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_above(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value > floor } end # Wait while the value is strictly above the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or below the limit, or nil on timeout def wait_while_above(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value <= limit } end # Wait while the value is strictly below the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or above the floor, or nil on timeout def wait_while_below(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value >= floor } end # Reset the counter to its initial value. # @return [self] Returns self for chaining. def reset self.value = initial self end # Increment the counter by one if its current value is below the limit. # The check and increment happen atomically. # @param [Numeric, String, #to_int] limit The upper bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def increment_if_below(limit) # rubocop:disable Naming/PredicateMethod limit = Integer(limit) current = value Internal::Freeze.check(self) while current < limit return true if compare_and_set(current, current + 1) current = value end false end # Decrement the counter by one if its current value is above the floor. # The check and decrement happen atomically. # @param [Numeric, String, #to_int] floor The lower bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def decrement_if_above(floor) # rubocop:disable Naming/PredicateMethod floor = Integer(floor) current = value Internal::Freeze.check(self) while current > floor return true if compare_and_set(current, current - 1) current = value end false end # @overload add(by = 1) # (see #increment) def add(...) = increment(...) methods = Integer.public_instance_methods - Object.public_instance_methods - [:singleton_method_added] methods += %i[+ - / * ** <=> coerce rationalize to_i to_int to_f to_c to_r to_s] methods.uniq! Internal.delegate(self, :value, *methods) # @api private def inspect_with(inspector) super do inspector.breakable inspector.object(value) end end private def method_missing(...) = value.public_send(...) def respond_to_missing?(method, ...) = value.respond_to?(method) end |
#increment_if_below(limit) ⇒ Boolean
Increment the counter by one if its current value is below the limit. The check and increment happen atomically.
173 174 175 176 177 178 179 180 181 182 183 184 185 |
# File 'lib/farce/abstract/counter.rb', line 173 def increment_if_below(limit) # rubocop:disable Naming/PredicateMethod limit = Integer(limit) current = value Internal::Freeze.check(self) while current < limit return true if compare_and_set(current, current + 1) current = value end false end |
#initial ⇒ Integer
Returns The initial value retained by reset and copies.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/farce/abstract/counter.rb', line 27 module Counter include Internal::MarshalSupport::Counter include Internal::Copyable include Internal::Inspect def self.included(base) Internal.prepare_mutable_numeric(base) super end # Numeric's default copying returns self, which is unsuitable for mutable counters. define_method(:dup, Kernel.instance_method(:dup)) define_method(:clone, Kernel.instance_method(:clone)) include Value include Internal::ValueSerialization # Pass the current value to the given block, replacing it with the block's result. # May retry if the value changes during the block's execution. # # Provided for compatibility with `Concurrent::AtomicFixnum`. # # @yield [count] Calculate the new value based on the current value. # @yieldparam count [Integer] the current value of the atomic fixnum. # @yieldreturn [Integer] the new value to replace the current value. # @return [Integer] the new value after the block has been applied. def update raise LocalJumpError, "no block given" unless block_given? while true current = value new_value = yield(current) return new_value if compare_and_set(current, new_value) end end # Wait until the current value differs from the expected value. # @param expected [Integer] the value to wait to change from # @param timeout [Numeric, nil] the total seconds available # @yield called when the timeout expires # @return [Integer, BasicObject, nil] the changed value or timeout fallback def wait_until_changed(expected, timeout: nil) signal = change_signal Internal.with_timeout(timeout) do |_, deadline| observed = signal.generation current = value return current unless expected == current break unless signal.wait(observed, timeout: Clock.remaining(deadline)) end yield if block_given? end # Wait until a block condition matches the current value. # One timeout budget covers all checks and waits. The block is not interrupted. # @yieldparam value [Integer] the current value # @yieldreturn [Boolean] whether the value matches # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_until(timeout: nil, &) = Internal.wait_until(self, timeout:, &) # Wait while the block returns a truthy value. # @yieldparam value [Integer] the current value # @yieldreturn [BasicObject] a truthy value to keep waiting, or nil or false to stop # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the value when the condition becomes false, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_while(timeout: nil) raise LocalJumpError, "no block given" unless block_given? wait_until(timeout:) { |value| !yield(value) } end # Wait while `object === value` is true. # @param object [#===] the pattern to stop matching # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first nonmatching value, or nil on timeout def wait_while_match(object, timeout: nil) wait_while(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # (see #wait_until_changed) def wait_while_value(...) = wait_until_changed(...) # Wait until `object == value` is true. # Counters compare integer values by equality. # @param object [BasicObject] the value to compare with the current value # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_value(object, timeout: nil) wait_until(timeout:) { |value| object == value } end # Wait until `object === value` is true. # @param object [#===] the pattern to match # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_match(object, timeout: nil) wait_until(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # Wait until the value is strictly below the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_below(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value < limit } end # Wait until the value is strictly above the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_above(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value > floor } end # Wait while the value is strictly above the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or below the limit, or nil on timeout def wait_while_above(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value <= limit } end # Wait while the value is strictly below the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or above the floor, or nil on timeout def wait_while_below(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value >= floor } end # Reset the counter to its initial value. # @return [self] Returns self for chaining. def reset self.value = initial self end # Increment the counter by one if its current value is below the limit. # The check and increment happen atomically. # @param [Numeric, String, #to_int] limit The upper bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def increment_if_below(limit) # rubocop:disable Naming/PredicateMethod limit = Integer(limit) current = value Internal::Freeze.check(self) while current < limit return true if compare_and_set(current, current + 1) current = value end false end # Decrement the counter by one if its current value is above the floor. # The check and decrement happen atomically. # @param [Numeric, String, #to_int] floor The lower bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def decrement_if_above(floor) # rubocop:disable Naming/PredicateMethod floor = Integer(floor) current = value Internal::Freeze.check(self) while current > floor return true if compare_and_set(current, current - 1) current = value end false end # @overload add(by = 1) # (see #increment) def add(...) = increment(...) methods = Integer.public_instance_methods - Object.public_instance_methods - [:singleton_method_added] methods += %i[+ - / * ** <=> coerce rationalize to_i to_int to_f to_c to_r to_s] methods.uniq! Internal.delegate(self, :value, *methods) # @api private def inspect_with(inspector) super do inspector.breakable inspector.object(value) end end private def method_missing(...) = value.public_send(...) def respond_to_missing?(method, ...) = value.respond_to?(method) end |
#reset ⇒ self
Reset the counter to its initial value.
164 165 166 167 |
# File 'lib/farce/abstract/counter.rb', line 164 def reset self.value = initial self end |
#update {|count| ... } ⇒ Integer
Pass the current value to the given block, replacing it with the block's result. May retry if the value changes during the block's execution.
Provided for compatibility with Concurrent::AtomicFixnum.
53 54 55 56 57 58 59 60 |
# File 'lib/farce/abstract/counter.rb', line 53 def update raise LocalJumpError, "no block given" unless block_given? while true current = value new_value = yield(current) return new_value if compare_and_set(current, new_value) end end |
#value ⇒ Integer
Returns The current value of the counter.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 |
# File 'lib/farce/abstract/counter.rb', line 27 module Counter include Internal::MarshalSupport::Counter include Internal::Copyable include Internal::Inspect def self.included(base) Internal.prepare_mutable_numeric(base) super end # Numeric's default copying returns self, which is unsuitable for mutable counters. define_method(:dup, Kernel.instance_method(:dup)) define_method(:clone, Kernel.instance_method(:clone)) include Value include Internal::ValueSerialization # Pass the current value to the given block, replacing it with the block's result. # May retry if the value changes during the block's execution. # # Provided for compatibility with `Concurrent::AtomicFixnum`. # # @yield [count] Calculate the new value based on the current value. # @yieldparam count [Integer] the current value of the atomic fixnum. # @yieldreturn [Integer] the new value to replace the current value. # @return [Integer] the new value after the block has been applied. def update raise LocalJumpError, "no block given" unless block_given? while true current = value new_value = yield(current) return new_value if compare_and_set(current, new_value) end end # Wait until the current value differs from the expected value. # @param expected [Integer] the value to wait to change from # @param timeout [Numeric, nil] the total seconds available # @yield called when the timeout expires # @return [Integer, BasicObject, nil] the changed value or timeout fallback def wait_until_changed(expected, timeout: nil) signal = change_signal Internal.with_timeout(timeout) do |_, deadline| observed = signal.generation current = value return current unless expected == current break unless signal.wait(observed, timeout: Clock.remaining(deadline)) end yield if block_given? end # Wait until a block condition matches the current value. # One timeout budget covers all checks and waits. The block is not interrupted. # @yieldparam value [Integer] the current value # @yieldreturn [Boolean] whether the value matches # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_until(timeout: nil, &) = Internal.wait_until(self, timeout:, &) # Wait while the block returns a truthy value. # @yieldparam value [Integer] the current value # @yieldreturn [BasicObject] a truthy value to keep waiting, or nil or false to stop # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the value when the condition becomes false, or nil on timeout # @raise [LocalJumpError] if no block is given def wait_while(timeout: nil) raise LocalJumpError, "no block given" unless block_given? wait_until(timeout:) { |value| !yield(value) } end # Wait while `object === value` is true. # @param object [#===] the pattern to stop matching # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first nonmatching value, or nil on timeout def wait_while_match(object, timeout: nil) wait_while(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # (see #wait_until_changed) def wait_while_value(...) = wait_until_changed(...) # Wait until `object == value` is true. # Counters compare integer values by equality. # @param object [BasicObject] the value to compare with the current value # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_value(object, timeout: nil) wait_until(timeout:) { |value| object == value } end # Wait until `object === value` is true. # @param object [#===] the pattern to match # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_match(object, timeout: nil) wait_until(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end # Wait until the value is strictly below the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_below(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value < limit } end # Wait until the value is strictly above the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the matching value, or nil on timeout def wait_until_above(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value > floor } end # Wait while the value is strictly above the limit. # @param limit [Numeric, String, #to_int] the upper bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or below the limit, or nil on timeout def wait_while_above(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value <= limit } end # Wait while the value is strictly below the floor. # @param floor [Numeric, String, #to_int] the lower bound, converted to an Integer # @param timeout [Numeric, nil] the total seconds available # @return [Integer, nil] the first value at or above the floor, or nil on timeout def wait_while_below(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value >= floor } end # Reset the counter to its initial value. # @return [self] Returns self for chaining. def reset self.value = initial self end # Increment the counter by one if its current value is below the limit. # The check and increment happen atomically. # @param [Numeric, String, #to_int] limit The upper bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def increment_if_below(limit) # rubocop:disable Naming/PredicateMethod limit = Integer(limit) current = value Internal::Freeze.check(self) while current < limit return true if compare_and_set(current, current + 1) current = value end false end # Decrement the counter by one if its current value is above the floor. # The check and decrement happen atomically. # @param [Numeric, String, #to_int] floor The lower bound, converted to an Integer. # @return [Boolean] true if the counter changed, otherwise false. def decrement_if_above(floor) # rubocop:disable Naming/PredicateMethod floor = Integer(floor) current = value Internal::Freeze.check(self) while current > floor return true if compare_and_set(current, current - 1) current = value end false end # @overload add(by = 1) # (see #increment) def add(...) = increment(...) methods = Integer.public_instance_methods - Object.public_instance_methods - [:singleton_method_added] methods += %i[+ - / * ** <=> coerce rationalize to_i to_int to_f to_c to_r to_s] methods.uniq! Internal.delegate(self, :value, *methods) # @api private def inspect_with(inspector) super do inspector.breakable inspector.object(value) end end private def method_missing(...) = value.public_send(...) def respond_to_missing?(method, ...) = value.respond_to?(method) end |
#wait_until(timeout: nil) {|value| ... } ⇒ Integer?
Wait until a block condition matches the current value. One timeout budget covers all checks and waits. The block is not interrupted.
85 |
# File 'lib/farce/abstract/counter.rb', line 85 def wait_until(timeout: nil, &) = Internal.wait_until(self, timeout:, &) |
#wait_until_above(floor, timeout: nil) ⇒ Integer?
Wait until the value is strictly above the floor.
139 140 141 142 |
# File 'lib/farce/abstract/counter.rb', line 139 def wait_until_above(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value > floor } end |
#wait_until_below(limit, timeout: nil) ⇒ Integer?
Wait until the value is strictly below the limit.
130 131 132 133 |
# File 'lib/farce/abstract/counter.rb', line 130 def wait_until_below(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value < limit } end |
#wait_until_changed(expected, timeout: nil) { ... } ⇒ Integer, ...
Wait until the current value differs from the expected value.
67 68 69 70 71 72 73 74 75 76 |
# File 'lib/farce/abstract/counter.rb', line 67 def wait_until_changed(expected, timeout: nil) signal = change_signal Internal.with_timeout(timeout) do |_, deadline| observed = signal.generation current = value return current unless expected == current break unless signal.wait(observed, timeout: Clock.remaining(deadline)) end yield if block_given? end |
#wait_until_match(object, timeout: nil) ⇒ Integer?
Wait until object === value is true.
122 123 124 |
# File 'lib/farce/abstract/counter.rb', line 122 def wait_until_match(object, timeout: nil) wait_until(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end |
#wait_until_value(object, timeout: nil) ⇒ Integer?
Wait until object == value is true.
Counters compare integer values by equality.
114 115 116 |
# File 'lib/farce/abstract/counter.rb', line 114 def wait_until_value(object, timeout: nil) wait_until(timeout:) { |value| object == value } end |
#wait_while(timeout: nil) {|value| ... } ⇒ Integer?
Wait while the block returns a truthy value.
93 94 95 96 |
# File 'lib/farce/abstract/counter.rb', line 93 def wait_while(timeout: nil) raise LocalJumpError, "no block given" unless block_given? wait_until(timeout:) { |value| !yield(value) } end |
#wait_while_above(limit, timeout: nil) ⇒ Integer?
Wait while the value is strictly above the limit.
148 149 150 151 |
# File 'lib/farce/abstract/counter.rb', line 148 def wait_while_above(limit, timeout: nil) limit = Integer(limit) wait_until(timeout:) { |value| value <= limit } end |
#wait_while_below(floor, timeout: nil) ⇒ Integer?
Wait while the value is strictly below the floor.
157 158 159 160 |
# File 'lib/farce/abstract/counter.rb', line 157 def wait_while_below(floor, timeout: nil) floor = Integer(floor) wait_until(timeout:) { |value| value >= floor } end |
#wait_while_match(object, timeout: nil) ⇒ Integer?
Wait while object === value is true.
102 103 104 |
# File 'lib/farce/abstract/counter.rb', line 102 def wait_while_match(object, timeout: nil) wait_while(timeout:) { |value| object === value } # rubocop:disable Style/CaseEquality end |
#wait_while_value { ... } ⇒ Integer, ...
Wait until the current value differs from the expected value.
107 |
# File 'lib/farce/abstract/counter.rb', line 107 def wait_while_value(...) = wait_until_changed(...) |