Module: RactorRailsShim::StorageStrategy::Ractor
- Defined in:
- lib/ractor_rails_shim/foundation/storage_strategy.rb
Overview
Rector-mode strategy. Two reader/writer entry points:
* `lookup` / `store` — EXACT-key 3-tier passthrough. Used by
IESAccessor (and anything that hands over
a fully-qualified storage key). No ancestor
walk; the caller owns the key.
* `lookup_by_attr` / `store_by_attr` — per-receiver class_attribute
semantics: derive a storage key from the
receiver's class + the attribute name, then
walk the ancestor chain so each class keeps
its OWN class_attribute value (siblings no
longer clobber each other). Backed by the
Ractor backends (IES + SHAREABLE_FALLBACK
+ CLASS_ATTR_VALUES[main]).
Class Method Summary collapse
-
.lookup(owner, key, missing_default) ⇒ Object
Exact-key 3-tier passthrough (IES → SHAREABLE_FALLBACK → CLASS_ATTR_VALUES).
-
.lookup_by_attr(owner, attr_sym, missing_default) ⇒ Object
Per-receiver class_attribute lookup: derive the receiver's storage key (memoized via
StorageStrategy.ca_key) and walk ancestors so subclasses inherit parent values while keeping their own overrides. -
.lookup_resolved(owner, attr_sym, missing_default, resolved_key) ⇒ Object
Zero-allocation hot reader.
-
.replay_callbacks!(context, kind, &block) ⇒ Object
Shared callback-replay logic used by both the "always" path (Thread mode) and the "on-empty" path (Ractor mode).
-
.replay_callbacks?(callbacks) ⇒ Boolean
Ractor mode: replay captured callbacks only when __callbacks is empty (the worker-Ractor case — workers get the empty default).
- .store(owner, key, value) ⇒ Object
- .store_by_attr(owner, attr_sym, value) ⇒ Object
-
.store_resolved(owner, attr_sym, value, resolved_key) ⇒ Object
Zero-allocation hot writer.
Class Method Details
.lookup(owner, key, missing_default) ⇒ Object
Exact-key 3-tier passthrough (IES → SHAREABLE_FALLBACK →
CLASS_ATTR_VALUES). Contract: key IS the storage key.
78 79 80 81 82 83 84 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 78 def lookup(owner, key, missing_default) v = RactorRailsShim.storage[key] return v if RactorRailsShim.storage.key?(key) fb = RactorRailsShim::Registry.shareable_fallback[key] return fb unless fb.nil? RactorRailsShim::Registry.class_attr_values[key] if ::Ractor.main? end |
.lookup_by_attr(owner, attr_sym, missing_default) ⇒ Object
Per-receiver class_attribute lookup: derive the receiver's storage
key (memoized via StorageStrategy.ca_key) and walk ancestors so
subclasses inherit parent values while keeping their own overrides.
ancestors (not superclass) is required so module-declared
class_attributes (e.g. AbstractController::Callbacks#__callbacks)
are reachable — superclass skips included modules.
Per-ancestor tier order:
1. `storage` (IES) — current execution context's override
(workers set their own value here).
2. `shareable_fallback` — frozen shareable table built at
prepare_for_ractors! (worker reads).
3. `class_attr_values` — the persistent seed registry (main
only). IES is execution-context
ISOLATED, so a seed written during
boot is invisible in a later context;
`class_attr_values` is a plain Hash
that survives across contexts, so it
MUST be consulted per-ancestor (not
just for the final receiver) — otherwise
inherited values (e.g. a parent's
`__callbacks`) are missed.
114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 114 def lookup_by_attr(owner, attr_sym, missing_default) klass = owner.is_a?(Module) ? owner : owner.class klass.ancestors.each do |anc| kkey = RactorRailsShim::StorageStrategy.ca_key(anc, attr_sym) v = RactorRailsShim.storage[kkey] return v if RactorRailsShim.storage.key?(kkey) fb = RactorRailsShim::Registry.shareable_fallback[kkey] return fb unless fb.nil? if ::Ractor.main? cv = RactorRailsShim::Registry.class_attr_values[kkey] return cv unless cv.nil? end end missing_default end |
.lookup_resolved(owner, attr_sym, missing_default, resolved_key) ⇒ Object
Zero-allocation hot reader. resolved_key is a literal symbol baked
into the generated reader method (see _class_attr_methods). The
first read for a receiver resolves via lookup_by_attr (ancestor
walk) and caches the result in ractor-local IES keyed by receiver
object_id; every subsequent read is a literal-key Hash lookup +
integer index — no Array/ancestors allocation.
143 144 145 146 147 148 149 150 151 152 153 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 143 def lookup_resolved(owner, attr_sym, missing_default, resolved_key) klass = owner.is_a?(Module) ? owner : owner.class oid = klass.object_id cache = RactorRailsShim.storage[resolved_key] return cache[oid] if cache && cache.key?(oid) v = lookup_by_attr(owner, attr_sym, missing_default) cache = RactorRailsShim.storage[resolved_key] cache ||= (RactorRailsShim.storage[resolved_key] = {}) cache[oid] = v v end |
.replay_callbacks!(context, kind, &block) ⇒ Object
Shared callback-replay logic used by both the "always" path
(Thread mode) and the "on-empty" path (Ractor mode). Walks the
SHAREABLE_DECLARED_CALLBACKS table and replays captured symbolic
filters (before/after) that apply to the current action.
Delegate callback replay to the generalized transport layer
(RactorRailsShim::Callbacks::Registry). The registry owns the single
yield and dispatches to every applicable transport (the symbolic
filter transport for any kind + the dependent-association transport
for :destroy). Kept as a thin delegator so the storage-strategy
contract (replay_callbacks? / replay_callbacks!) stays intact and the
Thread strategy can keep mirroring it. The run_callbacks nil-safe
patch calls the registry directly, but external callers/tests may
still reach the strategy.
190 191 192 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 190 def replay_callbacks!(context, kind, &block) ::RactorRailsShim::Callbacks.registry.replay(context, kind, &block) end |
.replay_callbacks?(callbacks) ⇒ Boolean
Ractor mode: replay captured callbacks only when __callbacks is empty (the worker-Ractor case — workers get the empty default). In the main Ractor, __callbacks is live and replay is skipped.
173 174 175 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 173 def replay_callbacks?(callbacks) callbacks.nil? || callbacks.empty? end |
.store(owner, key, value) ⇒ Object
86 87 88 89 90 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 86 def store(owner, key, value) RactorRailsShim.storage[key] = value RactorRailsShim::Registry.class_attr_values[key] = value if ::Ractor.main? value end |
.store_by_attr(owner, attr_sym, value) ⇒ Object
130 131 132 133 134 135 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 130 def store_by_attr(owner, attr_sym, value) kkey = RactorRailsShim::StorageStrategy.ca_key(owner, attr_sym) RactorRailsShim.storage[kkey] = value RactorRailsShim::Registry.class_attr_values[kkey] = value if ::Ractor.main? value end |
.store_resolved(owner, attr_sym, value, resolved_key) ⇒ Object
Zero-allocation hot writer. Writes the per-receiver slot (so the
ancestor-walk lookup_by_attr stays correct) AND updates the resolved
cache for this receiver so the next read is hot.
158 159 160 161 162 163 164 165 166 167 168 |
# File 'lib/ractor_rails_shim/foundation/storage_strategy.rb', line 158 def store_resolved(owner, attr_sym, value, resolved_key) kkey = RactorRailsShim::StorageStrategy.ca_key(owner, attr_sym) RactorRailsShim.storage[kkey] = value RactorRailsShim::Registry.class_attr_values[kkey] = value if ::Ractor.main? klass = owner.is_a?(Module) ? owner : owner.class oid = klass.object_id cache = RactorRailsShim.storage[resolved_key] cache ||= (RactorRailsShim.storage[resolved_key] = {}) cache[oid] = value value end |