class Concurrent::Collection::AtomicReferenceMapBackend::Node
Key-value entry. Nodes with a hash field of MOVED
are special, and do not contain user keys or values. Otherwise, keys are never nil
, and NULL
value
fields indicate that a node is in the process of being deleted or created. For purposes of read-only access, a key may be read before a value, but can only be used after checking value to be +!= NULL+.
@!visibility private
Constants
- HASH_BITS
- LOCKED
- MOVED
Encodings for special uses of
Node
hash fields. See above for explanation.- SPIN_LOCK_ATTEMPTS
- WAITING
Attributes
Public Class Methods
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 261 def initialize(hash, key, value, next_node = nil) super() @key = key self.lazy_set_hash(hash) self.lazy_set_value(value) self.next = next_node end
Private Class Methods
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 344 def locked_hash?(hash) (hash & LOCKED) != 0 end
Public Instance Methods
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 299 def key?(key) @key.eql?(key) end
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 321 def locked? self.class.locked_hash?(hash) end
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 303 def matches?(key, hash) pure_hash == hash && key?(key) end
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 307 def pure_hash hash & HASH_BITS end
Spins a while if LOCKED
bit set and this node is the first of its bin, and then sets WAITING
bits on hash field and blocks (once) if they are still set. It is OK for this method to return even if lock is not available upon exit, which enables these simple single-wait mechanics.
The corresponding signalling operation is performed within callers: Upon detecting that WAITING
has been set when unlocking lock (via a failed CAS from non-waiting LOCKED
state), unlockers acquire the cheap_synchronize
lock and perform a cheap_broadcast
.
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 278 def try_await_lock(table, i) if table && i >= 0 && i < table.size # bounds check, TODO: why are we bounds checking? spins = SPIN_LOCK_ATTEMPTS randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.get while equal?(table.volatile_get(i)) && self.class.locked_hash?(my_hash = hash) if spins >= 0 if (randomizer = (randomizer >> 1)).even? # spin at random if (spins -= 1) == 0 Thread.pass # yield before blocking else randomizer = base_randomizer = Concurrent::ThreadSafe::Util::XorShiftRandom.xorshift(base_randomizer) if randomizer.zero? end end elsif cas_hash(my_hash, my_hash | WAITING) force_acquire_lock(table, i) break end end end end
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 311 def try_lock_via_hash(node_hash = hash) if cas_hash(node_hash, locked_hash = node_hash | LOCKED) begin yield ensure unlock_via_hash(locked_hash, node_hash) end end end
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 325 def unlock_via_hash(locked_hash, node_hash) unless cas_hash(locked_hash, node_hash) self.hash = node_hash cheap_synchronize { cheap_broadcast } end end
Private Instance Methods
# File lib/concurrent-ruby/concurrent/collection/map/atomic_reference_map_backend.rb, line 333 def force_acquire_lock(table, i) cheap_synchronize do if equal?(table.volatile_get(i)) && (hash & WAITING) == WAITING cheap_wait else cheap_broadcast # possibly won race vs signaller end end end