Class: Trane::Registry::Instance

Inherits:
Object
  • Object
show all
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 in replace!'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

Constructor Details

#initializeInstance

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.

Parameters:

Returns:



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

#errorsObject



132
133
134
# File 'lib/trane/registry.rb', line 132

def errors
  @snapshot[:errors]
end

#errors_by_nameObject

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

#operationsObject



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.

Raises:



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

#representationsObject



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.

Parameters:

Returns:

  • (Array<Symbol>)

    frozen Array of declared (non-extra) field names



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.

Parameters:

Returns:

  • (Set<Symbol>)

    frozen Set of field names



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