Server: the game thread is the keep-alive timeout's only judge; kick, then close a second later

The writer thread's own check raced it and logged "Disconnected" instead of
vanilla's "Timed out".

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
Ludovic MarieandClaude Opus 5.5 committed 2026-10-30 07:30:07 +01:00
1 parent 5fb90b440c
commit c3601b74f3
2 files changed
+18 -21

No files matched your search

+9 -15
View File
@@ -1337,22 +1337,16 @@ fn write_loop(
Err(RecvTimeoutError::Timeout) => {}
}
let now = Instant::now();
if let Some((id, at)) = sent_at {
if alive.load(Ordering::Relaxed) == id {
let ms = i32::try_from(at.elapsed().as_millis()).unwrap_or(i32::MAX);
let old = latency.load(Ordering::Relaxed);
latency.store((old * 3 + ms) / 4, Ordering::Relaxed);
sent_at = None;
} else if at.elapsed() >= KEEP_ALIVE_EVERY {
let _ = writer.send(
&play::Clientbound::Disconnect(cfg::Disconnect {
reason: translatable("disconnect.timeout", &[]),
})
.encode(),
);
break;
}
if let Some((id, at)) = sent_at
&& alive.load(Ordering::Relaxed) == id
{
let ms = i32::try_from(at.elapsed().as_millis()).unwrap_or(i32::MAX);
let old = latency.load(Ordering::Relaxed);
latency.store((old * 3 + ms) / 4, Ordering::Relaxed);
sent_at = None;
}
// The game thread times a client out ([`PlayerSender::timed_out`]):
// this thread may be blocked on a client that stopped reading.
if sent_at.is_none() && now >= next {
let id = i64::try_from(start.elapsed().as_millis()).unwrap_or(0);
if writer
+9 -6
View File
@@ -1192,10 +1192,16 @@ impl Hub {
}
}
}
// keepConnectionAlive's timeout, checked once a second here as well:
// a client that stops reading blocks its writer thread, which sends
// the keep-alives and checks them, and would hold its slot forever.
// keepConnectionAlive's timeout, checked once a second here, not on
// the writer thread: a client that stops reading blocks that thread
// and would hold its slot forever. Kicked, it gets the reason; still
// here a second later, its connection is closed under the writer.
if self.ticks.is_multiple_of(20) {
for g in &self.guests {
if !g.left_reason.is_empty() && g.sender.timed_out() {
g.sender.close();
}
}
let silent: Vec<u32> = self
.guests
.iter()
@@ -1204,9 +1210,6 @@ impl Hub {
.collect();
for conn in silent {
self.kick(conn, translatable("disconnect.timeout", &[]));
if let Some(g) = self.guest(conn) {
g.sender.close();
}
}
}
events