fix: surface doctor lock owner state (#105) (thanks @artemgetmann)

This commit is contained in:
Peter Steinberger 2026-04-21 04:52:33 +01:00
parent e73dba0ecb
commit 0ce920839e
No known key found for this signature in database
3 changed files with 96 additions and 12 deletions

View File

@ -8,6 +8,7 @@
### Fixed
- Doctor: report lock owner PID and distinguish paired stores locked by another process. (#105 — thanks @artemgetmann)
- Media: recover panics per download job so one bad payload no longer drains the worker pool. (#179 — thanks @shaun0927)
- Messages: attribute history messages from LID-addressed groups to the top-level participant sender. (#19 — thanks @entropyy0)
- Messages: show display text for replies, reactions, and media in `messages context`. (#183 — thanks @fuleinist)

View File

@ -5,6 +5,7 @@ import (
"fmt"
"os"
"path/filepath"
"strconv"
"strings"
"text/tabwriter"
@ -14,6 +15,31 @@ import (
"github.com/steipete/wacli/internal/out"
)
func parseLockOwnerPID(lockInfo string) int {
for _, line := range strings.Split(lockInfo, "\n") {
line = strings.TrimSpace(line)
if !strings.HasPrefix(line, "pid=") {
continue
}
pid, err := strconv.Atoi(strings.TrimSpace(strings.TrimPrefix(line, "pid=")))
if err == nil && pid > 0 {
return pid
}
}
return 0
}
func doctorConnectionState(authed, connected, lockHeld, connect bool) string {
switch {
case connected:
return "connected"
case authed && lockHeld && !connect:
return "locked_by_other_process"
default:
return "disconnected"
}
}
func newDoctorCmd(flags *rootFlags) *cobra.Command {
var connect bool
@ -57,23 +83,28 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
connected = true
}
}
lockOwnerPID := parseLockOwnerPID(lockInfo)
type report struct {
StoreDir string `json:"store_dir"`
LockHeld bool `json:"lock_held"`
LockInfo string `json:"lock_info,omitempty"`
Authed bool `json:"authenticated"`
Connected bool `json:"connected"`
FTSEnabled bool `json:"fts_enabled"`
StoreDir string `json:"store_dir"`
LockHeld bool `json:"lock_held"`
LockInfo string `json:"lock_info,omitempty"`
LockOwnerPID int `json:"lock_owner_pid,omitempty"`
Authed bool `json:"authenticated"`
Connected bool `json:"connected"`
ConnectionState string `json:"connection_state"`
FTSEnabled bool `json:"fts_enabled"`
}
rep := report{
StoreDir: storeDir,
LockHeld: lockHeld,
LockInfo: lockInfo,
Authed: authed,
Connected: connected,
FTSEnabled: a.DB().HasFTS(),
StoreDir: storeDir,
LockHeld: lockHeld,
LockInfo: lockInfo,
LockOwnerPID: lockOwnerPID,
Authed: authed,
Connected: connected,
ConnectionState: doctorConnectionState(authed, connected, lockHeld, connect),
FTSEnabled: a.DB().HasFTS(),
}
if flags.asJSON {
@ -86,8 +117,12 @@ func newDoctorCmd(flags *rootFlags) *cobra.Command {
if rep.LockHeld && rep.LockInfo != "" {
fmt.Fprintf(w, "LOCK_INFO\t%s\n", rep.LockInfo)
}
if rep.LockOwnerPID > 0 {
fmt.Fprintf(w, "LOCK_OWNER_PID\t%d\n", rep.LockOwnerPID)
}
fmt.Fprintf(w, "AUTHENTICATED\t%v\n", rep.Authed)
fmt.Fprintf(w, "CONNECTED\t%v\n", rep.Connected)
fmt.Fprintf(w, "CONNECTION_STATE\t%s\n", rep.ConnectionState)
fmt.Fprintf(w, "FTS5\t%v\n", rep.FTSEnabled)
_ = w.Flush()

48
cmd/wacli/doctor_test.go Normal file
View File

@ -0,0 +1,48 @@
package main
import "testing"
func TestParseLockOwnerPID(t *testing.T) {
tests := []struct {
name string
info string
want int
}{
{name: "pid line", info: "pid=50394\nacquired_at=2026-04-05T12:30:11Z", want: 50394},
{name: "trimmed pid", info: " pid= 42 ", want: 42},
{name: "missing pid", info: "acquired_at=2026-04-05T12:30:11Z"},
{name: "invalid pid", info: "pid=abc"},
{name: "zero pid", info: "pid=0"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
if got := parseLockOwnerPID(tc.info); got != tc.want {
t.Fatalf("parseLockOwnerPID() = %d, want %d", got, tc.want)
}
})
}
}
func TestDoctorConnectionState(t *testing.T) {
tests := []struct {
name string
authed bool
connected bool
lockHeld bool
connect bool
want string
}{
{name: "connected wins", authed: true, connected: true, lockHeld: true, want: "connected"},
{name: "locked paired session", authed: true, lockHeld: true, want: "locked_by_other_process"},
{name: "connect requested stays disconnected", authed: true, lockHeld: true, connect: true, want: "disconnected"},
{name: "plain disconnected", authed: true, want: "disconnected"},
}
for _, tc := range tests {
t.Run(tc.name, func(t *testing.T) {
got := doctorConnectionState(tc.authed, tc.connected, tc.lockHeld, tc.connect)
if got != tc.want {
t.Fatalf("doctorConnectionState() = %q, want %q", got, tc.want)
}
})
}
}