mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-10 13:31:38 +00:00
fix(feishu): send WebSocket CLOSE frame on disconnect (#10202)
Feishu adapter's disconnect() cancelled WSS-thread tasks but never called the lark_oapi client's _disconnect() coroutine, so no WebSocket CLOSE frame was sent. Feishu's server kept routing messages to the stale endpoint for minutes (CLOSE-WAIT timeout), silencing the channel across every shutdown path — systemd restart, hermes update, hermes gateway restart, and the --replace takeover during 'hermes dashboard' invocations. Schedule ws_client._disconnect() on the WSS thread loop via run_coroutine_threadsafe with a 5s timeout before the existing task-cancel + loop-stop sequence. Defensive hasattr guard + broad except keeps disconnect() resilient if lark_oapi's internals shift. Fixes #10202
This commit is contained in:
parent
a6079dd350
commit
77700a0ec0
2 changed files with 111 additions and 1 deletions
|
|
@ -1756,10 +1756,49 @@ class FeishuAdapter(BasePlatformAdapter):
|
|||
await self._cancel_pending_tasks(self._pending_text_batch_tasks)
|
||||
await self._cancel_pending_tasks(self._pending_media_batch_tasks)
|
||||
self._reset_batch_buffers()
|
||||
|
||||
# Send a WebSocket CLOSE frame to Feishu BEFORE tearing down the
|
||||
# thread loop. Without this, Feishu's server never learns the
|
||||
# connection is dead and continues routing messages to the stale
|
||||
# endpoint — the channel goes silent until the server-side
|
||||
# CLOSE-WAIT expires (minutes to hours). See issue #10202.
|
||||
#
|
||||
# ``_disable_websocket_auto_reconnect()`` nils ``self._ws_client``,
|
||||
# so capture the client reference first.
|
||||
ws_client = self._ws_client
|
||||
ws_thread_loop = self._ws_thread_loop
|
||||
self._disable_websocket_auto_reconnect()
|
||||
await self._stop_webhook_server()
|
||||
|
||||
ws_thread_loop = self._ws_thread_loop
|
||||
if (
|
||||
ws_client is not None
|
||||
and ws_thread_loop is not None
|
||||
and not ws_thread_loop.is_closed()
|
||||
and hasattr(ws_client, "_disconnect")
|
||||
):
|
||||
try:
|
||||
future = asyncio.run_coroutine_threadsafe(
|
||||
ws_client._disconnect(), ws_thread_loop
|
||||
)
|
||||
# 5s is generous — the CLOSE frame is a single WebSocket
|
||||
# control frame. If it takes longer than that the
|
||||
# connection is already wedged and we gain nothing by
|
||||
# waiting further.
|
||||
await asyncio.wait_for(asyncio.wrap_future(future), timeout=5.0)
|
||||
logger.debug("[Feishu] Sent WebSocket CLOSE frame to Feishu")
|
||||
except asyncio.TimeoutError:
|
||||
logger.warning(
|
||||
"[Feishu] CLOSE frame not acknowledged within 5s — "
|
||||
"Feishu may briefly route messages to the stale "
|
||||
"connection until server-side timeout"
|
||||
)
|
||||
except Exception as exc:
|
||||
logger.debug(
|
||||
"[Feishu] Could not send WebSocket CLOSE frame: %s",
|
||||
exc,
|
||||
exc_info=True,
|
||||
)
|
||||
|
||||
if ws_thread_loop is not None and not ws_thread_loop.is_closed():
|
||||
logger.debug("[Feishu] Cancelling websocket thread tasks and stopping loop")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue