diff options
| author | Paul Buetow <paul@buetow.org> | 2026-03-01 23:31:56 +0200 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-03-01 23:31:56 +0200 |
| commit | 1ef38a857c895e5f970d219ba6802b2627801620 (patch) | |
| tree | 3b79793b68ce090e37b80b762124677808b6ddfd /lib/dslkeywords | |
| parent | 1217524955c7ea891ea85cb17dfd13f2b7b1fc3d (diff) | |
refactor: replace ObjectSpace scan with inherited-hook registry
ResourceDependencies#initialize iterated every live object in the Ruby
heap (ObjectSpace.each_object(Class)) to discover Resource subclasses,
which is O(heap), non-deterministic, and load-order-dependent (subclasses
loaded after an instance was created were invisible to it).
Add Resource.inherited + @@subclass_names so each subclass self-registers
at load time. ResourceDependencies#initialize now just assigns the shared
(frozen) registry reference instead of scanning ObjectSpace.
Benefits:
- O(1) constructor path instead of O(heap)
- Load-order safe: new subclasses are visible to all instances immediately
- Deterministic: set is built in require order, not GC-dependent order
Also fix the "recource" typo in the ResourceDependencies comment.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'lib/dslkeywords')
| -rw-r--r-- | lib/dslkeywords/resource.rb | 23 |
1 files changed, 18 insertions, 5 deletions
diff --git a/lib/dslkeywords/resource.rb b/lib/dslkeywords/resource.rb index 143815f..c1c2554 100644 --- a/lib/dslkeywords/resource.rb +++ b/lib/dslkeywords/resource.rb @@ -20,15 +20,14 @@ module RCM end end - # To track recource dependencies + # To track resource dependencies module ResourceDependencies def initialize(...) super(...) @requires = Set.new - @valid_resources = Set.new - ObjectSpace.each_object(Class).each do |klass| - @valid_resources << klass.to_s.sub('RCM::', '').downcase.to_sym if klass < Resource - end + # Use the class-level registry (populated via Resource.inherited) rather + # than scanning ObjectSpace — deterministic, load-order-safe, and O(1). + @valid_resources = Resource.subclass_names end def method_missing(method_name, *args) @@ -93,6 +92,20 @@ module RCM class NoSuchResourceObject < StandardError; end @@resource_find_cache = {} + # Class-level registry: every subclass is registered here when it is + # first loaded (via the inherited hook), so ResourceDependencies can + # look up valid keyword names without scanning ObjectSpace. + @@subclass_names = Set.new + + def self.inherited(subclass) + super + @@subclass_names << subclass.to_s.sub('RCM::', '').downcase.to_sym + end + + # Return a frozen snapshot so callers cannot accidentally mutate the + # shared registry through the @valid_resources instance variable. + def self.subclass_names = @@subclass_names.freeze + def self.find(id) return @@resource_find_cache[id] if @@resource_find_cache.key?(id) |
