Class: Cordis::Fiber

Inherits:
Object
  • Object
show all
Defined in:
lib/cordis/fiber.rb

Overview

Fiber (upstream 4.0's new name for EffectScope): the lifecycle unit of a plugin, and also the collector of revertible effects (the paper's phi accumulator). Note: the clash with Ruby core's ::Fiber is deliberate, to match upstream naming; Cordis never uses core Fiber directly.

Defined Under Namespace

Classes: Entry

Constant Summary collapse

INACTIVE =
:__inactive__

Instance Attribute Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent_ctx, config: nil, inject: {}, runtime: nil) ⇒ Fiber

Returns a new instance of Fiber.



18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# File 'lib/cordis/fiber.rb', line 18

def initialize(parent_ctx, config: nil, inject: {}, runtime: nil)
  @config = config
  @inject = inject
  @runtime = runtime
  @disposables = []
  @provided = {} # name => isolate key (for boundary-crossing notify on state change)
  @internal_store = {} # currently satisfied dependencies (name => Impl), continuously updated
  @store = nil         # snapshot taken while loading; nil means unloaded
  @current_epoch = INACTIVE
  @target_epoch = INACTIVE
  @inertia = nil       # in-flight load/unload transition task
  @error = nil
  @state = :pending

  if runtime.nil? # root fiber
    @uid = 0
    @ctx = parent_ctx
    @parent_fiber = nil
    @current_epoch = @target_epoch = ''
    @store = {}
    @state = :active
    return
  end

  @uid = parent_ctx.root.registry.next_uid
  @parent_fiber = parent_ctx.fiber
  @parent_ctx = parent_ctx
  @ctx = parent_ctx.extend(fiber: self)
  # inject with a Hash config doubles as an intercept entry on the plugin's ctx
  # (mirrors upstream fiber.ts:139)
  inject.each { |dep, cfg| @ctx = @ctx.intercept(dep, cfg) if cfg.is_a?(Hash) }
  runtime.fibers << self
  @ctx.events.emit('internal/plugin', self)
  inject.each_key { |name| check_impl(name) }
  # a plugin is itself a revertible effect on the parent fiber (mirrors upstream fiber.ts:170)
  @entry = parent_ctx.fiber.effect('ctx.plugin()') do
    refresh
    -> { terminate }
  end
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def config
  @config
end

#ctxObject (readonly)

Returns the value of attribute ctx.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def ctx
  @ctx
end

#errorObject (readonly)

Returns the value of attribute error.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def error
  @error
end

#injectObject (readonly)

Returns the value of attribute inject.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def inject
  @inject
end

#parent_ctxObject (readonly)

Returns the value of attribute parent_ctx.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def parent_ctx
  @parent_ctx
end

#parent_fiberObject (readonly)

Returns the value of attribute parent_fiber.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def parent_fiber
  @parent_fiber
end

#providedObject (readonly)

Returns the value of attribute provided.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def provided
  @provided
end

#runtimeObject (readonly)

Returns the value of attribute runtime.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def runtime
  @runtime
end

#stateObject (readonly)

Returns the value of attribute state.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def state
  @state
end

#storeObject (readonly)

Returns the value of attribute store.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def store
  @store
end

#uidObject (readonly)

Returns the value of attribute uid.



15
16
17
# File 'lib/cordis/fiber.rb', line 15

def uid
  @uid
end

Instance Method Details

#assert_activeObject



84
85
86
87
88
# File 'lib/cordis/fiber.rb', line 84

def assert_active
  return if @store && %i[loading active].include?(@state)

  raise InactiveEffectError, 'cannot create effect on inactive context'
end

#awaitObject

Wait for all in-flight transitions to finish (while, not if: transitions chain).

Raises:



91
92
93
94
95
96
97
98
# File 'lib/cordis/fiber.rb', line 91

def await
  while (task = @inertia)
    task.wait
  end
  raise @error if @error

  self
end

#check_impl(name) ⇒ Object

-- coeffect machinery (dependency-graph updates are fully synchronous; only load/unload execution is async) --



123
124
125
126
127
128
129
130
# File 'lib/cordis/fiber.rb', line 123

def check_impl(name)
  impl = @ctx.root.services[@ctx.isolate_key(name)]
  if impl && impl.fiber.state == :active
    @internal_store[name] = impl
  else
    @internal_store.delete(name)
  end
end

#disposeObject



100
101
102
103
104
105
# File 'lib/cordis/fiber.rb', line 100

def dispose
  return restart if root?

  @entry.call
  nil
end

#disposed?Boolean

Returns:

  • (Boolean)


60
# File 'lib/cordis/fiber.rb', line 60

def disposed? = @uid.nil?

#effect(label = nil, &block) ⇒ Object

Apply a side effect and collect its inverse. The block returns: nil (nothing), a callable (single disposer), or an Array/Enumerator of callables (multi-step disposers, reverted in reverse order — upstream's function* effects). If setup raises midway, already-collected disposers are rolled back in reverse, then re-raised.



72
73
74
75
76
77
78
79
80
81
82
# File 'lib/cordis/fiber.rb', line 72

def effect(label = nil, &block)
  assert_active
  disposers = collect_disposers(block, rollback: true)
  entry = Entry.new(disposers, label)
  @disposables << entry
  lambda do
    next unless @disposables.reject! { |e| e.equal?(entry) }

    entry.disposers.reverse_each(&:call)
  end
end

#nameObject



62
63
64
65
66
# File 'lib/cordis/fiber.rb', line 62

def name
  return 'root' if root?

  @runtime.name || 'plugin'
end

#refreshObject



132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/cordis/fiber.rb', line 132

def refresh
  epoch = ''
  @inject.each_key do |name|
    impl = @internal_store[name]
    if impl.nil?
      epoch = INACTIVE
      break
    end
    epoch = "#{epoch}:#{impl.fiber.uid}"
  end
  set_epoch(epoch)
end

#restartObject



107
108
109
110
111
112
113
# File 'lib/cordis/fiber.rb', line 107

def restart
  set_epoch(INACTIVE)
  await # wait for the unload first (refreshing right away would set the target back
  # to the original epoch and coalesce the unload intent away)
  refresh
  await
end

#root?Boolean

Returns:

  • (Boolean)


59
# File 'lib/cordis/fiber.rb', line 59

def root? = @runtime.nil?

#update(config) ⇒ Object



115
116
117
118
# File 'lib/cordis/fiber.rb', line 115

def update(config)
  @config = config
  restart
end