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 ¶
- Constants
- Variables
- func DefaultLogPath() string
- func DefaultSocketPath() string
- func IsProcessAlive(pid int) bool
- func LockHolderPID(lockPath string) int
- func LockPath(socketPath string) string
- func SocketPathOverridden() bool
- 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 AutospawnAndConnectContextFromExecutable(ctx context.Context, socketPath, executable string) (*Conn, error)
- func AutospawnAndConnectContextFromExecutableWithTimeout(ctx context.Context, socketPath, executable string, ...) (*Conn, error)
- func Connect(path string) (*Conn, error)
- func WaitForSocket(path string, timeout time.Duration) (*Conn, error)
- func WaitForSocketContext(ctx context.Context, path string, timeout time.Duration) (*Conn, error)
Constants ¶
const AutospawnTimeout = 5 * time.Second
AutospawnTimeout is the budget for this executable's `daemon` mode to start and open its Unix socket. Discovery + the gateway handshake run in the background, so the socket appears as soon as the daemon reaches its accept loop — sub-second on a healthy machine.
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 DefaultLogPath ¶
func DefaultLogPath() string
DefaultLogPath returns the canonical daemon log location.
func DefaultSocketPath ¶
func DefaultSocketPath() string
DefaultSocketPath returns the canonical socket location.
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 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 AutospawnAndConnectContextFromExecutable ¶
func AutospawnAndConnectContextFromExecutable(ctx context.Context, socketPath, executable string) (*Conn, error)
AutospawnAndConnectContextFromExecutable 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.
func AutospawnAndConnectContextFromExecutableWithTimeout ¶
func AutospawnAndConnectContextFromExecutableWithTimeout(ctx context.Context, socketPath, executable string, startupTimeout time.Duration) (*Conn, error)
AutospawnAndConnectContextFromExecutableWithTimeout is the exact-executable cutover path with a caller-owned startup budget. Ordinary CLI/MCP autospawn retains AutospawnTimeout; restart uses its explicit timeout because a first start may include validated schema migration before the socket is published.
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 WaitForSocket ¶
WaitForSocket polls until the socket appears or the deadline expires, then dials it.
func WaitForSocketContext ¶
WaitForSocketContext polls until the socket appears, ctx is cancelled, or the deadline expires, then dials it.
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.