fix(cache): close the connection the schema init opened

`with sqlite3.Connection` ends the transaction but keeps the file handle open,
so every cache class leaked one handle per instance until the garbage collector
happened to run. On Windows that makes the database file undeletable while the
process lives (WinError 32), which broke 30 cache tests in teardown and would
also block moving or clearing a cache at runtime.

All eight `with <connection>` sites now go through `closing_connection()`, and a
regression test asserts the schema init leaves no usable connection behind.
This commit is contained in:
Will Miao
2026-10-12 11:35:14 +08:00
parent 5fbac84910
commit fd40cd952b
7 changed files with 61 additions and 11 deletions
+18 -1
View File
@@ -14,7 +14,8 @@ the write path in WAL so readers are never blocked by a writer.
from __future__ import annotations
import sqlite3
from typing import Any
from contextlib import contextmanager
from typing import Any, Iterator
# How long a connection waits for a competing writer before raising.
CONCURRENT_TIMEOUT_SECONDS = 30.0
@@ -79,3 +80,19 @@ def connect_cache_db(
pass
return conn
@contextmanager
def closing_connection(conn: sqlite3.Connection) -> Iterator[sqlite3.Connection]:
"""Yield *conn* and close it on exit.
``with sqlite3.Connection`` only ends the transaction: it leaves the file
handle open, and a live handle keeps the cache database undeletable on
Windows (``WinError 32``) until the garbage collector happens to run. Every
``with`` over a cache connection goes through here instead.
"""
try:
yield conn
finally:
conn.close()