Class: Cordis::Context
- Inherits:
-
Object
- Object
- Cordis::Context
- Defined in:
- lib/cordis/context.rb
Overview
Thin shell: root bootstrap, service access (the paper's coeffect context), and delegation to the plugin/event entry points. Breaking change (round 2): extend now matches upstream semantics — it creates a "same root, different fiber" view of the ctx, not a new disposal scope; new scopes always come from ctx.plugin.
Defined Under Namespace
Classes: Impl
Instance Attribute Summary collapse
-
#events ⇒ Object
readonly
Returns the value of attribute events.
-
#fiber ⇒ Object
readonly
Returns the value of attribute fiber.
-
#registry ⇒ Object
readonly
Returns the value of attribute registry.
-
#root ⇒ Object
readonly
Returns the value of attribute root.
-
#services ⇒ Object
readonly
Returns the value of attribute services.
Instance Method Summary collapse
- #bail(name) ⇒ Object
-
#effect(label = nil) ⇒ Object
-- effect / plugin --.
- #emit(name) ⇒ Object
-
#extend(fiber: @fiber) ⇒ Object
Internal: a ctx view bound to another fiber (sharing root/registry/events/services).
-
#get(name) ⇒ Object
Strict: the provider fiber must be ACTIVE to be visible (a service whose provider is still loading is invisible to dependents).
-
#initialize ⇒ Context
constructor
A new instance of Context.
- #inject(deps, &block) ⇒ Object
-
#intercept(name, config) ⇒ Object
A ctx view carrying per-caller config for service
name(upstream ctx.intercept). -
#isolate(name, label = nil) ⇒ Object
A ctx view where service
namelives in its own realm: provides/injects through this view no longer see (or are seen by) the default realm. -
#isolate_key(name) ⇒ Object
The store key for
namein this ctx's realm. -
#method_missing(name, *args, &block) ⇒ Object
Service access: walk the fiber parent chain looking at snapshots (error messages match upstream).
-
#notify(pairs) ⇒ Object
Dependency-graph update (fully synchronous): linear scan over all fibers, re-checking satisfaction and recomputing epochs.
-
#on(name, prepend: false) ⇒ Object
-- events --.
- #once(name, prepend: false) ⇒ Object
- #parallel(name) ⇒ Object
- #plugin(plugin, config = nil) ⇒ Object
-
#provide(name, value = nil) ⇒ Object
Providing a service is itself a revertible effect.
-
#resolve_config(name, base = nil) ⇒ Object
Effective intercept config for
nameas seen from this ctx, merged overbase(upstream Service, minus the Config schema merge). - #respond_to_missing?(name, include_private = false) ⇒ Boolean
- #serial(name) ⇒ Object
- #set(name, value) ⇒ Object
- #waterfall(name) ⇒ Object
Constructor Details
#initialize ⇒ Context
Returns a new instance of Context.
14 15 16 17 18 19 20 21 22 |
# File 'lib/cordis/context.rb', line 14 def initialize @root = self @services = {} # isolate key => Impl (default key is the name itself) @isolate = {} # name => isolation label; absent = the shared default realm @intercept = {} # name => per-caller config (copy-on-write, chain pre-merged) @fiber = Fiber.new(self) # root fiber: always active, dispose = restart @registry = Registry.new(self) @events = Events.new(self) end |
Dynamic Method Handling
This class handles dynamic methods through the method_missing method
#method_missing(name, *args, &block) ⇒ Object
Service access: walk the fiber parent chain looking at snapshots (error messages match upstream).
154 155 156 157 158 |
# File 'lib/cordis/context.rb', line 154 def method_missing(name, *args, &block) return super unless args.empty? && block.nil? resolve_service(name) end |
Instance Attribute Details
#events ⇒ Object (readonly)
Returns the value of attribute events.
12 13 14 |
# File 'lib/cordis/context.rb', line 12 def events @events end |
#fiber ⇒ Object (readonly)
Returns the value of attribute fiber.
12 13 14 |
# File 'lib/cordis/context.rb', line 12 def fiber @fiber end |
#registry ⇒ Object (readonly)
Returns the value of attribute registry.
12 13 14 |
# File 'lib/cordis/context.rb', line 12 def registry @registry end |
#root ⇒ Object (readonly)
Returns the value of attribute root.
12 13 14 |
# File 'lib/cordis/context.rb', line 12 def root @root end |
#services ⇒ Object (readonly)
Returns the value of attribute services.
12 13 14 |
# File 'lib/cordis/context.rb', line 12 def services @services end |
Instance Method Details
#bail(name) ⇒ Object
77 |
# File 'lib/cordis/context.rb', line 77 def bail(name, *) = @root.events.bail(name, *) |
#effect(label = nil) ⇒ Object
-- effect / plugin --
65 |
# File 'lib/cordis/context.rb', line 65 def effect(label = nil, &) = @fiber.effect(label, &) |
#emit(name) ⇒ Object
76 |
# File 'lib/cordis/context.rb', line 76 def emit(name, *) = @root.events.emit(name, *) |
#extend(fiber: @fiber) ⇒ Object
Internal: a ctx view bound to another fiber (sharing root/registry/events/services).
25 26 27 28 29 |
# File 'lib/cordis/context.rb', line 25 def extend(fiber: @fiber) ctx = dup ctx.instance_variable_set(:@fiber, fiber) ctx end |
#get(name) ⇒ Object
Strict: the provider fiber must be ACTIVE to be visible (a service whose provider is still loading is invisible to dependents).
116 117 118 119 120 121 |
# File 'lib/cordis/context.rb', line 116 def get(name) impl = @root.services[isolate_key(name.to_sym)] return nil unless impl && impl.fiber.state == :active impl.value end |
#inject(deps, &block) ⇒ Object
68 69 70 |
# File 'lib/cordis/context.rb', line 68 def inject(deps, &block) plugin({ inject: deps, apply: block, name: 'inject' }) end |
#intercept(name, config) ⇒ Object
A ctx view carrying per-caller config for service name (upstream ctx.intercept).
Nested intercepts merge, inner overriding outer (Hash configs only; anything else replaces).
42 43 44 45 46 47 48 49 |
# File 'lib/cordis/context.rb', line 42 def intercept(name, config) name = name.to_sym old = @intercept[name] merged = old.is_a?(Hash) && config.is_a?(Hash) ? old.merge(config) : config ctx = extend ctx.instance_variable_set(:@intercept, @intercept.merge(name => merged)) ctx end |
#isolate(name, label = nil) ⇒ Object
A ctx view where service name lives in its own realm: provides/injects through
this view no longer see (or are seen by) the default realm. Pass the same label
to two isolate calls to share one realm between them.
34 35 36 37 38 |
# File 'lib/cordis/context.rb', line 34 def isolate(name, label = nil) ctx = extend ctx.instance_variable_set(:@isolate, @isolate.merge(name.to_sym => label || Object.new)) ctx end |
#isolate_key(name) ⇒ Object
The store key for name in this ctx's realm.
61 |
# File 'lib/cordis/context.rb', line 61 def isolate_key(name) = @isolate[name] || name |
#notify(pairs) ⇒ Object
Dependency-graph update (fully synchronous): linear scan over all fibers, re-checking satisfaction and recomputing epochs. Only fibers whose ctx resolves the name to the same isolate key are touched. Takes { name => key } pairs and returns the touched fibers (provide's teardown uses the list to wait for dependents).
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 |
# File 'lib/cordis/context.rb', line 136 def notify(pairs) touched = [] @registry.runtimes.each do |runtime| runtime.fibers.each do |fib| hits = pairs.select { |n, key| fib.inject.key?(n) && fib.ctx.isolate_key(n) == key } next if hits.empty? hits.each_key { |n| fib.check_impl(n) } fib.refresh touched << fib end end pairs.each_key { |n| @events.emit('internal/service', n) } touched end |
#on(name, prepend: false) ⇒ Object
-- events --
74 |
# File 'lib/cordis/context.rb', line 74 def on(name, prepend: false, &) = @root.events.register(self, name, prepend: prepend, &) |
#once(name, prepend: false) ⇒ Object
75 |
# File 'lib/cordis/context.rb', line 75 def once(name, prepend: false, &) = @root.events.once(self, name, prepend: prepend, &) |
#parallel(name) ⇒ Object
79 |
# File 'lib/cordis/context.rb', line 79 def parallel(name, *) = @root.events.parallel(name, *) |
#plugin(plugin, config = nil) ⇒ Object
66 |
# File 'lib/cordis/context.rb', line 66 def plugin(plugin, config = nil) = @root.registry.plugin(self, plugin, config) |
#provide(name, value = nil) ⇒ Object
Providing a service is itself a revertible effect. Teardown ordering guarantee: remove from the store -> notify -> wait for every dependent to finish unloading -> only then delete the provider's self-visible entry (so dependents' disposers can still reach the service during their own teardown).
88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 |
# File 'lib/cordis/context.rb', line 88 def provide(name, value = nil) name = name.to_sym key = isolate_key(name) owner = @fiber @fiber.effect("ctx.provide(#{name.inspect})") do raise ServiceError, "service #{name.inspect} has already been registered" if @root.services.key?(key) impl = Impl.new(name, owner, value) @root.services[key] = impl owner.store[name] = impl # immediately visible to the provider itself owner.provided[name] = key @root.notify({ name => key }) if owner.state == :active lambda do @root.services.delete(key) dependents = @root.notify({ name => key }) dependents.each do |dep| dep.await rescue StandardError nil # allSettled semantics: one dependent failing to unload never blocks the rest end owner.store&.delete(name) owner.provided.delete(name) end end end |
#resolve_config(name, base = nil) ⇒ Object
Effective intercept config for name as seen from this ctx, merged over base
(upstream Service, minus the Config schema merge).
53 54 55 56 57 58 |
# File 'lib/cordis/context.rb', line 53 def resolve_config(name, base = nil) config = @intercept[name.to_sym] return base if config.nil? base.is_a?(Hash) && config.is_a?(Hash) ? base.merge(config) : config end |
#respond_to_missing?(name, include_private = false) ⇒ Boolean
160 161 162 |
# File 'lib/cordis/context.rb', line 160 def respond_to_missing?(name, include_private = false) @root.services.key?(isolate_key(name)) || super end |
#serial(name) ⇒ Object
78 |
# File 'lib/cordis/context.rb', line 78 def serial(name, *) = @root.events.serial(name, *) |
#set(name, value) ⇒ Object
123 124 125 126 127 128 129 130 |
# File 'lib/cordis/context.rb', line 123 def set(name, value) name = name.to_sym impl = @root.services[isolate_key(name)] raise ServiceError, %(cannot set property "#{name}" without provide) unless impl raise ServiceError, %(cannot set property "#{name}" in multiple fibers) unless impl.fiber.equal?(@fiber) impl.value = value end |
#waterfall(name) ⇒ Object
80 |
# File 'lib/cordis/context.rb', line 80 def waterfall(name, *, &) = @root.events.waterfall(name, *, &) |