From 6f875274b01d3093088824eec419753426a1f406 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 4 Oct 2026 09:14:38 +0000 Subject: [PATCH] Torture harness: dump all stacks on the first hang; SQLite 3.53 test fix test_new_memory_database_is_not_scanned_at_registration filled the memory database with VACUUM INTO, which SQLite 3.53 (the free-threaded 3.14 build) refuses for an existing in-memory database; it now uses a plain connection. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01EwkLo2rJWidpkDVT3HkvNv --- tests/connection_torture.py | 10 ++++++++++ tests/test_connection_review_fixes.py | 8 +++++--- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/connection_torture.py b/tests/connection_torture.py index 1c450cd5..c377d66c 100644 --- a/tests/connection_torture.py +++ b/tests/connection_torture.py @@ -362,6 +362,15 @@ class Torture: with self.lock: self.problems.append({"kind": kind, "message": message[:2000]}) + def dump_stacks_once(self, why): + """Every thread's stack on stderr, the first time something hangs.""" + with self.lock: + if getattr(self, "_dumped", False): + return + self._dumped = True + sys.stderr.write(f"\n=== {why}: stacks of all threads ===\n") + faulthandler.dump_traceback(all_threads=True) + def count(self, key): with self.lock: self.counts[key] += 1 @@ -414,6 +423,7 @@ class Torture: value = await asyncio.wait_for(awaitable, timeout) except asyncio.TimeoutError: self.problem("hang", f"{op} on {group} did not finish in {timeout}s") + self.dump_stacks_once(f"{op} on {group} hung") return False, None except asyncio.CancelledError: raise diff --git a/tests/test_connection_review_fixes.py b/tests/test_connection_review_fixes.py index 39153368..e6eb2ff0 100644 --- a/tests/test_connection_review_fixes.py +++ b/tests/test_connection_review_fixes.py @@ -619,10 +619,12 @@ async def test_new_memory_database_is_not_scanned_at_registration(): state = memory._watch_state assert state.scan_future is None assert state in watcher._pending - # Fill it the way the plugin does - conn = sqlite3.connect(":memory:") + # Fill it from a connection of its own, as the plugin does (it uses + # VACUUM INTO, which newer SQLite versions refuse for an existing + # in-memory database, so a plain connection here) + conn = sqlite3.connect("file:review_fixes_copy?mode=memory&cache=shared", uri=True) conn.execute("create table copied (id integer)") - conn.execute("VACUUM INTO 'file:review_fixes_copy?mode=memory&cache=shared'") + conn.commit() conn.close() # The next request scans it response = await ds.client.get("/copy.json")