fix(honcho): bound the shutdown flush by the shutdown deadline
Provider shutdown handed the manager a remaining budget, but flush_all ran first with no deadline and blocked on each session's flush lock. An async upload still in flight held that lock, so shutdown waited the full HTTP timeout past its declared budget. flush_all and the queue drain now take the deadline, skip a session whose lock or budget is gone, and log one warning with the count of messages that stayed unsynced.
This commit is contained in:
@@ -377,14 +377,14 @@ class TestStopAsyncWriterDrain:
|
||||
assert flushed == ["after"]
|
||||
assert mgr._async_queue.empty()
|
||||
|
||||
def test_shutdown_passes_the_join_timeout_through(self, make_manager, monkeypatch):
|
||||
def test_shutdown_gives_the_writer_join_what_the_flush_left_of_the_timeout(self, make_manager, monkeypatch):
|
||||
mgr = make_manager("async")
|
||||
seen = {}
|
||||
monkeypatch.setattr(mgr, "stop_async_writer", lambda timeout=10.0: seen.setdefault("timeout", timeout))
|
||||
|
||||
mgr.shutdown(timeout=2.5)
|
||||
|
||||
assert seen["timeout"] == 2.5
|
||||
assert 2.0 < seen["timeout"] <= 2.5
|
||||
|
||||
|
||||
class TestAsyncWriterRetry:
|
||||
|
||||
@@ -17,7 +17,7 @@ from plugins.memory.honcho.client import (
|
||||
spawn_context_thread,
|
||||
)
|
||||
from plugins.memory.honcho.client_cache import _client_slots, _client_slots_lock
|
||||
from plugins.memory.honcho.session import HonchoSessionManager
|
||||
from plugins.memory.honcho.session import HonchoSession, HonchoSessionManager
|
||||
from plugins.plugin_utils import SingletonSlot
|
||||
|
||||
|
||||
@@ -157,6 +157,31 @@ class TestProviderShutdown:
|
||||
release.set()
|
||||
provider._recall_sync_thread.join(timeout=2)
|
||||
|
||||
def test_shutdown_skips_a_flush_whose_lock_an_upload_holds_past_the_budget(self, async_manager, monkeypatch, caplog):
|
||||
provider = self._provider(async_manager)
|
||||
monkeypatch.setattr(provider, "_shutdown_join_budget", lambda: 0.2)
|
||||
session = HonchoSession("test-session", "user", "assistant", "sid")
|
||||
session.add_message("user", "pending")
|
||||
async_manager._cache["test-session"] = session
|
||||
entered, release = threading.Event(), threading.Event()
|
||||
|
||||
def upload(messages):
|
||||
entered.set()
|
||||
release.wait(timeout=5)
|
||||
|
||||
async_manager._sessions_cache["sid"] = SimpleNamespace(add_messages=upload)
|
||||
async_manager.save(session)
|
||||
try:
|
||||
assert entered.wait(timeout=1)
|
||||
started = time.monotonic()
|
||||
with caplog.at_level(logging.WARNING, logger="plugins.memory.honcho"):
|
||||
provider.shutdown()
|
||||
assert time.monotonic() - started < 1.0
|
||||
assert "1 message(s) in 1 session(s) still unsynced" in caplog.text
|
||||
assert session.messages[0].get("_synced") is None
|
||||
finally:
|
||||
release.set()
|
||||
|
||||
def test_shutdown_does_not_close_the_shared_client(self, async_manager):
|
||||
provider = self._provider(async_manager)
|
||||
provider.shutdown()
|
||||
|
||||
Reference in New Issue
Block a user