Fix heartbeat latency race between keepalive and event loop threads

Set _last_send on the event loop thread before sending so ack() can't
read a stale value from the previous heartbeat cycle.
This commit is contained in:
Oliver Ni
2026-07-08 16:13:06 -04:00
committed by GitHub
parent f7868eaf23
commit f9c66a6a38
+5 -4
View File
@@ -173,8 +173,7 @@ class KeepAliveHandler(threading.Thread):
data = self.get_payload() data = self.get_payload()
_log.debug(self.msg, self.shard_id, data['d']) _log.debug(self.msg, self.shard_id, data['d'])
coro = self.ws.send_heartbeat(data) f = asyncio.run_coroutine_threadsafe(self._send_heartbeat(data), loop=self.ws.loop)
f = asyncio.run_coroutine_threadsafe(coro, loop=self.ws.loop)
try: try:
# block until sending is complete # block until sending is complete
total = 0 total = 0
@@ -195,8 +194,6 @@ class KeepAliveHandler(threading.Thread):
except Exception: except Exception:
self.stop() self.stop()
else:
self._last_send = time.perf_counter()
def get_payload(self) -> Dict[str, Any]: def get_payload(self) -> Dict[str, Any]:
return { return {
@@ -214,6 +211,10 @@ class KeepAliveHandler(threading.Thread):
self._last_send = time.perf_counter() self._last_send = time.perf_counter()
return self.get_payload() return self.get_payload()
async def _send_heartbeat(self, data: Any) -> None:
self._last_send = time.perf_counter()
await self.ws.send_heartbeat(data)
def ack(self) -> None: def ack(self) -> None:
ack_time = time.perf_counter() ack_time = time.perf_counter()
self._last_ack = ack_time self._last_ack = ack_time