Help us shape the future of Go! Take the 2026 Go Developer Survey

session

package
v0.1.4 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 27, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Index

Constants

View Source
const (
	SourceCLI = "cli"
	SourceApp = "app"
)

SourceCLI and SourceApp are the canonical session Source values: the rysh command line vs the rysh desktop app. They are compared by the open guards so the two front-ends never drive the same saved session/KV state.

Variables

This section is empty.

Functions

func EnsureSourceMatch

func EnsureSourceMatch(rec Record, want string) error

EnsureSourceMatch refuses to open a session that belongs to a different front-end. A blank record source is legacy/unknown and treated as compatible with either front-end. want is the current invocation's source (normalized internally).

func FrontendName

func FrontendName(source string) string

FrontendName renders a session source as a human-readable front-end name for error messages.

func NormalizeSource

func NormalizeSource(s string) string

NormalizeSource maps a configured/recorded session source to its canonical value. Only "app" (the desktop app) is recognised explicitly; everything else — including the empty default — is the command line ("cli").

func ProcessAlive

func ProcessAlive(pid int) bool

ProcessAlive reports whether a process with the given PID is still running. A zombie (exited but not yet reaped by its parent) counts as DEAD: signal 0 succeeds on zombies, but the process can never run again. Treating zombies as alive made Terminate report "still alive after SIGKILL" (delete-session bailed without removing the record) and made spawn/attach believe a dead daemon was still serving its session.

func SpawnDaemon

func SpawnDaemon(name, workDir, configFile string) error

