Plug an addon IN that was NOT on the classpath when the host booted — and plug it back OUT.
inject! is the mount pipeline run over a slice that did not exist yet:
put the addon's paths on the live classpath, discover the manifests under
them (and ONLY under them — an addon already on the classpath that the
composer chose not to mount must not be resurrected by a scan), solve the
new specs against the peers already mounted, tear down the mounted
dependents that now have a new sibling to receive, mount the slice through
the ordinary IMountDriver, and register the new addons with hive-hot so
they reload like the rest. Every injected addon that mounts is REMEMBERED
in the injected-spec registry (hive-addon.mount.injected), which classpath
discovery unions in, so it stays discoverable from any thread; and it is
ADOPTED by the installed lifecycle manager under its manifest policy.
eject! is the inverse: teardown, unregister through the optional
IMountUnregister port, un-govern, un-hot, stop watching the dirs nobody else
needs, forget the registry entry — and REPORT what cannot be removed (the
classpath URLs a URLClassLoader cannot drop, the namespaces still loaded).
Classpath extension is a JVM concern: the URL is added to the highest
DynamicClassLoader above the calling thread's context loader, which is the
loader every later require resolves through in an nREPL-hosted image. A
thread with no DynamicClassLoader in its chain cannot extend the classpath;
inject! then REFUSES with :hot/no-dynamic-classloader rather than mounting
code the next require would fail to find.
Maven dependencies of the injected addon are NOT resolved by default; pass
:resolve-deps? true to resolve only the project's deps.edn :deps through
the tools.deps CLI, then extend the loader with libs absent from the running
basis (requires Clojure 1.12 and the tools.deps CLI).
Plug an addon IN that was NOT on the classpath when the host booted — and plug it back OUT. `inject!` is the mount pipeline run over a slice that did not exist yet: put the addon's paths on the live classpath, discover the manifests under them (and ONLY under them — an addon already on the classpath that the composer chose not to mount must not be resurrected by a scan), solve the new specs against the peers already mounted, tear down the mounted dependents that now have a new sibling to receive, mount the slice through the ordinary IMountDriver, and register the new addons with hive-hot so they reload like the rest. Every injected addon that mounts is REMEMBERED in the injected-spec registry (hive-addon.mount.injected), which classpath discovery unions in, so it stays discoverable from any thread; and it is ADOPTED by the installed lifecycle manager under its manifest policy. `eject!` is the inverse: teardown, unregister through the optional IMountUnregister port, un-govern, un-hot, stop watching the dirs nobody else needs, forget the registry entry — and REPORT what cannot be removed (the classpath URLs a URLClassLoader cannot drop, the namespaces still loaded). Classpath extension is a JVM concern: the URL is added to the highest DynamicClassLoader above the calling thread's context loader, which is the loader every later `require` resolves through in an nREPL-hosted image. A thread with no DynamicClassLoader in its chain cannot extend the classpath; inject! then REFUSES with :hot/no-dynamic-classloader rather than mounting code the next require would fail to find. Maven dependencies of the injected addon are NOT resolved by default; pass `:resolve-deps? true` to resolve only the project's deps.edn :deps through the tools.deps CLI, then extend the loader with libs absent from the running basis (requires Clojure 1.12 and the tools.deps CLI).
(dependency-delta resolved image-libs)Only the injected project's resolved libs absent from the running image. The project is resolved alone, never merged into the image's top-level deps: merging turns image transitive libs into roots and loses parent exclusions. Pure over resolved libs and basis libs; keeps the resolver's path order.
Only the injected project's resolved libs absent from the running image. The project is resolved alone, never merged into the image's top-level deps: merging turns image transitive libs into roots and loses parent exclusions. Pure over resolved libs and basis libs; keeps the resolver's path order.
(dynamic-loader)The highest DynamicClassLoader in the calling thread's loader chain — the context loader's chain first, then clojure.lang.RT/baseLoader's — or nil when there is none.
The highest DynamicClassLoader in the calling thread's loader chain — the context loader's chain first, then clojure.lang.RT/baseLoader's — or nil when there is none.
(eject! host specs target & [opts])Plug the addons TARGET names OUT of the running host — the inverse of
inject!. specs are the MountSpecs currently mounted (the same set inject!
takes); TARGET is an addon id, a collection of ids, or the path they were
injected from.
First the calls in flight are DRAINED: the IDrainGate (:drain-gate, default hive-addon.hot.drain/drain-gate) stops admitting calls to the ejected addons and the dependents a cascade takes down, and eject! waits up to :drain-ms (default hive-addon.hot.drain/default-drain-ms) for the admitted ones to finish. Calls still running then REFUSE the eject (:hot/busy? true, nothing torn down, the gate re-opened) unless {:force? true}, which goes ahead and says so (:hot/forced? true). The wait is reported under :hot/drain.
Then, in order: tear the addons down (reverse dependency order, through the IMountDriver), unregister them through the optional IMountUnregister port, stop the installed lifecycle manager governing them, deregister them from hive-hot, stop watching the source dirs no surviving addon still needs, and forget them in the injected-spec registry. The gate is re-opened afterwards.
An addon other MOUNTED addons depend on is REFUSED (:hot/refused? true, :hot/blocking) unless opts {:cascade? true}, which also tears those dependents down and remounts them WITHOUT the ejected sibling.
What cannot be removed is REPORTED, not hidden: classpath URLs stay on the DynamicClassLoader (a URLClassLoader cannot drop one), loaded namespaces stay in the image, and a host without IMountUnregister keeps an inert entry.
Source dirs go through the IHotDirs port (:hot-dirs, default the hive-hot adapter): each ejected addon releases its OWN claim, and what hive-hot keeps (a core dir, or one another owner claims) is reported under :hot/dirs-retained / :hot/dirs-shared.
opts: {:cascade? :force? :drain-ms :poll-ms :drain-gate :mount-driver
:hot-dirs :mount-opts :solve-opts} (+ the hive-addon.hot/mount-opt-keys
folded in from the top level).
Returns an EjectReport. Never throws. See plug-out! for the Result form.
Plug the addons TARGET names OUT of the running `host` — the inverse of
inject!. `specs` are the MountSpecs currently mounted (the same set inject!
takes); TARGET is an addon id, a collection of ids, or the path they were
injected from.
First the calls in flight are DRAINED: the IDrainGate (:drain-gate, default
hive-addon.hot.drain/drain-gate) stops admitting calls to the ejected addons
and the dependents a cascade takes down, and eject! waits up to :drain-ms
(default hive-addon.hot.drain/default-drain-ms) for the admitted ones to
finish. Calls still running then REFUSE the eject (:hot/busy? true, nothing
torn down, the gate re-opened) unless {:force? true}, which goes ahead and
says so (:hot/forced? true). The wait is reported under :hot/drain.
Then, in order: tear the addons down (reverse dependency order, through the
IMountDriver), unregister them through the optional IMountUnregister port,
stop the installed lifecycle manager governing them, deregister them from
hive-hot, stop watching the source dirs no surviving addon still needs, and
forget them in the injected-spec registry. The gate is re-opened afterwards.
An addon other MOUNTED addons depend on is REFUSED (:hot/refused? true,
:hot/blocking) unless opts {:cascade? true}, which also tears those
dependents down and remounts them WITHOUT the ejected sibling.
What cannot be removed is REPORTED, not hidden: classpath URLs stay on the
DynamicClassLoader (a URLClassLoader cannot drop one), loaded namespaces stay
in the image, and a host without IMountUnregister keeps an inert entry.
Source dirs go through the IHotDirs port (:hot-dirs, default the hive-hot
adapter): each ejected addon releases its OWN claim, and what hive-hot keeps
(a core dir, or one another owner claims) is reported under
:hot/dirs-retained / :hot/dirs-shared.
opts: {:cascade? :force? :drain-ms :poll-ms :drain-gate :mount-driver
:hot-dirs :mount-opts :solve-opts} (+ the hive-addon.hot/mount-opt-keys
folded in from the top level).
Returns an EjectReport. Never throws. See `plug-out!` for the Result form.(extend-classpath! path)(extend-classpath! path {:keys [loader]})Put path (a directory or a jar) on the live classpath. Idempotent: a URL
the loader chain already carries is reported :already? true and not
re-added.
(r/ok {:url .. :already? bool :loader <loader>}) on success; (r/err :hot/path-absent ..) when the path does not exist; (r/err :hot/no-dynamic-classloader ..) when no loader can be extended.
Put `path` (a directory or a jar) on the live classpath. Idempotent: a URL
the loader chain already carries is reported :already? true and not
re-added.
(r/ok {:url .. :already? bool :loader <loader>}) on success;
(r/err :hot/path-absent ..) when the path does not exist;
(r/err :hot/no-dynamic-classloader ..) when no loader can be extended.(inject! host specs path & [opts])Mount the addons whose manifests live under path into the running host,
alongside specs — the MountSpecs already mounted (the reload bridge's
effective specs), which is what the new ones are solved against and what
decides which dependents get remounted.
path is a project dir (its deps.edn :paths are the entries), a plain
source dir, or a jar.
Each injected spec is stamped with :hot/source-dirs and returned under :hot/specs: the host keeps THOSE as the addon's effective specs, so a later reload is scoped to the injected source on any thread.
Every injected addon that mounts is REMEMBERED in the injected-spec registry
(hive-addon.mount.injected), so discovery from any thread keeps seeing it,
and ADOPTED by the installed lifecycle manager (when one is installed) under
its manifest policy. eject! reverses both.
opts: {:mount-opts forwarded to mount! (e.g. :resolve-config); the keys in hive-addon.hot/mount-opt-keys are folded in from the top level as well :solve-opts forwarded to solve (:rules) :mount-driver IMountDriver override :loader DynamicClassLoader override :resolve-deps? hand the project's :deps to add-libs first :hot? register the new addons with hive-hot (default true) :hot-dirs IHotDirs override (default: the hive-hot adapter); each fresh addon claims its dirs under its own id :govern? adopt into the installed lifecycle manager (default true)}
Returns an InjectReport. Never throws; every failure is in the report.
Mount the addons whose manifests live under `path` into the running `host`,
alongside `specs` — the MountSpecs already mounted (the reload bridge's
effective specs), which is what the new ones are solved against and what
decides which dependents get remounted.
`path` is a project dir (its deps.edn :paths are the entries), a plain
source dir, or a jar.
Each injected spec is stamped with :hot/source-dirs and returned under
:hot/specs: the host keeps THOSE as the addon's effective specs, so a later
reload is scoped to the injected source on any thread.
Every injected addon that mounts is REMEMBERED in the injected-spec registry
(hive-addon.mount.injected), so discovery from any thread keeps seeing it,
and ADOPTED by the installed lifecycle manager (when one is installed) under
its manifest policy. `eject!` reverses both.
opts: {:mount-opts forwarded to mount! (e.g. :resolve-config); the keys
in hive-addon.hot/mount-opt-keys are folded in from
the top level as well
:solve-opts forwarded to solve (:rules)
:mount-driver IMountDriver override
:loader DynamicClassLoader override
:resolve-deps? hand the project's :deps to add-libs first
:hot? register the new addons with hive-hot (default true)
:hot-dirs IHotDirs override (default: the hive-hot adapter);
each fresh addon claims its dirs under its own id
:govern? adopt into the installed lifecycle manager (default true)}
Returns an InjectReport. Never throws; every failure is in the report.(manifests-under paths)MountSpecs whose manifest lives under paths — discovered through a
throwaway URLClassLoader over exactly those entries and no parent, so
nothing else on the classpath is seen. Returns {:specs [..] :errors [..]}.
MountSpecs whose manifest lives under `paths` — discovered through a
throwaway URLClassLoader over exactly those entries and no parent, so
nothing else on the classpath is seen. Returns {:specs [..] :errors [..]}.(plug-out! host specs target & [opts])eject! as a hive-dsl Result, for callers composing on the railway.
(r/ok EjectReport) when the addons are out. An UNSAFE or impossible eject is refused LOUDLY, as an err Result carrying the whole report: :hot/eject-refused mounted addons depend on the target (see :hot/blocking; nothing was touched — pass {:cascade? true}) :hot/eject-busy calls were still in flight when :drain-ms ran out (see :hot/drain; nothing was touched, calls are admitted again: retry, raise :drain-ms, or pass {:force? true}) :hot/eject-unknown nothing by that name is mounted or injected :hot/eject-failed the eject ran but a step failed (see :errors) Each err has :message. Never throws.
`eject!` as a hive-dsl Result, for callers composing on the railway.
(r/ok EjectReport) when the addons are out. An UNSAFE or impossible eject is
refused LOUDLY, as an err Result carrying the whole report:
:hot/eject-refused mounted addons depend on the target (see :hot/blocking;
nothing was touched — pass {:cascade? true})
:hot/eject-busy calls were still in flight when :drain-ms ran out (see
:hot/drain; nothing was touched, calls are admitted
again: retry, raise :drain-ms, or pass {:force? true})
:hot/eject-unknown nothing by that name is mounted or injected
:hot/eject-failed the eject ran but a step failed (see :errors)
Each err has :message. Never throws.(project-paths path)The classpath entries path stands for. A directory holding a deps.edn
contributes its :paths (default ["src"]) resolved against it; anything
else — a plain source dir, a jar — is itself the one entry.
The classpath entries `path` stands for. A directory holding a deps.edn contributes its :paths (default ["src"]) resolved against it; anything else — a plain source dir, a jar — is itself the one entry.
(registrable-specs specs fresh up)The MountSpecs an inject hands to hive-hot: the ones already mounted
(specs) plus only those fresh specs whose id is in up, the set of ids
that mounted. A fresh addon that failed to mount is left out, so hive-hot
never tracks an addon that is not there.
Deduplicated by :addon/id. When an id appears more than once (e.g. a classpath spec and an injected spec for the same addon), the LAST one wins, because a fresh spec carries the :hot/source-dirs the injection stamped. Each id keeps the position of its first appearance.
The MountSpecs an inject hands to hive-hot: the ones already mounted (`specs`) plus only those `fresh` specs whose id is in `up`, the set of ids that mounted. A fresh addon that failed to mount is left out, so hive-hot never tracks an addon that is not there. Deduplicated by :addon/id. When an id appears more than once (e.g. a classpath spec and an injected spec for the same addon), the LAST one wins, because a fresh spec carries the :hot/source-dirs the injection stamped. Each id keeps the position of its first appearance.
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 |