Liking cljdoc? Tell your friends :D

com.blockether.vis.internal.gateway.discovery

Gateway daemon discovery + registry (build order step 1).

One long-lived gateway per DB owns execution; every TUI/web/whatever is a thin client of it. This namespace answers the boot-time question: "is a gateway already running for my DB — attach; else spawn one, DETACHED (nobody's child), then hand back where to connect."

Registry: one EDN file per DB at ~/.vis/gateway/registry/<sha256(db)>.edn holding {:pid :port :host :secret :db :created-at}. Freshness = the recorded :pid is still alive AND a caller-supplied probe confirms the port+secret are really OUR daemon (guards OS pid reuse — see D4/Q3 in TODO-gateway-daemon).

Design decisions (locked, see TODO-gateway-daemon.md):

  • Q2 registry key = the DB path (two dirs sharing --db share one daemon).
  • Q3 race = port-bind winner is the daemon; the loser attaches. The daemon SELF-REGISTERS on startup (via register-self! from serve-main!), so a spawner never needs the child pid — it just polls for a fresh registry.
  • Q5 :memory never registers/spawns (headless one-shot stays in-process).

Effects (spawn, probe, pid-liveness) are injectable so the orchestration in discover-or-start! is unit-testable without a real process.

Gateway daemon discovery + registry (build order step 1).

One long-lived gateway per DB owns execution; every TUI/web/whatever is a
thin client of it. This namespace answers the boot-time question: "is a
gateway already running for my DB — attach; else spawn one, DETACHED (nobody's
child), then hand back where to connect."

Registry: one EDN file per DB at `~/.vis/gateway/registry/<sha256(db)>.edn`
holding `{:pid :port :host :secret :db :created-at}`. Freshness = the recorded
`:pid` is still alive AND a caller-supplied `probe` confirms the port+secret
are really OUR daemon (guards OS pid reuse — see D4/Q3 in TODO-gateway-daemon).

Design decisions (locked, see TODO-gateway-daemon.md):
- Q2 registry key = the DB path (two dirs sharing `--db` share one daemon).
- Q3 race = port-bind winner is the daemon; the loser attaches. The daemon
  SELF-REGISTERS on startup (via [[register-self!]] from `serve-main!`), so a
  spawner never needs the child pid — it just polls for a fresh registry.
- Q5 `:memory` never registers/spawns (headless one-shot stays in-process).

Effects (`spawn`, `probe`, pid-liveness) are injectable so the orchestration
in [[discover-or-start!]] is unit-testable without a real process.
raw docstring

acquire-spawn-lock!clj

(acquire-spawn-lock! db)

Try to grab the EXCLUSIVE cross-process spawn lock for db WITHOUT blocking. Returns a {:channel :lock} holder when THIS process won the right to spawn, or nil when another process already holds it (that process is the designated spawner — the caller should just await the registry instead of launching a competing daemon). Never throws.

Try to grab the EXCLUSIVE cross-process spawn lock for `db` WITHOUT blocking.
Returns a `{:channel :lock}` holder when THIS process won the right to spawn,
or nil when another process already holds it (that process is the designated
spawner — the caller should just await the registry instead of launching a
competing daemon). Never throws.
sourceraw docstring

await-registry!clj

(await-registry! db probe)
(await-registry! db
                 probe
                 {:keys [timeout-ms poll-ms on-tick]
                  :or {timeout-ms 8000 poll-ms 100}})

Poll for a FRESH registry entry for db up to timeout-ms, checking every poll-ms. :on-tick (optional) is called before each sleep with the elapsed millis so a caller can render live 'still waiting…' feedback; it never breaks the poll loop. Returns the entry or nil on timeout.

Poll for a FRESH registry entry for `db` up to `timeout-ms`, checking every
`poll-ms`. `:on-tick` (optional) is called before each sleep with the elapsed
millis so a caller can render live 'still waiting…' feedback; it never breaks
the poll loop. Returns the entry or nil on timeout.
sourceraw docstring

base-argvclj

(base-argv)

Prefix argv that re-launches vis in a fresh process: the native binary as-is, or java -cp … clojure.main -m com.blockether.vis.core on the JVM (mirrors the telegram self-restart trick).

Prefix argv that re-launches vis in a fresh process: the native binary as-is,
or `java -cp … clojure.main -m com.blockether.vis.core` on the JVM (mirrors the
telegram self-restart trick).
sourceraw docstring

current-pidclj

(current-pid)
source

db-targetclj

(db-target db)

Normalize a DB target/spec to the value that identifies a daemon. SQLite specs use their :path; :memory stays ephemeral; other values pass through.

Normalize a DB target/spec to the value that identifies a daemon. SQLite specs
use their `:path`; `:memory` stays ephemeral; other values pass through.
sourceraw docstring

delete-registry!clj

(delete-registry! db)

Remove the registry file for db. Never throws. Returns true when a file was removed.

Remove the registry file for `db`. Never throws. Returns true when a file was
removed.
sourceraw docstring

deregister-self!clj

(deregister-self! db)

Called from the daemon's shutdown hook: delete OUR registry entry for db, but only if it still points at us (never clobber a successor that took over).

Called from the daemon's shutdown hook: delete OUR registry entry for `db`,
but only if it still points at us (never clobber a successor that took over).
sourceraw docstring

discover-or-start!clj

(discover-or-start! {:keys [db] :as opts}
                    &
                    {:keys [probe spawn timeout-ms poll-ms on-event]
                     :or {probe (constantly true) spawn spawn-detached!}})

Resolve a gateway for db. Returns:

  • {:mode :none} for a :memory DB (Q5, never spawns);
  • {:mode :attach :entry {…}} a fresh daemon already runs — connect;
  • {:mode :spawned :entry {…}} WE spawned one and it self-registered;
  • {:mode :awaited :entry {…}} ANOTHER process was spawning — we waited on its daemon instead of piling on;
  • {:mode :timeout} nobody came up in time.

probe is the port+secret liveness check (defaults to pid-liveness alone). spawn (default spawn-detached!) and now are injectable for tests.

:on-event (optional) is a side-effecting callback the caller uses to surface live progress so waiters are NEVER left staring at a frozen screen. It fires only on the SLOW path (never on a plain :attach) with maps: {:phase :spawning} WE won the lock and are launching the daemon; {:phase :awaiting} another vis is starting it — we wait, not spawn; {:phase :tick :elapsed-ms n} a poll heartbeat while awaiting either of those; {:phase :ready :mode m :entry e} the daemon came up; {:phase :timeout} nobody came up in time. It never throws upward.

THUNDERING-HERD GUARD (see acquire-spawn-lock!): when the registry is not fresh, only the ONE process that wins the cross-process spawn lock actually launches a daemon; every other concurrent starter finds the lock held, learns 'someone is already spawning', and just awaits the winner's self-registration. That replaces the old blind-port-bind race (N daemons launched, N-1 crashing) with a single spawn + N-1 cheap waiters. A stale registry is deleted before spawning; the lock is re-checked against a double-read so a daemon that came up between our read and the lock is attached, not re-spawned.

Resolve a gateway for `db`. Returns:
- `{:mode :none}`                          for a `:memory` DB (Q5, never spawns);
- `{:mode :attach  :entry {…}}`            a fresh daemon already runs — connect;
- `{:mode :spawned :entry {…}}`            WE spawned one and it self-registered;
- `{:mode :awaited :entry {…}}`            ANOTHER process was spawning — we waited
                                           on its daemon instead of piling on;
- `{:mode :timeout}`                       nobody came up in time.

`probe` is the port+secret liveness check (defaults to pid-liveness alone).
`spawn` (default [[spawn-detached!]]) and `now` are injectable for tests.

`:on-event` (optional) is a side-effecting callback the caller uses to surface
live progress so waiters are NEVER left staring at a frozen screen. It fires
only on the SLOW path (never on a plain `:attach`) with maps:
`{:phase :spawning}`           WE won the lock and are launching the daemon;
`{:phase :awaiting}`           another vis is starting it — we wait, not spawn;
`{:phase :tick :elapsed-ms n}` a poll heartbeat while awaiting either of those;
`{:phase :ready :mode m :entry e}` the daemon came up;
`{:phase :timeout}`            nobody came up in time. It never throws upward.

THUNDERING-HERD GUARD (see [[acquire-spawn-lock!]]): when the registry is not
fresh, only the ONE process that wins the cross-process spawn lock actually
launches a daemon; every other concurrent starter finds the lock held, learns
'someone is already spawning', and just awaits the winner's self-registration.
That replaces the old blind-port-bind race (N daemons launched, N-1 crashing)
with a single spawn + N-1 cheap waiters. A stale registry is deleted before
spawning; the lock is re-checked against a double-read so a daemon that came up
between our read and the lock is attached, not re-spawned.
sourceraw docstring

lock-fileclj

(lock-file db)

OS advisory-lock file guarding daemon SPAWN for db. Sits beside the registry file so it shares the per-DB key; a stale lockfile is harmless (the OS lock, not the file's existence, is what's held).

OS advisory-lock file guarding daemon SPAWN for `db`. Sits beside the registry
file so it shares the per-DB key; a stale lockfile is harmless (the OS lock,
not the file's existence, is what's held).
sourceraw docstring

memory-db?clj

(memory-db? db)

