[fix](trx-server): prevent audio thread crash on ALSA EPIPE
CPAL error callbacks can fire millions of times per second on ALSA EPIPE (errno -32). Previously each invocation did a string allocation and mutex lock, saturating CPU and eventually crashing the server. - Use atomic swap in both input and output error callbacks so only the first error fires the expensive log+notify path; all subsequent hits cost a single atomic op and return immediately. - Replace stream.play()? with explicit error handling in run_playback so a play failure triggers stream recreation instead of permanently terminating the playback thread. Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com> Signed-off-by: Stan Grams <stanislawgrams@gmail.com>
This commit is contained in:
+24
-11
@@ -317,9 +317,13 @@ fn run_capture(
|
|||||||
let stream_failed = stream_failed.clone();
|
let stream_failed = stream_failed.clone();
|
||||||
let stream_err_tx = stream_err_tx.clone();
|
let stream_err_tx = stream_err_tx.clone();
|
||||||
move |err| {
|
move |err| {
|
||||||
input_err_logger.log(&err.to_string());
|
// swap ensures only the first error does expensive work;
|
||||||
stream_failed.store(true, Ordering::SeqCst);
|
// subsequent callbacks (can fire millions/s on ALSA EPIPE)
|
||||||
let _ = stream_err_tx.try_send(());
|
// return immediately after a single atomic op.
|
||||||
|
if !stream_failed.swap(true, Ordering::SeqCst) {
|
||||||
|
input_err_logger.log(&err.to_string());
|
||||||
|
let _ = stream_err_tx.try_send(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
@@ -503,9 +507,13 @@ fn run_playback(
|
|||||||
let stream_failed = stream_failed.clone();
|
let stream_failed = stream_failed.clone();
|
||||||
let stream_err_tx = stream_err_tx.clone();
|
let stream_err_tx = stream_err_tx.clone();
|
||||||
move |err| {
|
move |err| {
|
||||||
output_err_logger.log(&err.to_string());
|
// swap ensures only the first error does expensive work;
|
||||||
stream_failed.store(true, Ordering::SeqCst);
|
// subsequent callbacks (can fire millions/s on ALSA EPIPE)
|
||||||
let _ = stream_err_tx.try_send(());
|
// return immediately after a single atomic op.
|
||||||
|
if !stream_failed.swap(true, Ordering::SeqCst) {
|
||||||
|
output_err_logger.log(&err.to_string());
|
||||||
|
let _ = stream_err_tx.try_send(());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
None,
|
None,
|
||||||
@@ -521,10 +529,12 @@ fn run_playback(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
if !playing {
|
if playing {
|
||||||
// stay paused until packets arrive
|
if let Err(e) = stream.play() {
|
||||||
} else {
|
warn!("Audio playback: stream.play failed, recreating: {}", e);
|
||||||
stream.play()?;
|
std::thread::sleep(AUDIO_STREAM_RECOVERY_DELAY);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
@@ -544,7 +554,10 @@ fn run_playback(
|
|||||||
match rx.try_recv() {
|
match rx.try_recv() {
|
||||||
Ok(packet) => {
|
Ok(packet) => {
|
||||||
if !playing {
|
if !playing {
|
||||||
stream.play()?;
|
if let Err(e) = stream.play() {
|
||||||
|
warn!("Audio playback: stream.play failed, recreating: {}", e);
|
||||||
|
break;
|
||||||
|
}
|
||||||
playing = true;
|
playing = true;
|
||||||
info!("Audio playback: started");
|
info!("Audio playback: started");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user