Context
Discovered by @powerzist during work on #162 (Windows compatibility). Filing here per their request so the underlying handle leak gets tracked separately from the PR-level scope split.
Symptom
On Windows, several tests fail with FileExistsError when trying to create directories under tmp_path:
self = WindowsPath('C:/Users/user/AppData/Local/Temp/pytest-of-user/pytest-30/test_happy_path_indexes_codex_0/memories')
mode = 511, parents = False, exist_ok = False
def mkdir(self, mode=0o777, parents=False, exist_ok=False):
try:
> os.mkdir(self, mode)
E FileExistsError: [WinError 183] 파일이 이미 있으므로 만들 수 없습니다:
'C:\\Users\\user\\AppData\\Local\\Temp\\pytest-of-user\\pytest-30\\
test_happy_path_indexes_codex_0\\memories'
Reproduced at packages/memtomem/tests/test_cli_ingest.py:551 and :581.
Environment from the report:
- Windows 10/11
cpython-3.13.5-windows-x86_64 (uv-managed)
Powerzist's observation
The temporary directory system creates up to three directories, then starts reusing them. When reusing a directory, it attempts to delete the existing contents before creating it again. However, on Windows, some files inside the directory may still be locked, which can cause the deletion step to fail. As a result, the directory remains in place, and a FileExistsError is raised when attempting to recreate it.
In my environment, I consistently observed that only up to three directories were present under the temporary path, which appears to align with this reuse behavior.
This matches pytest's default tmp_path_retention_count=3 — pytest reuses tmp directory roots across sessions, and if the previous session's rmtree cleanup fails, the next run sees the leftover. On Linux/macOS rmtree generally succeeds even while files are still open; Windows blocks on any open handle.
Hypothesis (root cause upstream of mkdir)
The components fixture (packages/memtomem/tests/conftest.py:41-71) tears down via close_components(), which only closes storage, embedder, search_pipeline, and llm. If any of those retain an OS-level handle after await …close() returns, pytest's subsequent cleanup of the tmp dir fails on Windows, and the leftover memories/ directory is the downstream symptom.
Likely culprits to investigate first:
- SQLite WAL/SHM sidecars —
test.db-wal / test.db-shm may still be mapped after the connection closes if WAL checkpoint hasn't run.
- ONNX Runtime mmap of the model file — the embedder may keep an mmap handle on the ONNX session that isn't released by
await embedder.close().
A blanket exist_ok=True at the call sites (the path #162 originally took) suppresses the symptom but hides the leak — and the leftover directory may carry a stale test.db / test.db-wal from the previous run, which could cause quieter flakes later that are much harder to bisect.
Suggested investigation path
- Verify on Windows: after
close_components() returns, are there still open handles on storage._db, embedder.session, etc.? (Windows handle.exe from Sysinternals works for this.)
- Audit
Storage.close(), Embedder.close(), LLMProvider.close() for missing finalization (WAL checkpoint, ONNX session.release, file handle close).
- If a third-party library holds a C-level lock that can't be released, fall back to site-targeted
exist_ok=True on only the demonstrated paths (conftest.py:48, test_cli_ingest.py:551, test_cli_ingest.py:581) with a comment pointing at the upstream cause — not a blanket apply across sites that don't hold handles.
Acceptance
- Tests under the
components fixture run cleanly on Windows across consecutive sessions (no FileExistsError from leftover memories/ dirs).
- If a fix is applied at the source (
close_components() or one of the .close() methods), no exist_ok=True is needed at call sites.
- If a site-targeted fallback is used, the comment at each site explains why (handle leak in component X) and not just what (allow reuse).
Related
Credit: investigation and traceback by @powerzist.
Context
Discovered by @powerzist during work on #162 (Windows compatibility). Filing here per their request so the underlying handle leak gets tracked separately from the PR-level scope split.
Symptom
On Windows, several tests fail with
FileExistsErrorwhen trying to create directories undertmp_path:Reproduced at
packages/memtomem/tests/test_cli_ingest.py:551and:581.Environment from the report:
cpython-3.13.5-windows-x86_64(uv-managed)Powerzist's observation
This matches pytest's default
tmp_path_retention_count=3— pytest reuses tmp directory roots across sessions, and if the previous session'srmtreecleanup fails, the next run sees the leftover. On Linux/macOSrmtreegenerally succeeds even while files are still open; Windows blocks on any open handle.Hypothesis (root cause upstream of
mkdir)The
componentsfixture (packages/memtomem/tests/conftest.py:41-71) tears down viaclose_components(), which only closesstorage,embedder,search_pipeline, andllm. If any of those retain an OS-level handle afterawait …close()returns, pytest's subsequent cleanup of the tmp dir fails on Windows, and the leftovermemories/directory is the downstream symptom.Likely culprits to investigate first:
test.db-wal/test.db-shmmay still be mapped after the connection closes if WAL checkpoint hasn't run.await embedder.close().A blanket
exist_ok=Trueat the call sites (the path #162 originally took) suppresses the symptom but hides the leak — and the leftover directory may carry a staletest.db/test.db-walfrom the previous run, which could cause quieter flakes later that are much harder to bisect.Suggested investigation path
close_components()returns, are there still open handles onstorage._db,embedder.session, etc.? (Windowshandle.exefrom Sysinternals works for this.)Storage.close(),Embedder.close(),LLMProvider.close()for missing finalization (WAL checkpoint, ONNX session.release, file handle close).exist_ok=Trueon only the demonstrated paths (conftest.py:48,test_cli_ingest.py:551,test_cli_ingest.py:581) with a comment pointing at the upstream cause — not a blanket apply across sites that don't hold handles.Acceptance
componentsfixture run cleanly on Windows across consecutive sessions (noFileExistsErrorfrom leftovermemories/dirs).close_components()or one of the.close()methods), noexist_ok=Trueis needed at call sites.Related
encoding="utf-8"only after this issue was filed.Credit: investigation and traceback by @powerzist.