SpawnDaemon launches a detached `rysh daemon <name>` process for an existing (but currently stopped) session, mirroring the spawn path the CLI uses on attach. workDir is the working directory the daemon starts in (normally the session's recorded Path); configFile, when non-empty, is propagated via --config so the spawned daemon resolves the same rysh directory / registry as the caller.

The child inherits the caller's environment (so RYSH_SESSION_SOURCE and other RYSH_* settings carry over) with RYSH_SESSION pinned to name. It starts in its own session/process group (see daemonSysProcAttr) with no controlling terminal and is released, so it outlives the caller — exactly like the CLI's own daemon spawn.

func Terminate

func Terminate(pid int, grace time.Duration) error

Terminate gracefully stops the process with the given PID and never sends a bare SIGKILL. It sends SIGTERM first so the process can run its shutdown handlers — a rysh daemon flushes its actor state to JetStream KV and marks its session record stopped on SIGTERM — waits up to grace for it to exit, and only then escalates to SIGKILL so a wedged process that ignores SIGTERM still dies (and never lingers as an orphan holding the NATS port). Returns nil once the process is gone (or was already gone).

func UpdateAppClients

func UpdateAppClients(cfg config.Config, name string, n int)

UpdateAppClients records the daemon's live desktop-app (WebSocket) client count on its session record, read-modify-write so attach bookkeeping (TUIPIDs, state) set by other writers is preserved. Failures are silent — presence display must never break the daemon. Called by the web hub on every client connect/disconnect transition.

func UpdateProxyPort

func UpdateProxyPort(cfg config.Config, name string, port int)

UpdateProxyPort records the governance proxy's port (design 001) on the session record, read-modify-write so attach bookkeeping (TUIPIDs, state, app-client count) set by other writers is preserved. port 0 clears it (proxy stopped). Failures are silent — recording the endpoint must never break the daemon. Called from ##proxy on/off.

Types

type ReapAction

type ReapAction struct {
	Name   string
	Killed bool   // a live-but-wedged daemon was terminated
	Reason string // human-readable explanation
}

ReapAction records what the reaper did to a single session, for logging.

type ReapOptions

type ReapOptions struct {
	// NATSProbeTimeout bounds a single NATS reachability probe.
	NATSProbeTimeout time.Duration
	// NATSProbeAttempts is how many times to probe before declaring a live
	// daemon unreachable, guarding against a transient blip killing a healthy
	// daemon.
	NATSProbeAttempts int
	// StartupGrace skips records updated within this window, so a daemon that is
	// still coming up (PID alive, NATS not yet listening) is never mistaken for a
	// wedged orphan and killed.
	StartupGrace time.Duration
	// contains filtered or unexported fields
}

ReapOptions tunes orphan detection. The zero value is sensible — every field falls back to a default.

type Record

type Record struct {
	Name    string `json:"name"`
	Path    string `json:"path"`
	State   string `json:"state"`
	PID     int    `json:"pid,omitempty"`      // daemon process PID
	TUIPIDs []int  `json:"tui_pids,omitempty"` // all attached TUI process PIDs
	// AppClients is the number of desktop-app (WebSocket) clients currently
	// connected to the daemon's web server. The registry's State field tracks
	// TUI attachment only, so app sessions read "detached" even while the
	// desktop app is driving them — this field lets list/info render them as
	// "attached (app)". Maintained live by the daemon's web hub.
	AppClients int `json:"app_clients,omitempty"`
	NATSPort   int `json:"nats_port,omitempty"`
	// ProxyPort is the loopback governance proxy's port (design 001) while it is
	// running, or 0 when it is off. Maintained live by the daemon on ##proxy
	// on/off (read-modify-write, so attach bookkeeping is preserved), so tools
	// inspecting the registry — and ##session info — can see the governed
	// endpoint (http://127.0.0.1:{ProxyPort}) without attaching a TUI.
	ProxyPort int       `json:"proxy_port,omitempty"`
	UpdatedAt time.Time `json:"updated_at"`
	// Version and BinHash identify the rysh binary that the running daemon was
	// launched from. They are stamped by the daemon at startup and compared on
	// attach so a rebuilt binary can detect that the live daemon is outdated and
	// (with --upgrade) restart it. Version is the human-readable build string
	// (main.version, e.g. a git-describe tag); BinHash is a short content hash of
	// the executable, which changes on every rebuild even when Version does not
	// (e.g. repeated "-dirty" dev builds at the same commit).
	Version string `json:"version,omitempty"`
	BinHash string `json:"bin_hash,omitempty"`
	// ConfigFile is the rysh.config.yaml the daemon loaded (empty when none was
	// found), and RyshDir is the rysh directory that config location implied
	// (see config.resolveConfig). They are recorded so the session — and tools
	// that inspect the registry (e.g. "rysh list-sessions") — are aware of which
	// configuration and rysh state directory this session resolved at startup.
	ConfigFile string `json:"config_file,omitempty"`
	RyshDir    string `json:"rysh_dir,omitempty"`
	// Source identifies the front-end that owns the session: "cli" (rysh command
	// line) or "app" (rysh desktop app). It is stamped by the daemon at startup
	// from cfg.SessionSource (RYSH_SESSION_SOURCE). The two front-ends refuse to
	// open each other's sessions so the CLI TUI and the desktop app never drive
	// the same saved layout. Empty means legacy/unknown and is treated as
	// compatible with either front-end.
	Source string `json:"source,omitempty"`
}

func SelfHeal

func SelfHeal(existing Record, found bool, self Record) (healed Record, changed bool)

SelfHeal returns the record a live daemon should have on disk, given what is actually there. It is the daemon-side truth check behind the boot Upsert's standing guarantee (see startSessionRecordGuard in main): existing is the record read from the registry (found reports whether one exists), self is the record the daemon wrote at boot (name, path, PID, NATS port, version, config identity, source).

The record is left untouched (changed=false) when it already reflects this daemon: same PID, same NATS port, and a live state (running/detached) — the attach flow owns TUIPIDs and the running/detached flip, so a truthful record is never rewritten. Anything else (missing file, foreign or stale PID, wrong NATS port, state "stopped" while the daemon is alive) is replaced with the daemon's own identity; the TUI bookkeeping is preserved when the record was otherwise ours.

func WaitReady

func WaitReady(store *Store, name string, timeout time.Duration) (Record, error)

WaitReady polls the registry until the named session's daemon has recorded a live PID and NATS port (i.e. it is ready to accept connections), or until the timeout elapses. It is the same readiness gate the CLI uses after spawning a daemon.

func (*Record) AddTUIPID

func (r *Record) AddTUIPID(pid int)

AddTUIPID adds a TUI PID to the active list (idempotent).

func (*Record) AliveTUIPIDs

func (r *Record) AliveTUIPIDs() []int

AliveTUIPIDs returns only the PIDs that are still alive.

func (*Record) CleanStalePIDs

func (r *Record) CleanStalePIDs()

CleanStalePIDs removes dead PIDs from TUIPIDs.

func (*Record) DaemonAlive

func (r *Record) DaemonAlive() bool

DaemonAlive reports whether this record's daemon process is currently running.

func (*Record) HasAliveTUIs

func (r *Record) HasAliveTUIs() bool

HasAliveTUIs checks if any tracked TUI PIDs are still alive.

func (*Record) RemoveTUIPID

func (r *Record) RemoveTUIPID(pid int)

RemoveTUIPID removes a TUI PID from the active list.

type Store

type Store struct {
	// contains filtered or unexported fields
}

func NewStore

func NewStore(cfg config.Config) (*Store, error)

func (*Store) Delete

func (s *Store) Delete(name string) error

func (*Store) Get

func (s *Store) Get(name string) (Record, error)

func (*Store) List

func (s *Store) List() ([]Record, error)

func (*Store) Reap

func (s *Store) Reap(opts ReapOptions) ([]ReapAction, error)

Reap scans the registry and cleans up orphaned daemons:

  • A record whose daemon PID is dead is reconciled to stopped (this happens as a side effect of List; nothing is killed).
  • A daemon that is alive but unreachable on its recorded NATS port — i.e. wedged or zombied, no longer serving — is gracefully terminated (SIGTERM→SIGKILL via Terminate) and its record marked stopped.

Healthy daemons (PID alive AND NATS reachable) are deliberately left running, even when detached and idle: a long-lived detached session that you can re-attach to is an intended feature, so the reaper never kills a daemon that is still serving. To stop such a session, use "rysh stop" / "rysh delete-session".

Reaping is best-effort: a probe or terminate failure is recorded in the returned actions but never aborts the sweep. The returned slice contains only sessions the reaper acted on (or tried to).

func (*Store) Upsert

func (s *Store) Upsert(record Record) (Record, error)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL