fix(audio): log dropped capture errors instead of silently discarding
The cpal stream-error closure used `let _ = err_tx.try_send(...)` against a bounded sync_channel(16). If the live session's listener stalled or the frontend disconnected, runtime stream errors were silently dropped — the diagnostic bundle showed nothing for a session that mysteriously stopped working. - Bump the error channel capacity 16 → 32 (matches AUDIO_CHANNEL_CAPACITY). - On try_send failure, log to stderr with the device name + a per-session drop counter so the symptom is visible in the diagnostic bundle even when the typed event never reached the frontend. - Plumb a new `dropped_errors: Arc<AtomicU64>` through `build_input_stream` alongside the existing `dropped_chunks`, mirroring the same pattern. (kon-audio doesn't build in the audit sandbox: it links against ALSA which the sandbox lacks. CI cross-platform compiles it.) https://claude.ai/code/session_0189xUb6ie6t9qHkzatGZ9Rb
This commit is contained in:
@@ -370,11 +370,15 @@ fn open_and_validate(
|
||||
let (tx, rx) = mpsc::sync_channel::<AudioChunk>(AUDIO_CHANNEL_CAPACITY);
|
||||
let requeue_tx = tx.clone();
|
||||
let dropped_chunks = Arc::new(AtomicU64::new(0));
|
||||
// Bounded channel for runtime stream errors. Capacity 16 = plenty for
|
||||
// the rare error case; if it ever fills, we drop newer errors silently
|
||||
// because they would be redundant noise in a stream that is already
|
||||
// failing. (Codex review 2026/04/17 M2)
|
||||
let (err_tx, err_rx) = mpsc::sync_channel::<CaptureRuntimeError>(16);
|
||||
// Bounded channel for runtime stream errors. Capacity 32 = plenty for
|
||||
// the rare error case; if it ever fills, drops are reported via stderr
|
||||
// and counted in `dropped_errors` so the symptom is visible in the
|
||||
// diagnostic bundle even when the listener has gone away. Errors
|
||||
// beyond the cap are by definition redundant noise in a stream that
|
||||
// is already failing. (Codex review 2026/04/17 M2; capacity bump and
|
||||
// drop logging added 2026/04/25 audit pass.)
|
||||
let (err_tx, err_rx) = mpsc::sync_channel::<CaptureRuntimeError>(32);
|
||||
let dropped_errors = Arc::new(AtomicU64::new(0));
|
||||
|
||||
let stream = match format {
|
||||
SampleFormat::F32 => build_input_stream::<f32>(
|
||||
@@ -385,6 +389,7 @@ fn open_and_validate(
|
||||
tx,
|
||||
dropped_chunks.clone(),
|
||||
err_tx.clone(),
|
||||
dropped_errors.clone(),
|
||||
name.to_string(),
|
||||
),
|
||||
SampleFormat::I16 => build_input_stream::<i16>(
|
||||
@@ -395,6 +400,7 @@ fn open_and_validate(
|
||||
tx,
|
||||
dropped_chunks.clone(),
|
||||
err_tx.clone(),
|
||||
dropped_errors.clone(),
|
||||
name.to_string(),
|
||||
),
|
||||
SampleFormat::U16 => build_input_stream::<u16>(
|
||||
@@ -405,6 +411,7 @@ fn open_and_validate(
|
||||
tx,
|
||||
dropped_chunks.clone(),
|
||||
err_tx.clone(),
|
||||
dropped_errors.clone(),
|
||||
name.to_string(),
|
||||
),
|
||||
other => {
|
||||
@@ -503,6 +510,7 @@ fn build_input_stream<T>(
|
||||
tx: mpsc::SyncSender<AudioChunk>,
|
||||
dropped_chunks: Arc<AtomicU64>,
|
||||
err_tx: mpsc::SyncSender<CaptureRuntimeError>,
|
||||
dropped_errors: Arc<AtomicU64>,
|
||||
device_name: String,
|
||||
) -> std::result::Result<cpal::Stream, cpal::BuildStreamError>
|
||||
where
|
||||
@@ -532,10 +540,24 @@ where
|
||||
// frontend can show a toast. Also keep the eprintln for ops
|
||||
// logs. (Codex review 2026/04/17 M2)
|
||||
eprintln!("[kon-audio] capture error: {err}");
|
||||
let _ = err_tx.try_send(CaptureRuntimeError {
|
||||
if err_tx
|
||||
.try_send(CaptureRuntimeError {
|
||||
device_name: err_device_name.clone(),
|
||||
message: err.to_string(),
|
||||
});
|
||||
})
|
||||
.is_err()
|
||||
{
|
||||
// Channel full — listener has stalled or detached. Note
|
||||
// it in stderr and the dropped-errors counter so the
|
||||
// diagnostic bundle still shows the symptom even if the
|
||||
// frontend never received the typed event.
|
||||
let prior = dropped_errors.fetch_add(1, Ordering::Relaxed);
|
||||
eprintln!(
|
||||
"[kon-audio] capture error channel full; dropped error #{} for device '{}'",
|
||||
prior + 1,
|
||||
err_device_name,
|
||||
);
|
||||
}
|
||||
},
|
||||
None,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user