From ac68a6411a3d048a91c08d4b33cf852f4e93b7e1 Mon Sep 17 00:00:00 2001 From: kshitijk4poor <82637225+kshitijk4poor@users.noreply.github.com> Date: Sun, 5 Jul 2026 11:27:55 +0530 Subject: [PATCH] fix(gateway): drain async log queue on os._exit shutdown backstop MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The QueueListener change routes rotating file handlers through an in-memory queue drained on a dedicated thread, with an atexit hook to flush on shutdown. But _exit_after_graceful_shutdown() uses os._exit, which bypasses atexit — so on the early-exit and #53107 hard-exit paths the queued records (including the shutdown reason) were silently lost. Explicitly flush_log_queue() before os._exit, and correct the now-stale comment that claimed handlers are synchronous with nothing pending. --- gateway/run.py | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/gateway/run.py b/gateway/run.py index ade178629f1..dc1b0a525db 100644 --- a/gateway/run.py +++ b/gateway/run.py @@ -20239,16 +20239,28 @@ def _exit_after_graceful_shutdown(exit_code: int) -> None: released — so this is a no-op on the normal shutdown path and the actual cleanup on the early-exit paths. - Logging is not flushed here: the gateway's handlers are synchronous - ``RotatingFileHandler``s that write each record immediately (no - ``MemoryHandler``/``QueueHandler`` buffering), so there is nothing pending. - Only stdio is buffered, so only stdio is flushed. + Logging IS flushed here: the rotating file handlers are driven by an + async ``QueueListener`` on a dedicated thread (see + ``hermes_logging._register_queued_handler``), so records emitted right + before shutdown may still be sitting in the in-memory queue. ``os._exit`` + below bypasses ``atexit``, so the ``atexit``-registered listener drain + never runs on this path — we must drain explicitly here or lose the last + log lines (including the shutdown reason on the early-exit paths). Stdio + is flushed too. """ for stream in (sys.stdout, sys.stderr): try: stream.flush() except Exception: pass + # Drain the async log queue: os._exit bypasses atexit, so the listener's + # atexit drain won't fire. flush_log_queue() no-ops when logging never + # initialized a queue (e.g. very early aborts), so this is always safe. + try: + from hermes_logging import flush_log_queue + flush_log_queue() + except Exception: + pass # Guaranteed cleanup chokepoint: os._exit skips atexit, and the early # SystemExit exit paths never run _stop_impl, so release here (idempotent). try: