refactor(shell): sync.RWMutex for sessions + sync.Map for exited (mirror process pkg)
Same shape as the process package refactor:
- LocalManager.mu (sync.Mutex) -> sessionsMu (sync.RWMutex).
Read paths (Status, Subscribe, Stdin, ExitStatus, Resize) take
RLock; write paths (Start, Stop, Restart, List) take Lock.
- LocalManager.exited: was a hand-rolled map[workspaceID]map[shellID]struct{}
guarded by exitedMu; now a sync.Map keyed by shellID only (UUID
is globally unique, no need for the nested map). Helpers
IsExited / MarkAsExited / ClearExited.
- shellOrder stays a plain map; read+written under sessionsMu.
- waitExit remains the sole caller of MarkAsExited; Start /
Stop / Restart call ClearExited.
- New TestShellIsExitedHelpers covers the helper semantics.
go test -race -count=2 ./... clean. Same caveat as the process
package: at this app's concurrency level, neither sync.RWMutex nor
sync.Map measurably beats the previous pair — the change is mostly
stylistic (one fewer lock, no nested maps, more idiomatic Go).
Conversation: 019f3673-d2d5-78f0-a7a9-5e3e91b65933
This commit is contained in:
@@ -201,3 +201,50 @@ func TestShellMultiInstance(t *testing.T) {
|
||||
t.Fatalf("List returned shell %q, want %q", list[0].ShellID, shellID2)
|
||||
}
|
||||
}
|
||||
|
||||
func TestShellIsExitedHelpers(t *testing.T) {
|
||||
mgr := NewManager("bash", []string{"-i"})
|
||||
root := t.TempDir()
|
||||
|
||||
shellIDA, err := mgr.Start("test-ws", root)
|
||||
if err != nil {
|
||||
t.Fatalf("Start shell A failed: %v", err)
|
||||
}
|
||||
shellIDB, err := mgr.Start("test-ws", root)
|
||||
if err != nil {
|
||||
t.Fatalf("Start shell B failed: %v", err)
|
||||
}
|
||||
|
||||
t.Cleanup(func() {
|
||||
_ = mgr.Stop("test-ws", shellIDA)
|
||||
_ = mgr.Stop("test-ws", shellIDB)
|
||||
})
|
||||
|
||||
if mgr.IsExited(shellIDA) {
|
||||
t.Errorf("IsExited(A) = true, want false")
|
||||
}
|
||||
if mgr.IsExited(shellIDB) {
|
||||
t.Errorf("IsExited(B) = true, want false")
|
||||
}
|
||||
|
||||
if err := mgr.Stop("test-ws", shellIDA); err != nil {
|
||||
t.Fatalf("Stop shell A failed: %v", err)
|
||||
}
|
||||
|
||||
if mgr.IsExited(shellIDA) {
|
||||
t.Errorf("IsExited(A) = true after Stop, want false")
|
||||
}
|
||||
if mgr.IsExited(shellIDB) {
|
||||
t.Errorf("IsExited(B) = true after stopping A, want false")
|
||||
}
|
||||
|
||||
mgr.MarkAsExited(shellIDB)
|
||||
if !mgr.IsExited(shellIDB) {
|
||||
t.Errorf("IsExited(B) = false after MarkAsExited, want true")
|
||||
}
|
||||
|
||||
mgr.ClearExited(shellIDB)
|
||||
if mgr.IsExited(shellIDB) {
|
||||
t.Errorf("IsExited(B) = true after ClearExited, want false")
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user