Fix startup command and holder fallback
ci / test (push) Has been cancelled
ci / fuzz-smoke (push) Has been cancelled
ci / windows-client (push) Has been cancelled
ci / package-release (linux-x86_64, ubuntu-latest) (push) Has been cancelled
ci / package-release (macos-aarch64, macos-14) (push) Has been cancelled
ci / package-release (macos-x86_64, macos-13) (push) Has been cancelled
ci / package-release (windows-x86_64, windows-latest) (push) Has been cancelled
ci / remote-bench (push) Has been cancelled

This commit is contained in:
DuProcess
2026-06-26 19:31:33 -04:00
parent 60d73df5be
commit 4842a129f2
2 changed files with 14 additions and 9 deletions
+9 -8
View File
@@ -1048,7 +1048,7 @@ fn startup_command(args: &[String]) -> Option<Vec<u8>> {
return None; return None;
} }
let mut command = args.join(" "); let mut command = args.join(" ");
command.push('\n'); command.push('\r');
Some(command.into_bytes()) Some(command.into_bytes())
} }
@@ -1121,7 +1121,7 @@ fn expand_command_extension(template: &str, args: &[String]) -> String {
fn startup_command_from_string(command: &str) -> Vec<u8> { fn startup_command_from_string(command: &str) -> Vec<u8> {
let mut command = command.to_string(); let mut command = command.to_string();
command.push('\n'); command.push('\r');
command.into_bytes() command.into_bytes()
} }
@@ -5540,6 +5540,7 @@ mod tests {
assert!(Predictor::predictable(b"hello world")); assert!(Predictor::predictable(b"hello world"));
assert!(!Predictor::predictable(b"")); assert!(!Predictor::predictable(b""));
assert!(!Predictor::predictable(b"\n")); assert!(!Predictor::predictable(b"\n"));
assert!(!Predictor::predictable(b"\r"));
assert!(!Predictor::predictable(b"\x1b[A")); assert!(!Predictor::predictable(b"\x1b[A"));
assert!(!Predictor::predictable("snowman: \u{2603}".as_bytes())); assert!(!Predictor::predictable("snowman: \u{2603}".as_bytes()));
assert!(!Predictor::predictable("wide: 你".as_bytes())); assert!(!Predictor::predictable("wide: 你".as_bytes()));
@@ -5760,10 +5761,10 @@ mod tests {
#[test] #[test]
fn startup_command_joins_args_for_remote_shell() { fn startup_command_joins_args_for_remote_shell() {
assert_eq!(startup_command(&[]), None); assert_eq!(startup_command(&[]), None);
assert_eq!(startup_command(&["tm".to_string()]), Some(b"tm\n".to_vec())); assert_eq!(startup_command(&["tm".to_string()]), Some(b"tm\r".to_vec()));
assert_eq!( assert_eq!(
startup_command(&["tm".to_string(), "work".to_string()]), startup_command(&["tm".to_string(), "work".to_string()]),
Some(b"tm work\n".to_vec()) Some(b"tm work\r".to_vec())
); );
} }
@@ -5845,7 +5846,7 @@ mod tests {
assert_eq!( assert_eq!(
resolved_startup_command(&["tm".into(), "work space".into()], &config, &host), resolved_startup_command(&["tm".into(), "work space".into()], &config, &host),
Some(b"tm 'work space'\n".to_vec()) Some(b"tm 'work space'\r".to_vec())
); );
} }
@@ -5872,7 +5873,7 @@ mod tests {
assert_eq!( assert_eq!(
resolved_startup_command(&["tm".into(), "dosh".into()], &config, &host), resolved_startup_command(&["tm".into(), "dosh".into()], &config, &host),
Some(b"/opt/tm/bin/tm --remote 'dosh'\n".to_vec()) Some(b"/opt/tm/bin/tm --remote 'dosh'\r".to_vec())
); );
} }
@@ -5913,11 +5914,11 @@ mod tests {
assert_eq!( assert_eq!(
resolved_startup_command(&["echo".into(), "ok".into()], &config, &host), resolved_startup_command(&["echo".into(), "ok".into()], &config, &host),
Some(b"echo ok\n".to_vec()) Some(b"echo ok\r".to_vec())
); );
assert_eq!( assert_eq!(
resolved_startup_command(&[], &config, &host), resolved_startup_command(&[], &config, &host),
Some(b"tm\n".to_vec()) Some(b"tm\r".to_vec())
); );
host.default_command = None; host.default_command = None;
+5 -1
View File
@@ -28,6 +28,7 @@ use std::time::Duration;
/// One-byte commands a server sends to a holder over its control socket after /// One-byte commands a server sends to a holder over its control socket after
/// the holder has handed back the master fd. /// the holder has handed back the master fd.
const HOLDER_CMD_SHUTDOWN: u8 = b'X'; const HOLDER_CMD_SHUTDOWN: u8 = b'X';
const HOLDER_STARTUP_TIMEOUT: Duration = Duration::from_millis(750);
/// Magic written into a holder's `meta` file, bumped if the on-disk layout /// Magic written into a holder's `meta` file, bumped if the on-disk layout
/// changes so a stale holder from an incompatible build is ignored. /// changes so a stale holder from an incompatible build is ignored.
@@ -236,11 +237,14 @@ pub fn spawn_holder(
// Wait (briefly) for the holder to come up. The holder forks/setsids before // Wait (briefly) for the holder to come up. The holder forks/setsids before
// creating the socket; once the socket exists we can connect. We also reap // creating the socket; once the socket exists we can connect. We also reap
// the short-lived launcher child so it never becomes a zombie. // the short-lived launcher child so it never becomes a zombie.
let deadline = std::time::Instant::now() + Duration::from_secs(5); let deadline = std::time::Instant::now() + HOLDER_STARTUP_TIMEOUT;
loop { loop {
if sock.exists() { if sock.exists() {
break; break;
} }
if let Some(status) = child.try_wait().context("wait holder launcher")? {
bail!("holder launcher for session {session} exited before socket was ready: {status}");
}
if std::time::Instant::now() >= deadline { if std::time::Instant::now() >= deadline {
let _ = child.kill(); let _ = child.kill();
let _ = child.wait(); let _ = child.wait();