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
|
/// feed reads) run without holding that lock so they cannot delay interactive
|
||||||
/// ones such as selecting a profile.
|
/// ones such as selecting a profile.
|
||||||
pub async fn serve() -> Result<(), AppError> {
|
pub async fn serve() -> Result<(), AppError> {
|
||||||
use tokio::io::AsyncBufReadExt;
|
|
||||||
use tokio::task::JoinSet;
|
use tokio::task::JoinSet;
|
||||||
|
|
||||||
// Shared state, so the NIP-46 signer's background task and the request loop
|
// 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 stdout = Arc::new(tokio::sync::Mutex::new(tokio::io::stdout()));
|
||||||
|
|
||||||
let stdin = tokio::io::stdin();
|
// Read stdin on a dedicated blocking thread, not tokio::io::stdin().
|
||||||
let mut lines = tokio::io::BufReader::new(stdin).lines();
|
// 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();
|
let mut tasks = JoinSet::new();
|
||||||
|
|
||||||
while let Some(line) = lines
|
while let Some(line) = line_rx.recv().await {
|
||||||
.next_line()
|
|
||||||
.await
|
|
||||||
.map_err(|e| AppError::io("Could not read from stdin", e))?
|
|
||||||
{
|
|
||||||
if line.trim().is_empty() {
|
if line.trim().is_empty() {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue