Skip to content

Commit 5c6b439

Browse files
committed
fix: close codex sqlite connections after reads
1 parent c6c645f commit 5c6b439

2 files changed

Lines changed: 48 additions & 3 deletions

File tree

codex_loader.py

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import re
88
import sqlite3
99
from collections import OrderedDict
10+
from contextlib import closing
1011
from dataclasses import dataclass
1112
from datetime import UTC, datetime, timedelta
1213
from pathlib import Path
@@ -206,7 +207,7 @@ def _load_sqlite_rate_limits() -> CodexRateLimits | None:
206207
"ORDER BY ts DESC, ts_nanos DESC, id DESC LIMIT 50"
207208
)
208209
try:
209-
with sqlite3.connect(f"file:{LOGS_DB}?mode=ro", uri=True) as conn:
210+
with closing(sqlite3.connect(f"file:{LOGS_DB}?mode=ro", uri=True)) as conn:
210211
rows = conn.execute(query).fetchall()
211212
except (OSError, sqlite3.Error):
212213
if os.environ.get("USAGE_DEBUG") == "1":
@@ -331,7 +332,7 @@ def _load_thread_metadata() -> dict[str, _ThreadMetadata]:
331332
if not STATE_DB.exists():
332333
return {}
333334
try:
334-
with sqlite3.connect(f"file:{STATE_DB}?mode=ro", uri=True) as conn:
335+
with closing(sqlite3.connect(f"file:{STATE_DB}?mode=ro", uri=True)) as conn:
335336
rows = conn.execute(
336337
"SELECT id, model, cwd FROM threads",
337338
).fetchall()
@@ -369,7 +370,7 @@ def _load_sqlite_log_entries(
369370
params = (cutoff_ts,)
370371
query += " ORDER BY ts ASC, ts_nanos ASC, id ASC"
371372
try:
372-
with sqlite3.connect(f"file:{LOGS_DB}?mode=ro", uri=True) as conn:
373+
with closing(sqlite3.connect(f"file:{LOGS_DB}?mode=ro", uri=True)) as conn:
373374
rows = conn.execute(query, params).fetchall()
374375
except (OSError, sqlite3.Error):
375376
if os.environ.get("USAGE_DEBUG") == "1":

tests/test_codex_loader.py

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,50 @@ def test_load_entries_includes_sqlite_logs_when_sessions_dir_is_missing(
318318
assert entries[0].project == "demo"
319319

320320

321+
def test_sqlite_reads_close_connections(
322+
monkeypatch: pytest.MonkeyPatch,
323+
tmp_path: Path,
324+
) -> None:
325+
class _Cursor:
326+
def __init__(self, rows: list[tuple[Any, ...]]) -> None:
327+
self._rows = rows
328+
329+
def fetchall(self) -> list[tuple[Any, ...]]:
330+
return self._rows
331+
332+
class _Connection:
333+
def __init__(self) -> None:
334+
self.closed = False
335+
336+
def execute(self, *_args: Any) -> _Cursor:
337+
return _Cursor([])
338+
339+
def close(self) -> None:
340+
self.closed = True
341+
342+
connections: list[_Connection] = []
343+
344+
def _connect(*_args: Any, **_kwargs: Any) -> _Connection:
345+
conn = _Connection()
346+
connections.append(conn)
347+
return conn
348+
349+
state_db = tmp_path / "state.sqlite"
350+
logs_db = tmp_path / "logs.sqlite"
351+
state_db.touch()
352+
logs_db.touch()
353+
monkeypatch.setattr(codex_loader, "STATE_DB", state_db)
354+
monkeypatch.setattr(codex_loader, "LOGS_DB", logs_db)
355+
monkeypatch.setattr(sqlite3, "connect", _connect)
356+
357+
assert codex_loader._load_thread_metadata() == {}
358+
assert codex_loader._load_sqlite_rate_limits() is None
359+
assert codex_loader._load_sqlite_log_entries({}, None, {}) == []
360+
361+
assert len(connections) == 3
362+
assert all(conn.closed for conn in connections)
363+
364+
321365
def test_load_entries_skips_sqlite_logs_already_covered_by_jsonl(
322366
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
323367
) -> None:

0 commit comments

Comments
 (0)