Documentation
¶
Index ¶
- Constants
- Variables
- func AcquireWorkspaceLock(root string) (func(), error)
- func EntryPath(cwd string, pid int) string
- func IsAlive(e Entry) bool
- func IsOrphan(e Entry) bool
- func LockPath(root string) string
- func LockPidPath(root string) string
- func Reap(e Entry) error
- func RegistryDir() string
- func Remove(cwd string) error
- func RemoveByPID(cwd string, pid int) error
- func WorkspaceID(cwd string) string
- func Write(e Entry) error
- type Entry
- type ErrLockHeld
- type HubStatus
- type Info
Constants ¶
const ( LockFileName = "hub.lock" LockPidFileName = "hub.lock.pid" )
LockFileName is the basename of the workspace-scoped lock file `bones hub start` acquires before any port bind, URL-file write, or hub bootstrap. Lives at <root>/.bones/hub.lock alongside the URL files and pid files (per the issue brief: "workspace-scoped lock file path lives under the workspace's bones-state directory"). The sibling hub.lock.pid records the holder for human-readable error messages.
Variables ¶
var ErrNotFound = errors.New("registry: entry not found")
ErrNotFound is returned by Read when no entry exists for the given cwd.
var HealthTimeout = 500 * time.Millisecond
Functions ¶
func AcquireWorkspaceLock ¶ added in v0.9.0
AcquireWorkspaceLock takes an exclusive non-blocking advisory lock on <root>/.bones/hub.lock. Used by `bones hub start` to refuse a second concurrent start against the same workspace before any side effects (port bind, URL-file write, fork-exec).
Returns a release func that drops the lock + removes the sibling pid file. If the lock is held by a live process, returns *ErrLockHeld. If the sibling pid file names a dead process, the stale pid is overwritten and the lock is reclaimed (the kill -9 recovery path from the issue's acceptance criteria).
Cross-platform: on Unix this is syscall.Flock(LOCK_EX|LOCK_NB); on Windows it falls back to a best-effort pid-file probe (see lock_windows.go) since flock is not available there.
func EntryPath ¶
EntryPath returns the absolute path of the JSON file for a given workspace cwd and hub pid. The pid is encoded into the filename so concurrent hub starts against the same workspace produce distinct files (#208 acceptance criterion (c)).
func IsAlive ¶
IsAlive returns true if BOTH (a) the recorded HubPID is alive on this host AND (b) GET <HubURL> succeeds at the TCP/HTTP level within HealthTimeout. Both checks are required because a recycled PID can pass (a) but fail (b).
The HTTP probe doesn't require any specific endpoint — any HTTP response (including 4xx) means the port is bound and serving, which is what we actually want to know. The Fossil HTTP server bones uses doesn't expose a /health endpoint and we deliberately don't add a sidecar HTTP server just for the probe.
func IsOrphan ¶ added in v0.7.0
IsOrphan reports whether e represents a process that is alive on this host but whose workspace is no longer reachable. Three signals qualify a workspace as gone:
- e.Cwd does not exist on disk (ENOENT)
- e.Cwd exists but its workspace marker (.bones/agent.id) does not
- e.Cwd resolves into the user's Trash (~/.Trash on macOS, the XDG-Trash equivalent on Linux)
The PID-alive check is the same one IsAlive uses; an entry whose PID is dead is not an orphan (it's a stale entry awaiting prune) and will be reported by IsAlive returning false.
func LockPath ¶ added in v0.9.0
LockPath returns the absolute path of the workspace lock file for the workspace at root.
func LockPidPath ¶ added in v0.9.0
LockPidPath returns the absolute path of the sibling pid file recording the lock holder.
func Reap ¶ added in v0.7.0
Reap terminates the process for e and removes its registry entry. SIGTERM first; if the PID is still alive after reapGrace, SIGKILL. Returns nil on success (process gone, entry removed) or an error describing which step failed. The entry is removed even after SIGKILL — leaving it would create a permanent registry record for a process that, by definition, isn't going to come back.
func RegistryDir ¶
func RegistryDir() string
RegistryDir returns the directory that holds workspace entry files.
func Remove ¶
Remove deletes ALL registry entries for the given workspace cwd — every per-pid file plus the legacy unsuffixed path. Idempotent. Used by `bones down` and stale-entry pruners that operate at workspace granularity. Callers that want to drop a single entry by pid should use RemoveByPID instead.
func RemoveByPID ¶ added in v0.9.0
RemoveByPID deletes the single registry entry for (cwd, pid). Idempotent: missing files are not an error. Used by Reap and by foreground hub.Start's defer cleanup so a workspace with two live entries can lose one without forgetting the other.
func WorkspaceID ¶
WorkspaceID returns a deterministic 16-hex-char identifier for an absolute cwd. Used as the registry filename prefix: ~/.bones/workspaces/<id>-<pid>.json.
Types ¶
type Entry ¶
type Entry struct {
Cwd string `json:"cwd"`
Name string `json:"name"`
HubURL string `json:"hub_url"`
NATSURL string `json:"nats_url"`
HubPID int `json:"hub_pid"`
StartedAt time.Time `json:"started_at"`
}
Entry is one workspace's registry record. Each `bones hub start` writes its own entry at ~/.bones/workspaces/<WorkspaceID>-<HubPID>.json — pid in the filename so two concurrent starts against the same workspace produce two entries (the duplicate-hub case from #208) rather than the second silently overwriting the first.
func Duplicates ¶ added in v0.9.0
Duplicates returns every registry entry whose canonical Cwd matches the given workspace AND whose HubPID is alive on this host. When the length is 0 or 1, the workspace has no concurrent hubs (steady state). When the length is >= 2, the issue described in #208 is present: two `bones hub start` invocations are competing for the same workspace's URL files and fossil state.
Sibling primitive of Orphans(): both walk the per-pid registry, both filter on PID liveness, both stay read-only. Doctor and status consume Duplicates and emit WARN-class output; reaping is left to the operator (the brief defers automatic kill of duplicates to a dedicated verb, mirroring ADR 0043's read-only-doctor doctrine).
PID-alive is the only liveness signal — no HTTP probe — because the duplicate scenario typically includes one hub that lost the port race and is no longer serving HTTP. Such a hub is still holding fossil inodes and overwriting URL files; the operator needs to know about it even if HealthTimeout would mark it down.
func List ¶
List returns all registry entries, skipping corrupt files. Includes every per-pid entry so two concurrent hubs against one workspace surface as two list rows (caller decides whether to dedupe).
func Orphans ¶ added in v0.7.0
Orphans returns all registry entries whose process is alive but whose workspace is gone. Read-only; the caller decides what to do.
type ErrLockHeld ¶ added in v0.9.0
ErrLockHeld is returned by AcquireWorkspaceLock when another live process holds the workspace lock. The PID is the holder (parsed from hub.lock.pid); callers surface it in CLI output so the operator can act.
func (*ErrLockHeld) Error ¶ added in v0.9.0
func (e *ErrLockHeld) Error() string
type HubStatus ¶ added in v0.8.0
type HubStatus string
HubStatus is a coarse liveness label produced by ListInfo. Values:
HubRunning — registry recorded a hub PID, the PID is alive on this host,
and a quick TCP/HTTP probe of HubURL returned a response.
HubStopped — registry has an entry, but the hub is not reachable
(PID dead OR HTTP probe failed).
HubUnknown — the entry has no enough info to probe (e.g. missing
HubURL/HubPID), or a probe was deliberately skipped.
type Info ¶ added in v0.8.0
type Info struct {
Entry
ID string `json:"id"`
AgentID string `json:"agent_id"`
HubStatus HubStatus `json:"hub_status"`
LastTouched time.Time `json:"last_touched"`
}
Info is one workspace registry record enriched with the on-disk filename (ID), file mtime (LastTouched), the workspace's agent.id marker (when present), and a coarse hub liveness label.
ID is the hex string used as the registry filename: ~/.bones/workspaces/<ID>.json. It equals WorkspaceID(Cwd).
func ListInfo ¶ added in v0.8.0
ListInfo enumerates every registry entry, attaching ID + mtime + agent.id + hub-status. Corrupt or unreadable files are skipped (matching List). The hub-status probe runs IsAlive on each entry; callers that want to skip the probe (e.g. for fast paths) should use List + their own enrichment.
Results are sorted by Name, then Cwd, for stable output across calls.