True for the ephemeral in-memory DB target — it never spawns/attaches a daemon (Q5). Accepts raw --db values and resolved DB specs.

True for the ephemeral in-memory DB target — it never spawns/attaches a
daemon (Q5). Accepts raw `--db` values and resolved DB specs.
sourceraw docstring

native-image?clj

(native-image?)

True when running as the compiled GraalVM binary rather than on the JVM.

True when running as the compiled GraalVM binary rather than on the JVM.
sourceraw docstring

pid-alive?clj

(pid-alive? pid)

Best-effort: is OS process pid still running? A nil pid is NOT alive (an entry with no pid can't own a live daemon).

Best-effort: is OS process `pid` still running? A nil pid is NOT alive (an
entry with no pid can't own a live daemon).
sourceraw docstring

read-registryclj

(read-registry db)

Read the registry entry for db, or nil when absent/unreadable/garbage.

Read the registry entry for `db`, or nil when absent/unreadable/garbage.
sourceraw docstring

register-self!clj

(register-self! db {:keys [port host secret]})

Called by the daemon (serve-main!) once it is listening: write this process as the owner of db. secret should be the gateway bearer token (or any nonce a client can echo back on registry-fresh?'s probe). Returns the entry, or nil for a :memory DB (which never registers).

Called by the daemon (`serve-main!`) once it is listening: write this process
as the owner of `db`. `secret` should be the gateway bearer token (or any
nonce a client can echo back on [[registry-fresh?]]'s probe). Returns the
entry, or nil for a `:memory` DB (which never registers).
sourceraw docstring

registry-dirclj

(registry-dir)

Directory holding the per-DB registry files. Not created here.

Directory holding the per-DB registry files. Not created here.
sourceraw docstring

registry-fileclj

(registry-file db)

The registry EDN file for DB target db.

The registry EDN file for DB target `db`.
sourceraw docstring

registry-fresh?clj

(registry-fresh? entry)
(registry-fresh? {:keys [pid] :as entry} probe)

True when entry describes a LIVE daemon: it has a pid, that pid is alive, AND probe confirms it (defaults to trusting pid-liveness alone). The client adapter injects a real probe that hits http://host:port with :secret to defeat pid reuse (Q3).

True when `entry` describes a LIVE daemon: it has a pid, that pid is alive,
AND `probe` confirms it (defaults to trusting pid-liveness alone). The client
adapter injects a real `probe` that hits `http://host:port` with `:secret` to
defeat pid reuse (Q3).
sourceraw docstring

registry-keyclj

(registry-key db)

Stable key for a DB target: sha256 of its canonical path (Q2).

Stable key for a DB target: `sha256` of its canonical path (Q2).
sourceraw docstring

release-spawn-lock!clj

(release-spawn-lock! {:keys [lock channel]})

Release a holder from acquire-spawn-lock!. Never throws.

Release a holder from [[acquire-spawn-lock!]]. Never throws.
sourceraw docstring

spawn-argvclj

(spawn-argv {:keys [db port host token-file require-token? base]})

Full argv to launch a fresh gateway daemon for db on port. Serve flags follow the gateway start subcommand. :base lets a caller override the re-exec prefix (tests do).

Full argv to launch a fresh gateway daemon for `db` on `port`. Serve flags
follow the `gateway start` subcommand. `:base` lets a caller override the
re-exec prefix (tests do).
sourceraw docstring

spawn-detached!clj

(spawn-detached! opts)

Fire-and-forget launch of a gateway daemon for db, fully DETACHED so closing the spawner's terminal (SIGHUP) does NOT kill it (D4). On unix this runs the argv under nohup … & inside a throwaway sh (the daemon is reparented to init); elsewhere it falls back to a plain detached ProcessBuilder. The daemon SELF-REGISTERS its pid/port on startup, so we never need its pid here. Output is discarded (the daemon logs through Telemere). Returns nil.

Fire-and-forget launch of a gateway daemon for `db`, fully DETACHED so closing
the spawner's terminal (SIGHUP) does NOT kill it (D4). On unix this runs the
argv under `nohup … &` inside a throwaway `sh` (the daemon is reparented to
init); elsewhere it falls back to a plain detached ProcessBuilder. The daemon
SELF-REGISTERS its pid/port on startup, so we never need its pid here. Output
is discarded (the daemon logs through Telemere). Returns nil.
sourceraw docstring

write-registry!clj

(write-registry! db entry)

Write entry (a map, :db/:created-at filled in if absent) as the registry for db. Creates the registry dir. Returns the written entry.

Write `entry` (a map, `:db`/`:created-at` filled in if absent) as the registry
for `db`. Creates the registry dir. Returns the written entry.
sourceraw docstring

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close