Fix frozen client on ctrl+q: send bye instead of half-closing
Dropping the client's channel sender never closed the socket (the server's reader thread keeps the connection open), so the client froze waiting for frames. The server now sends a bye message; the client exits cleanly on it and its socket close lets the server clean up. Messages from already-disconnected clients are ignored. Verified end-to-end with the real client in a PTY (exits 0, daemon survives). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -100,6 +100,8 @@ fn client_loop(
|
|||||||
}
|
}
|
||||||
stdout.flush()?;
|
stdout.flush()?;
|
||||||
}
|
}
|
||||||
|
// Servern har kopplat ner oss (t.ex. ctrl+q) — avsluta snyggt.
|
||||||
|
Ok(ServerMessage::Bye) => return Ok(()),
|
||||||
Ok(_) => {}
|
Ok(_) => {}
|
||||||
Err(mpsc::TryRecvError::Empty) => break,
|
Err(mpsc::TryRecvError::Empty) => break,
|
||||||
Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
|
Err(mpsc::TryRecvError::Disconnected) => return Ok(()),
|
||||||
|
|||||||
@@ -111,6 +111,10 @@ pub enum ClientRole {
|
|||||||
#[serde(tag = "type", rename_all = "snake_case")]
|
#[serde(tag = "type", rename_all = "snake_case")]
|
||||||
pub enum ServerMessage {
|
pub enum ServerMessage {
|
||||||
HelloOk { version: String, socket_path: String },
|
HelloOk { version: String, socket_path: String },
|
||||||
|
/// Servern kopplar ner klienten kontrollerat (t.ex. efter ctrl+q
|
||||||
|
/// från klienten). Klienten ska återställa terminalen och avsluta —
|
||||||
|
/// det är klientens socket-stängning som låter servern städa upp.
|
||||||
|
Bye,
|
||||||
Frame {
|
Frame {
|
||||||
width: u16,
|
width: u16,
|
||||||
height: u16,
|
height: u16,
|
||||||
|
|||||||
12
src/main.rs
12
src/main.rs
@@ -438,6 +438,11 @@ fn handle_ipc_event(
|
|||||||
all_clients.remove(&client_id);
|
all_clients.remove(&client_id);
|
||||||
}
|
}
|
||||||
IpcEvent::Message { client_id, msg } => {
|
IpcEvent::Message { client_id, msg } => {
|
||||||
|
// Ignorera meddelanden från klienter som redan kopplats ner
|
||||||
|
// (t.ex. efter bye) men vars socket ännu inte hunnit stängas.
|
||||||
|
if !all_clients.contains_key(&client_id) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
match msg {
|
match msg {
|
||||||
ClientMessage::Input { data } => {
|
ClientMessage::Input { data } => {
|
||||||
let bytes = ipc::from_hex(&data);
|
let bytes = ipc::from_hex(&data);
|
||||||
@@ -540,7 +545,12 @@ fn disconnect_instead_of_quit(
|
|||||||
) {
|
) {
|
||||||
if app.should_quit {
|
if app.should_quit {
|
||||||
app.should_quit = false;
|
app.should_quit = false;
|
||||||
// Sändaren droppas → klientens ström stängs → klienten avslutar.
|
// Skicka bye så klienten avslutar och stänger socketen — att bara
|
||||||
|
// droppa sändaren räcker inte, eftersom serverns läsartråd håller
|
||||||
|
// anslutningen öppen och klienten annars fryser i väntan på frames.
|
||||||
|
if let Some((_, _, _, tx)) = all_clients.get(&client_id) {
|
||||||
|
let _ = tx.try_send(ServerMessage::Bye);
|
||||||
|
}
|
||||||
all_clients.remove(&client_id);
|
all_clients.remove(&client_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -148,13 +148,15 @@ def main():
|
|||||||
disp2.recv_until("frame")
|
disp2.recv_until("frame")
|
||||||
disp2.send(key_event(ch="q", modifiers="CONTROL"))
|
disp2.send(key_event(ch="q", modifiers="CONTROL"))
|
||||||
try:
|
try:
|
||||||
|
got_bye = False
|
||||||
deadline = time.time() + 3
|
deadline = time.time() + 3
|
||||||
while time.time() < deadline:
|
while time.time() < deadline:
|
||||||
disp2.recv()
|
if disp2.recv().get("type") == "bye":
|
||||||
disconnected = False
|
got_bye = True
|
||||||
|
break
|
||||||
except (EOFError, TimeoutError, OSError):
|
except (EOFError, TimeoutError, OSError):
|
||||||
disconnected = True
|
pass # EOF duger också som nedkoppling
|
||||||
check("ctrl+q från klient kopplar ner klienten", disconnected)
|
check("ctrl+q från klient ger bye till klienten", got_bye)
|
||||||
frame = disp.recv_until("frame")
|
frame = disp.recv_until("frame")
|
||||||
check("daemonen överlever klientens exit", frame.get("width", 0) > 0)
|
check("daemonen överlever klientens exit", frame.get("width", 0) > 0)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user