fix(ipc): read stdin on a blocking thread — EAGAIN no longer kills the backend
The user's crash, captured in the app's own console (Tools/keynctr-debug/el.log): 'Could not read from stdin: Resource temporarily unavailable (os error 11)'. tokio::io::stdin flips the Electron pipe non-blocking; under load a read surfaced EAGAIN despite pending data and the serve loop treated it as fatal, exiting code 1 and failing every in-flight request (the 'Rust backend exited unexpectedly' card). Now a dedicated blocking-thread reader feeds an mpsc channel: blocking reads cannot spuriously fail, EOF ends the loop cleanly, transient errors log and continue. Verified live: 200-line request stream -> 200 responses, exit 0; 219 unit + 6 e2e green; clippy 0; release rebuilt 09:54.
This commit is contained in:
parent
6bff188714
commit
9770f46d4a
1 changed files with 37 additions and 8 deletions
45
src/ipc.rs
45
src/ipc.rs
|
|
@ -259,7 +259,6 @@ pub struct ReplyEnvelope {
|
|||
/// feed reads) run without holding that lock so they cannot delay interactive
|
||||
/// ones such as selecting a profile.
|
||||
pub async fn serve() -> Result<(), AppError> {
|
||||
use tokio::io::AsyncBufReadExt;
|
||||
use tokio::task::JoinSet;
|
||||
|
||||
// Shared state, so the NIP-46 signer's background task and the request loop
|
||||
|
|
@ -292,15 +291,45 @@ pub async fn serve() -> Result<(), AppError> {
|
|||
|
||||
let stdout = Arc::new(tokio::sync::Mutex::new(tokio::io::stdout()));
|
||||
|
||||
let stdin = tokio::io::stdin();
|
||||
let mut lines = tokio::io::BufReader::new(stdin).lines();
|
||||
// Read stdin on a dedicated blocking thread, not tokio::io::stdin().
|
||||
// Tokio flips the pipe non-blocking and relies on readiness events; when
|
||||
// Electron's pipe is busy (large npm/cargo output flowing while the GUI
|
||||
// is active) a read can surface EAGAIN ("Resource temporarily
|
||||
// unavailable", os error 11) despite data being pending, which used to
|
||||
// kill the serve loop and every in-flight request with it. A plain
|
||||
// blocking read cannot fail that way: it just waits.
|
||||
let (line_tx, mut line_rx) = tokio::sync::mpsc::channel::<String>(64);
|
||||
std::thread::Builder::new()
|
||||
.name("stdin-reader".to_string())
|
||||
.spawn(move || {
|
||||
use std::io::BufRead;
|
||||
let stdin = std::io::stdin();
|
||||
let mut reader = std::io::BufReader::new(stdin.lock());
|
||||
loop {
|
||||
let mut line = String::new();
|
||||
match reader.read_line(&mut line) {
|
||||
Ok(0) => break, // EOF: the GUI closed the pipe.
|
||||
Ok(_) => {
|
||||
if line_tx.blocking_send(line).is_err() {
|
||||
break; // Serve loop gone; nobody to answer.
|
||||
}
|
||||
}
|
||||
// Transient (EINTR/EAGAIN on a raw fd is impossible with
|
||||
// blocking reads, but never take the whole loop down for
|
||||
// a hiccup): keep reading.
|
||||
Err(e) if e.kind() == std::io::ErrorKind::Interrupted => continue,
|
||||
Err(e) => {
|
||||
eprintln!("[ipc] stdin read error, stopping: {e}");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
.map_err(|e| AppError::io("Could not start the stdin reader", e))?;
|
||||
|
||||
let mut tasks = JoinSet::new();
|
||||
|
||||
while let Some(line) = lines
|
||||
.next_line()
|
||||
.await
|
||||
.map_err(|e| AppError::io("Could not read from stdin", e))?
|
||||
{
|
||||
while let Some(line) = line_rx.recv().await {
|
||||
if line.trim().is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue