Watch
0
0
Fork
You've already forked hyperhive
0

login session: only the child exiting finishes it, not one output pipe closing

This commit is contained in:
damocles 2026-10-21 21:27:26 +02:00 • committed by mara
commit 25f93d41e1

View file

@ -25,7 +25,7 @@ struct State {
/// First URL-looking substring we saw in the output. Surface this on the /// First URL-looking substring we saw in the output. Surface this on the
/// web UI as the link the operator should open. /// web UI as the link the operator should open.
url: Option<String>, url: Option<String>,
/// Set when the child has exited. The web UI uses this to know whether /// Set by `poll` when the child has exited. The web UI uses this to know whether
/// the operator can still paste a code. /// the operator can still paste a code.
finished: bool, finished: bool,
/// Exit status note (e.g. "exited with code 0", "killed by signal 15"), /// Exit status note (e.g. "exited with code 0", "killed by signal 15"),
@ -209,10 +209,10 @@ async fn pump<R: tokio::io::AsyncRead + Unpin>(
// we'll miss it until EOF (acceptable for the URL surface — claude // we'll miss it until EOF (acceptable for the URL surface — claude
// prints the URL on its own line). // prints the URL on its own line).
match reader.read_line(&mut buf).await { match reader.read_line(&mut buf).await {
Ok(0) => { // EOF means this pipe closed, not that the child exited: only
state.lock().unwrap().finished = true; // `poll` (via `try_wait`) may mark the session finished, or the
break; // next state render would drop a login that is still running.
} Ok(0) => break,
Ok(_) => { Ok(_) => {
let mut s = state.lock().unwrap(); let mut s = state.lock().unwrap();
if s.url.is_none() if s.url.is_none()
@ -225,9 +225,7 @@ async fn pump<R: tokio::io::AsyncRead + Unpin>(
} }
Err(e) => { Err(e) => {
tracing::warn!(error = ?e, %tag, "login pump read error"); tracing::warn!(error = ?e, %tag, "login pump read error");
let mut s = state.lock().unwrap(); state.lock().unwrap().exit_note = Some(format!("pump {tag} error: {e}"));
s.finished = true;
s.exit_note = Some(format!("pump {tag} error: {e}"));
break; break;
} }
} }
@ -277,7 +275,18 @@ impl Drop for LoginSession {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::extract_url; use super::{State, extract_url, pump};
use std::sync::{Arc, Mutex};
use tokio::io::BufReader;
#[tokio::test]
async fn a_closed_output_pipe_does_not_finish_the_session() {
let state = Arc::new(Mutex::new(State::default()));
pump(BufReader::new(tokio::io::empty()), state.clone(), "stdout").await;
assert!(!state.lock().unwrap().finished);
}
#[test] #[test]
fn picks_first_https() { fn picks_first_https() {