Documentation
¶
Overview ¶
Package rescue provides the out-of-band recovery path for a wedged tmux server. Every normal webui operation is a tmux client call, so a server stuck in a busy-loop (e.g. a re-entrant hook chain) takes the whole UI down with it: each call blocks until timeout. This package never asks the server for state — it discovers the server process via ps, samples stack traces, and kills by PID — so it works exactly when tmux itself does not.
Escalation ladder (mirrors the field-tested SOP): collect evidence first, then kill stuck hook children, and only force-kill the server + clean its socket as the last step.
Index ¶
- func SocketDir() string
- type Evidence
- type KillReport
- type Proc
- type Rescuer
- func (r *Rescuer) Diagnose(ctx context.Context) Evidence
- func (r *Rescuer) HandleDiagnose(w http.ResponseWriter, req *http.Request)
- func (r *Rescuer) HandleKillHooks(w http.ResponseWriter, req *http.Request)
- func (r *Rescuer) HandleKillServer(w http.ResponseWriter, req *http.Request)
- func (r *Rescuer) HandleStatus(w http.ResponseWriter, req *http.Request)
- func (r *Rescuer) KillHooks(ctx context.Context) (KillReport, error)
- func (r *Rescuer) KillServer(ctx context.Context) (KillReport, error)
- func (r *Rescuer) Probe(ctx context.Context) Status
- type Status
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
Types ¶
type KillReport ¶
type KillReport struct {
Killed []Proc `json:"killed"`
SocketRemoved bool `json:"socket_removed,omitempty"`
After Status `json:"after"`
}
KillReport describes what a kill action actually did.
type Proc ¶
type Proc struct {
PID int `json:"pid"`
PPID int `json:"ppid"`
TTY string `json:"tty"`
CPU float64 `json:"cpu"`
Etime string `json:"etime"`
Command string `json:"command"`
}
Proc is one row of the ps snapshot.
type Rescuer ¶
type Rescuer struct {
// contains filtered or unexported fields
}
Rescuer performs the out-of-band operations. The exec/kill seams are injectable so tests never touch real processes.
func (*Rescuer) Diagnose ¶
Diagnose collects the evidence bundle: probe status, the tmux slice of ps, and (macOS) a stack sample of each server candidate. Persisted best-effort under the cache dir so it survives the rescue that usually follows.
func (*Rescuer) HandleDiagnose ¶
func (r *Rescuer) HandleDiagnose(w http.ResponseWriter, req *http.Request)
HandleDiagnose — POST /api/rescue/diagnose. Read-only evidence collection; can take a few seconds when stack-sampling a spinning server.
func (*Rescuer) HandleKillHooks ¶
func (r *Rescuer) HandleKillHooks(w http.ResponseWriter, req *http.Request)
HandleKillHooks — POST /api/rescue/kill-hooks. Middle rung of the ladder: only ever signals children of a tmux server process.
func (*Rescuer) HandleKillServer ¶
func (r *Rescuer) HandleKillServer(w http.ResponseWriter, req *http.Request)
HandleKillServer — POST /api/rescue/kill-server. Destructive (all tmux sessions die), so it demands an explicit {"confirm":true} body — a bare POST from a stray fetch or curl must not be enough.
func (*Rescuer) HandleStatus ¶
func (r *Rescuer) HandleStatus(w http.ResponseWriter, req *http.Request)
HandleStatus — GET /api/rescue. Cheap enough to poll: one short-deadline tmux call + one ps.
func (*Rescuer) KillHooks ¶
func (r *Rescuer) KillHooks(ctx context.Context) (KillReport, error)
KillHooks terminates the TTY-LESS direct children of every tmux server candidate — the stuck hook scripts that keep the server's event loop spinning. Pane shells are also direct children of the server but sit on a pty, so hasTTY excludes them (killing those would destroy every session's running programs — verified live: a healthy server showed 14 pane shells as direct children). TERM first, then KILL for survivors.
func (*Rescuer) KillServer ¶
func (r *Rescuer) KillServer(ctx context.Context) (KillReport, error)
KillServer is the last rung: SIGKILL every server candidate (hook children first), wait for them to disappear, then remove the socket dir so the next tmux invocation starts a clean server. Pane shells are deliberately NOT signalled — closing the server tears down their ptys, which is how a manual `pkill -9 tmux` ends them too. Refuses to clean the socket while a candidate is still alive — a live server owning a removed socket is a worse state than the one we started in.
type Status ¶
type Status struct {
Tmux string `json:"tmux"` // ok | no_server | wedged | error
Detail string `json:"detail,omitempty"`
Servers []Proc `json:"servers"`
HookChildren []Proc `json:"hook_children"`
SocketDir string `json:"socket_dir"`
SocketExists bool `json:"socket_exists"`
// InsideTmux warns that this webui process itself lives in a tmux pane:
// force-killing the server would take the webui down with it.
InsideTmux bool `json:"inside_tmux"`
}
Status is the out-of-band health picture returned by Probe.