Class: Trane::Registry::Instance
- Inherits:
-
Object
- Object
- Trane::Registry::Instance
- Defined in:
- lib/trane/registry.rb
Overview
Trane registry. Owns its own snapshot ivar and replacement mutex; instances are independent of one another.
Constant Summary collapse
- ACTIVE_BUILDERS_KEY =
Single process-wide thread-local key holding a Hash of { instance object_id => active SnapshotBuilder } for the builders opened on the current thread. Keyed per instance so a builder is only visible to
register_*calls on the same instance AND the same thread; entries are deleted inreplace!'s ensure, so the table is empty between calls. One constant Symbol replaces the previous per-instance dynamic symbols, which pinned a symbol and a thread-local entry per discarded instance for the thread's lifetime (thread_variable_set(key, nil)does not delete the key). :trane_active_builders- MAX_CACHE_ENTRIES =
Defensive bound for the three object_id-keyed caches below (@compiled_serializers, @validator_field_names, @validator_declared_field_names). Under their documented invariant — keys are objects owned by the current frozen snapshot — the caches are cleared on every snapshot change and stay small (one entry per response/fields object in the registry). The bound only matters if a caller violates the invariant by passing transient objects built per request: past it, results are built without caching (a perf degradation) instead of growing the caches without limit (a leak).
10_000
Instance Method Summary collapse
-
#compiled_serializer_for(response_def, strict_mode) ⇒ Trane::Serializer
Returns a memoized Trane::Serializer for the given ResponseDefinition and strict_mode pair.
- #errors ⇒ Object
-
#errors_by_name ⇒ Object
Returns the frozen FQDN+short-name index built when the snapshot was last replaced.
-
#initialize ⇒ Instance
constructor
A new instance of Instance.
- #operations ⇒ Object
- #register_error(definition) ⇒ Object
-
#register_operation(definition) ⇒ Object
Incremental registration paths.
- #register_representation(definition) ⇒ Object
-
#replace! ⇒ Object
Atomic bulk replacement.
- #representations ⇒ Object
- #reset! ⇒ Object
- #validate! ⇒ Object
-
#validator_declared_field_names_for(fields) ⇒ Array<Symbol>
Cached frozen Array of non-
extra:field names. -
#validator_field_names_for(fields) ⇒ Set<Symbol>
Cached frozen Set of all field names for a given fields collection.
Constructor Details
#initialize ⇒ Instance
Returns a new instance of Instance.
116 117 118 119 120 121 122 |
# File 'lib/trane/registry.rb', line 116 def initialize @snapshot = EMPTY_SNAPSHOT @replace_mutex = Mutex.new @compiled_serializers = {} @validator_field_names = {} @validator_declared_field_names = {} end |
Instance Method Details
#compiled_serializer_for(response_def, strict_mode) ⇒ Trane::Serializer
Returns a memoized Trane::Serializer for the given ResponseDefinition and strict_mode pair. Instances are built lazily on first access and cached until the registry snapshot changes (via replace!, reset!, or any of the register_* copy-on-write paths).
INVARIANT: response_def must be owned by the current snapshot (i.e.
reachable from operations). The cache is keyed by object_id without
holding the object, so it can never notice a dead key; passing
transient objects built per call would grow it until
MAX_CACHE_ENTRIES, after which results are built uncached.
Thread-safety: concurrent first access on the same key may build two Serializers; last write wins. Subsequent reads share the cached instance. Serializer is frozen post-init and safe to share across threads.
236 237 238 239 240 241 242 243 244 |
# File 'lib/trane/registry.rb', line 236 def compiled_serializer_for(response_def, strict_mode) key = [ response_def.object_id, strict_mode ] cached = @compiled_serializers[key] return cached if cached built = Trane::Serializer.new(response_def, self, strict_mode: strict_mode) @compiled_serializers[key] = built if @compiled_serializers.size < MAX_CACHE_ENTRIES built end |
#errors ⇒ Object
132 133 134 |
# File 'lib/trane/registry.rb', line 132 def errors @snapshot[:errors] end |
#errors_by_name ⇒ Object
Returns the frozen FQDN+short-name index built when the snapshot was last replaced. Keys are Strings; values are Trane::ErrorDefinition instances.
138 139 140 |
# File 'lib/trane/registry.rb', line 138 def errors_by_name @snapshot[:errors_by_name] end |
#operations ⇒ Object
124 125 126 |
# File 'lib/trane/registry.rb', line 124 def operations @snapshot[:operations] end |
#register_error(definition) ⇒ Object
195 196 197 198 199 200 201 202 203 204 205 206 |
# File 'lib/trane/registry.rb', line 195 def register_error(definition) b = active_builder return b.register_error(definition) if b @replace_mutex.synchronize do s = @snapshot new_errors = s[:errors].merge(definition.key => definition).freeze new_index = SnapshotBuilder.build_errors_by_name(new_errors) @snapshot = s.merge(errors: new_errors, errors_by_name: new_index).freeze clear_derived_caches end end |
#register_operation(definition) ⇒ Object
Incremental registration paths. Used by specs and any caller
outside a replace! block. Copy-on-write under @replace_mutex,
so concurrent CoW writes are serialised with the same guarantee
as replace!. The active_builder early-return skips the mutex
acquisition when these methods are called from inside a
replace! block — re-acquiring a non-reentrant Mutex from the
same thread would deadlock. For bulk operations, prefer replace!.
173 174 175 176 177 178 179 180 181 182 |
# File 'lib/trane/registry.rb', line 173 def register_operation(definition) b = active_builder return b.register_operation(definition) if b @replace_mutex.synchronize do s = @snapshot @snapshot = s.merge(operations: s[:operations].merge(definition.name => definition).freeze).freeze clear_derived_caches end end |
#register_representation(definition) ⇒ Object
184 185 186 187 188 189 190 191 192 193 |
# File 'lib/trane/registry.rb', line 184 def register_representation(definition) b = active_builder return b.register_representation(definition) if b @replace_mutex.synchronize do s = @snapshot @snapshot = s.merge(representations: s[:representations].merge(definition.name => definition).freeze).freeze clear_derived_caches end end |
#replace! ⇒ Object
Atomic bulk replacement. Use this for reload paths (Railtie's
to_prepare, integration test setup). The block receives a
SnapshotBuilder; on successful completion of the block, the
built snapshot replaces the current one in a single assignment.
If the block raises, the prior snapshot is preserved.
Concurrent writers are serialised by a per-instance mutex; readers remain lock-free.
Nested calls (on the same thread) are not supported and raise.
152 153 154 155 156 157 158 159 160 161 162 163 164 |
# File 'lib/trane/registry.rb', line 152 def replace! raise Trane::Error, "nested Registry.replace! is not supported" if active_builder @replace_mutex.synchronize do builder = SnapshotBuilder.new active_builders[object_id] = builder yield builder @snapshot = builder.freeze_and_build clear_derived_caches ensure active_builders.delete(object_id) end end |
#representations ⇒ Object
128 129 130 |
# File 'lib/trane/registry.rb', line 128 def representations @snapshot[:representations] end |
#reset! ⇒ Object
208 209 210 211 |
# File 'lib/trane/registry.rb', line 208 def reset! @snapshot = EMPTY_SNAPSHOT clear_derived_caches end |
#validate! ⇒ Object
213 214 215 |
# File 'lib/trane/registry.rb', line 213 def validate! Trane::BootValidator.validate!(self) end |
#validator_declared_field_names_for(fields) ⇒ Array<Symbol>
Cached frozen Array of non-extra: field names. Used by ContractValidator
to detect missing declared keys. Same lifecycle / invalidation /
INVARIANT (snapshot-owned fields only) and MAX_CACHE_ENTRIES bound
as @compiled_serializers.
273 274 275 276 277 278 279 280 |
# File 'lib/trane/registry.rb', line 273 def validator_declared_field_names_for(fields) cached = @validator_declared_field_names[fields.object_id] return cached if cached built = fields.reject(&:extra).map(&:name).freeze @validator_declared_field_names[fields.object_id] = built if @validator_declared_field_names.size < MAX_CACHE_ENTRIES built end |
#validator_field_names_for(fields) ⇒ Set<Symbol>
Cached frozen Set of all field names for a given fields collection.
Used by ContractValidator to detect undeclared keys without per-request
allocation. A Set (not an Array) because the consumer does one
membership test per serialized key: with F fields that is O(F) total
instead of the O(F^2) an Array scan would cost — measurable on every
production response, where the validator runs in :log mode.
Same lifecycle / invalidation / INVARIANT (snapshot-owned fields
only) and MAX_CACHE_ENTRIES bound as @compiled_serializers.
257 258 259 260 261 262 263 264 |
# File 'lib/trane/registry.rb', line 257 def validator_field_names_for(fields) cached = @validator_field_names[fields.object_id] return cached if cached built = Set.new(fields.map(&:name)).freeze @validator_field_names[fields.object_id] = built if @validator_field_names.size < MAX_CACHE_ENTRIES built end |