feat(daemon): serialize per-session command dispatch
- commit
- c81e58043d099f9c2016784efaea45c6c8cf0926
- author
- brice · · 2026-07-23 22:04 UTC
- parent
- dc2f115
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +9 -1
M internal/usecase/daemon/palette.go +4 -1
| @@ -303,7 +303,10 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||
| 303 | 303 | if !ac.closeExecutedPalette(generation, rawQuery) { | |
| 304 | 304 | return | |
| 305 | 305 | } | |
| 306 | - | if err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac}, args); err != nil { | |
| 306 | + | sess.dispatchMu.Lock() | |
| 307 | + | err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac}, args) | |
| 308 | + | sess.dispatchMu.Unlock() | |
| 309 | + | if err != nil { | |
| 307 | 310 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | |
| 308 | 311 | d.reportError(sess, err) | |
| 309 | 312 | } else { | |
| @@ -303,7 +303,10 @@ func (d *Daemon) handlePaletteInput(ac *attachedClient, data []byte) { | |||||
| 303 | if !ac.closeExecutedPalette(generation, rawQuery) { | 303 | if !ac.closeExecutedPalette(generation, rawQuery) { | ||
| 304 | return | 304 | return | ||
| 305 | } | 305 | } | ||
| 306 | - | if err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac}, args); err != nil { | 306 | + | sess.dispatchMu.Lock() |
| 307 | + | err := cmd.Run(paletteExec{d: d, sess: sess, ac: ac}, args) | |||
| 308 | + | sess.dispatchMu.Unlock() | |||
| 309 | + | if err != nil { | |||
| 307 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | 310 | d.log.Error("palette command failed", "err", err, "command", cmd.Code) | ||
| 308 | d.reportError(sess, err) | 311 | d.reportError(sess, err) | ||
| 309 | } else { | 312 | } else { | ||
M internal/usecase/daemon/session.go +5 -0
| @@ -36,6 +36,11 @@ type session struct { | |||
| 36 | 36 | ctx context.Context | |
| 37 | 37 | cancel context.CancelFunc | |
| 38 | 38 | ||
| 39 | + | // dispatchMu serializes user-initiated mutating command dispatch (palette | |
| 40 | + | // and vev-cmd control) for this session, so a one-shot command cannot | |
| 41 | + | // resolve focus against one state and mutate a later one. Lock order: | |
| 42 | + | // dispatchMu strictly before d.mu, sess.mu, or any tab mu. | |
| 43 | + | dispatchMu sync.Mutex | |
| 39 | 44 | mu sync.Mutex // guards tabs, active, client, clipFiles, and clipboard queue state | |
| 40 | 45 | themeMu sync.Mutex | |
| 41 | 46 | tabs []*tab | |
| @@ -36,6 +36,11 @@ type session struct { | |||||
| 36 | ctx context.Context | 36 | ctx context.Context | ||
| 37 | cancel context.CancelFunc | 37 | cancel context.CancelFunc | ||
| 38 | 38 | ||||
| 39 | + | // dispatchMu serializes user-initiated mutating command dispatch (palette | |||
| 40 | + | // and vev-cmd control) for this session, so a one-shot command cannot | |||
| 41 | + | // resolve focus against one state and mutate a later one. Lock order: | |||
| 42 | + | // dispatchMu strictly before d.mu, sess.mu, or any tab mu. | |||
| 43 | + | dispatchMu sync.Mutex | |||
| 39 | mu sync.Mutex // guards tabs, active, client, clipFiles, and clipboard queue state | 44 | mu sync.Mutex // guards tabs, active, client, clipFiles, and clipboard queue state | ||
| 40 | themeMu sync.Mutex | 45 | themeMu sync.Mutex | ||
| 41 | tabs []*tab | 46 | tabs []*tab | ||