class ThreadSafe::AtomicReferenceCacheBackend::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+.
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/thread_safe/atomic_reference_cache_backend.rb, line 243 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/thread_safe/atomic_reference_cache_backend.rb, line 326 def locked_hash?(hash) (hash & LOCKED) != 0 end
Public Instance Methods
# File lib/thread_safe/atomic_reference_cache_backend.rb, line 281 def key?(key) @key.eql?(key) end
# File lib/thread_safe/atomic_reference_cache_backend.rb, line 303 def locked? self.class.locked_hash?(hash) end
# File lib/thread_safe/atomic_reference_cache_backend.rb, line 285 def matches?(key, hash) pure_hash == hash && key?(key) end
# File lib/thread_safe/atomic_reference_cache_backend.rb, line 289 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/thread_safe/atomic_reference_cache_backend.rb, line 260 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 = 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 = Util::XorShiftRandom.xorshift(base_randomizer) if randomizer.zero? end end elsif cas_hash(my_hash, my_hash | WAITING) force_aquire_lock(table, i) break end end end end
# File lib/thread_safe/atomic_reference_cache_backend.rb, line 293 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/thread_safe/atomic_reference_cache_backend.rb, line 307 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/thread_safe/atomic_reference_cache_backend.rb, line 315 def force_aquire_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