Console shutdown request reads the reply before closing

send_without_answer closed the socket right after writing "server shutdown".
With the reply still unread, Windows resets the connection, and the world
can drop the command: a world supervised by coa-launcher.exe ignored the
shutdown and the stop timed out after 90 seconds. The reply is now read up
to the next prompt, the end of the connection or 10 seconds.

The E2E also checks which supervisor runs the world and that manage.py
stops a world the Manager started.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
ДмитрийandClaude Opus 5.5 committed 2026-11-10 09:38:59 +05:00
1 parent a822bfc99b
commit a9fb982ab0
2 files changed
+27 -2

No files matched your search

@@ -85,10 +85,21 @@ fn main() {
assert_eq!(std::fs::read(&manage_py).unwrap(), original, "manage.py is not rewritten");
assert_eq!(realm_name(root), "AzerothCore", "no profile: the launcher's own realm name");
let shipped = root.join("Scripts/coa-launcher.exe");
let supervisor = state::running(root, "supervisor").expect("a supervisor");
println!("[supervisor] {}", supervisor.exe);
let expected = if shipped.is_file() { "coa-launcher.exe" } else { "python.exe" };
assert!(supervisor.exe.to_lowercase().ends_with(expected), "the world is supervised by {expected}");
// 2
assert!(drive(root, Verb::StopAll).ok, "stop-all");
assert!(nothing_running(root), "1-2 left something running");
// 2b: the repack's Python launcher stops a world the Manager started
assert!(drive(root, Verb::StartAll).ok);
assert!(manage(root, "stop-all").0, "manage.py stops what the Manager started");
assert!(nothing_running(root), "2b left something running");
// 3
assert!(manage(root, "start-all").0);
assert!(drive(root, Verb::StopAll).ok, "driver stops what manage.py started");
+16 -2
View File
@@ -88,12 +88,26 @@ impl Ra {
self.command(c)
}
/// Send a command the server does not answer before it closes the console (`server shutdown`), and return at
/// once.
/// Send a command whose answer does not matter (`server shutdown`). The reply is still read (up to the next
/// prompt, the end of the connection, or a few seconds): closing a socket that holds unread data resets the
/// connection, and the server can then drop the command it has not processed yet.
pub fn send_without_answer(mut self, cmd: &str) -> Result<()> {
let c = crate::console::check_command(cmd)?;
self.stream.write_all(format!("{c}\r\n").as_bytes())?;
self.stream.flush()?;
self.stream.set_read_timeout(Some(Duration::from_secs(10)))?;
let mut seen = Vec::new();
let mut buf = [0u8; 4096];
while let Ok(n) = self.stream.read(&mut buf) {
if n == 0 {
break;
}
seen.extend_from_slice(&buf[..n]);
if seen.ends_with(b"AC>") || seen.len() > 1 << 20 {
break;
}
}
let _ = self.stream.shutdown(std::net::Shutdown::Both);
Ok(())
}