fix(#5543): fire-and-forget commit_session_memory + drain-safe managed stop

Move the 1-5s commit_session_memory() extraction off the POST /api/session/new
request thread (was blocking + New Chat) into a fire-and-forget daemon thread,
guarded by the existing per-session in-flight serialization.

Data-loss prevention on managed stop:
- server.py installs a SIGTERM handler that requests an orderly stop so
  serve_forever()'s finally block runs drain_all_on_shutdown() (the default
  SIGTERM disposition terminated without unwinding, losing in-flight commits).
- session_lifecycle: bounded self-unregistering background-commit registry;
  a _draining flag so no NEW worker starts once the drain begins (the inline
  generation-drain commits any late arrival instead); a 30s overall drain
  deadline so a stuck worker cannot hang the stop path forever.

Co-authored-by: luperrypf <luperrypf@users.noreply.github.com>
This commit is contained in:
nesquena-hermesandluperrypf committed 2026-07-04 21:40:21 +00:00
1 parent 04717ad305
commit 19d00e3586
4 files changed
+539 -17

No files matched your search

+30
View File
@@ -688,6 +688,36 @@ def main() -> None:
print(f' Remote access: ssh -N -L {PORT}:127.0.0.1:{PORT} <user>@<your-server>', flush=True)
print(f' Then open: {scheme}://localhost:{PORT}', flush=True)
print('', flush=True)
# ctl.sh stops the WebUI with SIGTERM. Python's default SIGTERM handler
# terminates the process WITHOUT unwinding the try/finally around
# serve_forever(), so drain_all_on_shutdown() (which flushes in-flight
# fire-and-forget memory commits) would never run on the normal managed
# stop. Install a handler that requests an orderly shutdown so
# serve_forever() returns and the existing `finally` block drains cleanly.
#
# httpd.shutdown() blocks until serve_forever() has exited and MUST NOT be
# called from the thread running serve_forever() (it would deadlock), so we
# dispatch it from a short-lived helper thread. The handler is idempotent
# and guards against double-shutdown (e.g. repeated SIGTERM/SIGINT).
_shutdown_requested = threading.Event()
def _request_shutdown(signum, _frame):
if _shutdown_requested.is_set():
return
_shutdown_requested.set()
threading.Thread(
target=httpd.shutdown,
name="webui-sigterm-shutdown",
daemon=True,
).start()
try:
signal.signal(signal.SIGTERM, _request_shutdown)
except (ValueError, OSError):
# Not on the main thread (e.g. embedded/test harness); skip handler.
logger.debug("Could not install SIGTERM handler", exc_info=True)
try:
httpd.serve_forever()
finally: