Addon lifecycle boundary: mount on first use, release after idle, keep what is pinned, and release idle parts of what stays.
(def mgr (lifecycle/manager {:host h :specs specs :defaults {:policy :eager} :overrides {"hive.carto" {:policy :lazy :idle-ms 900000}} :surface-store (store/edn-dir-store dir)})) (lifecycle/boot! mgr) ; mounts eager, stubs dormant (lifecycle/install! mgr) ; dormancy oracle + the installed manager (lifecycle/start-sweeper! mgr)
A stub call runs activate!; a real handler runs inside with-use, which
counts it in flight and touches the addon. sweep! evicts what the pure plan
(hive-addon.lifecycle.policy/sweep-plan) allows and closes idle parts.
Activations and evictions are serialized by one reentrant lock. An eviction moves :active to :evicting only while nothing is in flight, and a use only starts on an :active addon, so no call runs on an addon being shut down.
Addon lifecycle boundary: mount on first use, release after idle, keep what is
pinned, and release idle parts of what stays.
(def mgr (lifecycle/manager {:host h :specs specs
:defaults {:policy :eager}
:overrides {"hive.carto" {:policy :lazy :idle-ms 900000}}
:surface-store (store/edn-dir-store dir)}))
(lifecycle/boot! mgr) ; mounts eager, stubs dormant
(lifecycle/install! mgr) ; dormancy oracle + the installed manager
(lifecycle/start-sweeper! mgr)
A stub call runs `activate!`; a real handler runs inside `with-use`, which
counts it in flight and touches the addon. `sweep!` evicts what the pure plan
(hive-addon.lifecycle.policy/sweep-plan) allows and closes idle parts.
Activations and evictions are serialized by one reentrant lock. An eviction
moves :active to :evicting only while nothing is in flight, and a use only
starts on an :active addon, so no call runs on an addon being shut down.(activate! mgr id)Mount ID and every dependency it lacks. Idempotent: an active addon answers :already-active? true. Returns an ActivationReport.
Mount ID and every dependency it lacks. Idempotent: an active addon answers :already-active? true. Returns an ActivationReport.
(adopt! mgr specs)Govern SPECS that are ALREADY mounted (hot inject, a host's own boot) as active addons under the resolved policy — the manifest's :addon/lifecycle under the manager's defaults and overrides. Returns the adopted ids.
An adopted addon that nothing could advertise while dormant cannot be :lazy: it is governed as :eager with :downgraded :no-surface (exactly as boot! does), until a surface is learned for it.
Govern SPECS that are ALREADY mounted (hot inject, a host's own boot) as active addons under the resolved policy — the manifest's :addon/lifecycle under the manager's defaults and overrides. Returns the adopted ids. An adopted addon that nothing could advertise while dormant cannot be :lazy: it is governed as :eager with :downgraded :no-surface (exactly as boot! does), until a surface is learned for it.
(boot! mgr)(boot! mgr {:keys [mount-eager?] :or {mount-eager? true}})Mount what the policy says mounts at boot and stub the rest. OPTS {:mount-eager? false} records eager addons as active without mounting them, for a host that mounted them itself. Returns a BootReport.
Mount what the policy says mounts at boot and stub the rest.
OPTS {:mount-eager? false} records eager addons as active without mounting
them, for a host that mounted them itself. Returns a BootReport.(call-with-use mgr id f)Call F with ID counted in flight. An ungoverned ID runs F untracked. A dormant ID is activated first; when that fails F is not called and the ActivationReport is thrown as ex-info.
Call F with ID counted in flight. An ungoverned ID runs F untracked. A dormant ID is activated first; when that fails F is not called and the ActivationReport is thrown as ex-info.
(current mgr)The Manager now governing MGR's state: the one a later reseat-host! seated, else MGR itself. Resolved at call time, so a caller holding an older Manager value still reaches the current host.
The Manager now governing MGR's state: the one a later reseat-host! seated, else MGR itself. Resolved at call time, so a caller holding an older Manager value still reaches the current host.
(dormant? mgr id)True for an addon this manager governs that is not mounted.
True for an addon this manager governs that is not mounted.
(evict! mgr id)(evict! mgr id {:keys [force?]})Release ID: shut it down, withdraw its surface, advertise stubs. Refuses while
it is in flight, an active addon depends on it, it is pinned, or it has no
surface a stub could advertise (see eviction-guards). OPTS {:force? true}
overrides :pinned and :no-surface. Returns an EvictionReport; a refusal is
:refused? true / :ok? false with its :reason.
Release ID: shut it down, withdraw its surface, advertise stubs. Refuses while
it is in flight, an active addon depends on it, it is pinned, or it has no
surface a stub could advertise (see `eviction-guards`). OPTS {:force? true}
overrides :pinned and :no-surface. Returns an EvictionReport; a refusal is
:refused? true / :ok? false with its :reason.Why an eviction is REFUSED, as an ordered rule table: [reason refuses?] where (refuses? mgr id state force?) answers true to refuse for that reason. The first rule that fires is the report's :reason. A new reason is a new row; do-evict! never changes.
:no-surface refuses a surface-less (e.g. hooks-only) addon: evicted, it would have no stub to route a call back through, so its hooks and whole contribution would vanish and it would never re-mount. :force? overrides it exactly as it overrides :pinned — the operator then owns the consequence.
Why an eviction is REFUSED, as an ordered rule table: [reason refuses?] where (refuses? mgr id state force?) answers true to refuse for that reason. The first rule that fires is the report's :reason. A new reason is a new row; do-evict! never changes. :no-surface refuses a surface-less (e.g. hooks-only) addon: evicted, it would have no stub to route a call back through, so its hooks and whole contribution would vanish and it would never re-mount. :force? overrides it exactly as it overrides :pinned — the operator then owns the consequence.
(forget! mgr ids)Stop governing IDS entirely — the inverse of adopt!, for an addon being plugged OUT. Withdraws any stubs advertised for them and drops their spec, state and cached surface. Does NOT shut anything down: the caller tears the addon down first. Returns the ids that were governed.
Stop governing IDS entirely — the inverse of adopt!, for an addon being plugged OUT. Withdraws any stubs advertised for them and drops their spec, state and cached surface. Does NOT shut anything down: the caller tears the addon down first. Returns the ids that were governed.
(install! mgr)Make MGR the installed manager and the source of the dormancy oracle. MGR is
also SEATED: it becomes current for every Manager sharing its state, so a
host that swapped :host by assoc and re-installed is honoured by later calls.
Prefer reseat-host!, which does this under the manager lock.
Make MGR the installed manager and the source of the dormancy oracle. MGR is also SEATED: it becomes `current` for every Manager sharing its state, so a host that swapped :host by assoc and re-installed is honoured by later calls. Prefer reseat-host!, which does this under the manager lock.
(manager {:keys [host specs] :as opts})A lifecycle manager. OPTS: :host ILifecycleHost (required) :specs [MountSpec ...] every addon this manager governs :defaults LifecycleDecl applied under each manifest's own :overrides {addon-id LifecycleDecl}, strongest :surface-store ISurfaceStore for learned surfaces :now-ms (fn [] ms) :reload-ns! (fn [roots ns-strs] -> reloader answer) run before an activation mounts; default uses hive-hot when present :on-activated (fn [ids]) after addons become active :on-evicted (fn [id]) after an addon becomes dormant :lock-timeout-ms
A lifecycle manager. OPTS:
:host ILifecycleHost (required)
:specs [MountSpec ...] every addon this manager governs
:defaults LifecycleDecl applied under each manifest's own
:overrides {addon-id LifecycleDecl}, strongest
:surface-store ISurfaceStore for learned surfaces
:now-ms (fn [] ms)
:reload-ns! (fn [roots ns-strs] -> reloader answer) run before an
activation mounts; default uses hive-hot when present
:on-activated (fn [ids]) after addons become active
:on-evicted (fn [id]) after an addon becomes dormant
:lock-timeout-ms(mount-host mh)(mount-host mh mount-opts)An ILifecycleHost over an IMountHost. Stubs are kept in an atom ({id {:surface s :activate! f}}) readable as (:stubs host).
An ILifecycleHost over an IMountHost. Stubs are kept in an atom
({id {:surface s :activate! f}}) readable as (:stubs host).(reseat-host! mgr host-fn)Govern MGR's addons through a new ILifecycleHost, in place. HOST-FN is the new host, or (fn [old-host] new-host).
Under the manager lock it builds a Manager on the new host that SHARES MGR's
specs, states, surfaces, lock and sweeper, seats it (see current), installs
it when MGR's state is the installed one (or nothing is installed), moves a
running sweeper onto it and re-arms the stubs of every dormant addon on the
new host. The old host is not called. Every activation, eviction and forget!
resolves the seated manager once it holds the lock, so none that starts after
this returns can reach the old host, and none running when it is called is cut
short: the reseat waits for it.
Returns a ReseatReport (hive-addon.lifecycle.schema/ReseatReport); on success (current mgr) is the new Manager.
Govern MGR's addons through a new ILifecycleHost, in place. HOST-FN is the new host, or (fn [old-host] new-host). Under the manager lock it builds a Manager on the new host that SHARES MGR's specs, states, surfaces, lock and sweeper, seats it (see `current`), installs it when MGR's state is the installed one (or nothing is installed), moves a running sweeper onto it and re-arms the stubs of every dormant addon on the new host. The old host is not called. Every activation, eviction and forget! resolves the seated manager once it holds the lock, so none that starts after this returns can reach the old host, and none running when it is called is cut short: the reseat waits for it. Returns a ReseatReport (hive-addon.lifecycle.schema/ReseatReport); on success (current mgr) is the new Manager.
(set-policy! mgr id decl)(set-policy! mgr id decl {:keys [force?]})Change ID's lifecycle at runtime (e.g. pin it). Returns the new Lifecycle.
Making an addon :lazy that has no surface a stub could advertise is REFUSED unless OPTS {:force? true}: the lifecycle is left as it was and returned with :refused :no-surface, because a lazy surface-less addon, once evicted, could never be woken again.
Change ID's lifecycle at runtime (e.g. pin it). Returns the new Lifecycle.
Making an addon :lazy that has no surface a stub could advertise is REFUSED
unless OPTS {:force? true}: the lifecycle is left as it was and returned with
:refused :no-surface, because a lazy surface-less addon, once evicted, could
never be woken again.(start-sweeper! mgr)(start-sweeper! mgr {:keys [interval-ms]})Sweep every :sweep-interval-ms on a daemon thread. Idempotent. Each tick
sweeps the CURRENT manager (see current), never the value captured here.
Sweep every :sweep-interval-ms on a daemon thread. Idempotent. Each tick sweeps the CURRENT manager (see `current`), never the value captured here.
(status mgr)The manager as wire-safe data.
The manager as wire-safe data.
(surface-of mgr id)[surface source] for ID: declared, else learned, else [nil :none].
[surface source] for ID: declared, else learned, else [nil :none].
(sweep! mgr)Evict every addon the sweep plan allows, then close idle parts. Returns {:plan SweepPlan :evicted [id ...] :refused {id reason} :parts {owner [part-id]}}.
Evict every addon the sweep plan allows, then close idle parts.
Returns {:plan SweepPlan :evicted [id ...] :refused {id reason} :parts {owner [part-id]}}.(tracked mgr id f)F wrapped so every call counts as a use of ID.
F wrapped so every call counts as a use of ID.
(with-use mgr id & body)Run BODY with addon ID counted in flight (see call-with-use).
Run BODY with addon ID counted in flight (see call-with-use).
cljdoc builds & hosts documentation for Clojure/Script libraries
| Ctrl+k | Jump to recent docs |
| ← | Move to previous article |
| → | Move to next article |
| Ctrl+/ | Jump to the search field |