From b66e0737d052cae41c10bda0b5016d86ab3e2d84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jose=20Luis=20Monta=C3=B1es=20Ojados?= Date: Sat, 17 Jan 2026 17:06:46 +0100 Subject: [PATCH] Fix deadlock on exit by making mic level updates async --- cmd/tui/model.go | 3 ++- pkg/audio/capture.go | 4 ++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/cmd/tui/model.go b/cmd/tui/model.go index 5f47956..a34d5c0 100644 --- a/cmd/tui/model.go +++ b/cmd/tui/model.go @@ -337,7 +337,8 @@ func (m *Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { // Update mic level for display (use the calculated level) if m.program != nil { - m.program.Send(micLevelMsg(level)) + // Use goroutine to prevent blocking the capture loop if the UI is busy (e.g. shutting down) + go m.program.Send(micLevelMsg(level)) } }) m.addLog("Audio capturer initialized") diff --git a/pkg/audio/capture.go b/pkg/audio/capture.go index 6f0572b..17fc267 100644 --- a/pkg/audio/capture.go +++ b/pkg/audio/capture.go @@ -22,6 +22,7 @@ type Capturer struct { running bool mu sync.Mutex stopChan chan struct{} + wg sync.WaitGroup // Callback for captured audio (called with 960-sample frames) onAudio func(samples []int16) @@ -136,6 +137,7 @@ func (c *Capturer) Start() error { return fmt.Errorf("failed to start audio client: %w", err) } + c.wg.Add(1) go c.captureLoop() return nil } @@ -151,6 +153,7 @@ func (c *Capturer) Stop() { c.mu.Unlock() close(c.stopChan) + c.wg.Wait() // Wait for capture loop to finish before proceeding c.client.Stop() } @@ -180,6 +183,7 @@ func (c *Capturer) IsRunning() bool { } func (c *Capturer) captureLoop() { + defer c.wg.Done() ticker := time.NewTicker(10 * time.Millisecond) // Check more often than 20ms defer ticker.Stop()