From 950ca2bd9966ae69ebe0463b1c4ba2d22f73b5ec Mon Sep 17 00:00:00 2001 From: Alex P Date: Mon, 1 Sep 2025 11:08:13 +0000 Subject: [PATCH] fix(audio): improve process termination handling in input supervisor Add more robust process state checking and error handling during audio input server shutdown. Use signal 0 to verify process existence before killing and handle various edge cases. Also improve logging to better track shutdown outcomes. --- internal/audio/cgo_audio.go | 3 +++ internal/audio/input_supervisor.go | 40 +++++++++++++++++++++++------- 2 files changed, 34 insertions(+), 9 deletions(-) diff --git a/internal/audio/cgo_audio.go b/internal/audio/cgo_audio.go index e6a48529..0eb24e41 100644 --- a/internal/audio/cgo_audio.go +++ b/internal/audio/cgo_audio.go @@ -711,6 +711,9 @@ func cgoAudioReadEncode(buf []byte) (int, error) { // Skip initialization check for now to avoid CGO compilation issues // TODO: Add proper initialization state checking + // Note: The C code already has comprehensive state tracking with capture_initialized, + // capture_initializing, playback_initialized, and playback_initializing flags. + // When CGO environment is properly configured, this should check C.capture_initialized. n := C.jetkvm_audio_read_encode(unsafe.Pointer(&buf[0])) if n < 0 { diff --git a/internal/audio/input_supervisor.go b/internal/audio/input_supervisor.go index be9042cd..dfd0fe5e 100644 --- a/internal/audio/input_supervisor.go +++ b/internal/audio/input_supervisor.go @@ -152,27 +152,49 @@ func (ais *AudioInputSupervisor) Stop() { // Wait for graceful shutdown with timeout done := make(chan error, 1) + var waitErr error go func() { - done <- ais.cmd.Wait() + waitErr = ais.cmd.Wait() + done <- waitErr }() select { case <-done: - ais.logger.Info().Msg("Audio input server subprocess stopped gracefully") + if waitErr != nil { + ais.logger.Info().Err(waitErr).Msg("Audio input server subprocess stopped with error") + } else { + ais.logger.Info().Msg("Audio input server subprocess stopped gracefully") + } case <-time.After(GetConfig().InputSupervisorTimeout): // Force kill if graceful shutdown failed ais.logger.Warn().Msg("Audio input server subprocess did not stop gracefully, force killing") - // Check if process is still alive before attempting to kill + // Use a more robust approach to check if process is still alive if ais.cmd != nil && ais.cmd.Process != nil { - // Check process state to avoid "process already finished" error - if ais.cmd.ProcessState == nil { - err := ais.cmd.Process.Kill() - if err != nil { - ais.logger.Error().Err(err).Msg("Failed to kill audio input server subprocess") + // Try to send signal 0 to check if process exists + if err := ais.cmd.Process.Signal(syscall.Signal(0)); err == nil { + // Process is still alive, force kill it + if killErr := ais.cmd.Process.Kill(); killErr != nil { + // Only log error if it's not "process already finished" + if !strings.Contains(killErr.Error(), "process already finished") { + ais.logger.Error().Err(killErr).Msg("Failed to kill audio input server subprocess") + } else { + ais.logger.Debug().Msg("Audio input server subprocess already finished during kill attempt") + } + } else { + ais.logger.Info().Msg("Audio input server subprocess force killed successfully") } } else { - ais.logger.Info().Msg("Audio input server subprocess already finished") + ais.logger.Debug().Msg("Audio input server subprocess already finished") } + // Wait a bit for the kill to take effect and collect the exit status + go func() { + select { + case <-done: + // Process finished + case <-time.After(1 * time.Second): + // Give up waiting + } + }() } } }