Apply the suggested optimization on top of the previous race fix.
- internal/process/manager.go: LocalManager.sessionsMu is now an
RWMutex. Read paths (Status, Subscribe lookup, Stdin lookup,
ExitStatus lookup) take RLock; mutating paths (Start, Stop,
Restart) take Lock. exited is now a sync.Map instead of a
hand-rolled map+mutex; reads (IsExited) are lock-free, writes
(MarkAsExited from waitExit, ClearExited on Start/Stop/Restart)
are atomic. Added IsExited / MarkAsExited / ClearExited helpers.
No behavior change. The previous race fix (the only one in the
package) is preserved: waitExit remains the sole caller of
sess.Cmd.Wait(), and all other code paths read the exited marker
rather than cmd.ProcessState.
- ACP: nothing to do. The ACP service talks to opencode through
process.Manager; it never reads cmd.ProcessState directly. The
process package race fix already covers the ACP code path.
Verified: go test -race -count=2 ./... clean across the repo.
Note: at this app's concurrency level (tens of workspaces, accessed
occasionally), neither sync.RWMutex nor sync.Map measurably beats
the previous map+mutex pair — the critical sections are O(1)
lookups with no contention. The change is mostly stylistic
(removes one lock, fewer deadlock surfaces, more idiomatic Go).
E2E: start / status(running) / duplicate(409) / stop / status(stopped)
/ restart / status(running) / stop all behave correctly.