Documentation
¶
Overview ¶
Package dial connects local adapters to the daemon's typed, newline-delimited JSON protocol over a Unix socket. A Conn serializes one unary call or stream at a time; connection recovery and autospawn coordinate process availability without taking ownership of daemon state.
Index ¶
- Variables
- func DefaultAuthorityPath() (string, error)
- func DefaultLogPath() string
- func DefaultSocketPath() string
- func DisplayPath(p string) string
- func IsProcessAlive(pid int) bool
- func LockHolderPID(lockPath string) int
- func LockPath(socketPath string) string
- func SocketPathOverridden() bool
- func StartupBudget() time.Duration
- func TailLastLine(path string, maxBytes int) string
- type Conn
- func AutospawnAndConnect(socketPath string) (*Conn, error)
- func AutospawnAndConnectContext(ctx context.Context, socketPath string) (*Conn, error)
- func AutospawnAndConnectContextFromExecutableWithTimeout(ctx context.Context, socketPath, executable string, ...) (*Conn, error)
- func Connect(path string) (*Conn, error)
Constants ¶
This section is empty.
Variables ¶
var ErrSocketMissing = errors.New("daemon socket missing")
ErrSocketMissing indicates the daemon is not reachable: either the socket file does not exist, or it exists but no daemon is listening on it (a stale socket left behind by a crashed predecessor). Both cases are mapped to the same sentinel because every caller — autospawn in cmd/canary, retry in WaitForSocket — treats them identically.
Functions ¶
func DefaultAuthorityPath ¶ added in v2.6.0
DefaultAuthorityPath returns the canonical daemon authority database location. The daemon verifies this file end to end before it publishes its socket, so callers waiting on a daemon start size it to budget that wait.
func DefaultLogPath ¶
func DefaultLogPath() string
DefaultLogPath returns the canonical daemon log location. It reads XDG_STATE_HOME the way DefaultAuthorityPath does, so a desk that moves its state directory keeps the log beside the database it describes.
func DefaultSocketPath ¶
func DefaultSocketPath() string
DefaultSocketPath returns the canonical socket location.
func DisplayPath ¶ added in v2.6.0
DisplayPath renders p for a human-facing hint, abbreviating the home directory to ~. Hints name the path Canary will actually use; spelling the home directory out puts the account name into terminal output and screenshots without telling the reader anything.
func IsProcessAlive ¶
IsProcessAlive reports whether a PID is currently a live, non-zombie process. Uses signal 0 (kill -0) as the ownership/existence probe, then filters out defunct processes because zombies still satisfy kill -0 on Unix even though SIGTERM/SIGKILL cannot make them "more exited".
EPERM (we know the process exists but cannot signal it — typically owned by another user) reports false here on purpose: the caller would otherwise try to SIGTERM a process it can't actually signal. For our recovery use case "not our daemon, leave it alone" is conservative and safe.
func LockHolderPID ¶
LockHolderPID returns the PID written to the lock file, or 0 if the file is missing, unreadable, or malformed. Best-effort: a 0 result is indistinguishable from "no daemon" and callers should treat it that way.
func LockPath ¶
LockPath returns the canonical instance-lock path co-located with the socket. The daemon writes its PID here under flock; the CLI reads it during recovery to detect a stuck daemon.
func SocketPathOverridden ¶
func SocketPathOverridden() bool
SocketPathOverridden reports whether CANARY_SOCKET points the CLI at a non-default daemon scope. Commands that manage system-wide state by process name (e.g. `canary restart`'s implicit app management) use this to stay hands-off: a process found by name cannot be attributed to the overridden scope, so signaling it would cross scopes.
func StartupBudget ¶ added in v2.6.0
StartupBudget returns how long to wait for a starting daemon to publish its socket.
A fixed constant is wrong by construction: before the daemon accepts connections it validates the whole authority and may perform a crash-safe out-of-place schema upgrade. Both ordinary validation and upgrade work scale with the authority size. The budget therefore prices a conservative number of source-size work units rather than assuming one validation pass.
An existing daemon.db is the direct size input. When it is absent, the same path can mean either a fresh install or a file-backed release that must first import and seal its legacy state corpus. The latter is sized recursively within the persistent namespace without following symbolic links. An incomplete legacy walk receives the finite maximum budget; a daemon that cannot start still reports promptly through the independent PID-death path.
func TailLastLine ¶
TailLastLine returns the last non-empty line of the file at path, capped at maxBytes from the tail. Returns "" if the file is missing, empty, or unreadable. Used by the CLI to surface the latest daemon log line in error messages when autospawn fails.
Types ¶
type Conn ¶
type Conn struct {
// contains filtered or unexported fields
}
Conn is a single client connection over the Unix socket.
func AutospawnAndConnect ¶
AutospawnAndConnect spawns this binary's `daemon` mode (located via os.Executable), waits for the Unix socket to appear at socketPath, and returns a live connection. On wait failure the returned error is annotated with whatever the lock file tells us plus the last daemon log line.
Shared between the CLI entry and internal/mcp (stdio MCP server) — both surfaces need the same "is the daemon up? if not, start it" dance.
Pre-spawn check: if the lock file points at a live PID, the daemon is already running — either still booting (socket not yet up) or stuck. Spawning another daemon there is wasted work because the flock would reject it; worse, when the lock file has been deleted out from under a live daemon (manual `rm`, aggressive cleanup script), a fresh spawn can co-exist with the old one and both hold a gateway connection.
Shutdown race: the daemon's Stop sequence removes the socket BEFORE it releases the lock. A CLI invocation that arrives during that window sees "PID alive + lock present + socket gone" — looks identical to a stuck daemon. To distinguish: poll PID liveness while waiting; when the daemon finishes exiting, fall through to spawn a fresh one. Only surface the "stuck daemon" error when the PID stays alive through the full budget.
func AutospawnAndConnectContext ¶
AutospawnAndConnectContext is AutospawnAndConnect with a caller-owned cancellation signal. It is used by stdio MCP so protocol shutdown can abort a pending daemon startup instead of leaving the server around after its host is gone.
func AutospawnAndConnectContextFromExecutableWithTimeout ¶
func AutospawnAndConnectContextFromExecutableWithTimeout(ctx context.Context, socketPath, executable string, startupTimeout time.Duration) (*Conn, error)
AutospawnAndConnectContextFromExecutableWithTimeout starts exactly executable and then verifies that the spawned PID owns the daemon lock before returning a connection. It is intentionally stricter than the ordinary autospawn path: callers use it after replacing an installed binary and stopping the prior daemon, so connecting to a concurrently started daemon from an unknown executable would be a false-success cutover.
The startup budget is caller-owned rather than derived here because a restart may also have to carry a validated schema migration before the socket is published. A non-positive budget falls back to StartupBudget.
func Connect ¶
Connect opens the socket. Returns ErrSocketMissing if path doesn't exist OR if it exists but no daemon is listening (ECONNREFUSED) — both of those mean "no daemon", and the caller's response is identical.
func (*Conn) Call ¶
Call performs a unary request/response round trip and decodes result into out. ctx cancellation forces an immediate read deadline so the in-flight read returns and Call surfaces ctx.Err(), matching Stream cancellation.
The socket deadline is cleared on return, success or failure, so a subsequent caller, including a long-lived stream, starts with fresh timing state rather than inheriting the unary call's deadline.
func (*Conn) DaemonVersion ¶
DaemonVersion runs a one-shot status.health call against the open Conn and returns the daemon's stamped version string. Short timeout so a wedged daemon doesn't delay the user's actual command — the caller (typically main.go) emits a non-fatal warning on mismatch, not an error.
Defined here rather than in main.go so internal/mcp can run the same check at boot if it ever wants to.