fix: Close Directory and Delete Cache reliability (#27, #34) #112

Merged
ValleyGeek merged 4 commits from 0.1.37/issue-27-34-close-directory into main 2026-06-20 07:41:26 +00:00
Member

Summary

  • #27 — "Close Directory and Delete Cache" reported the cache file as still in use on Windows. Root cause: every WorkingDB/GlobalConfigDB read or write opened a SQLite connection via a bare with self._connect() as conn:, which only commits/rolls back the transaction — it never closes the connection. A local repro confirmed this leaks file descriptors even after an explicit gc.collect() (200 connections → ~100 leaked fds), explaining a Windows handle64.exe dump showing dozens of simultaneous open handles to the working DB and global config DB. Every call site now routes through the existing properly-closing transaction() helper in both DB classes. Kept the earlier WAL-checkpoint + Windows-mmap-disable mitigation as defense in depth.
  • #34 — Closing a directory via "Close Directory and Delete Cache" didn't clear it from the recently-opened list, so it kept getting auto-reopened on next launch when "open last directory on startup" is enabled. A _remove_from_recent() helper already existed but was dead code; the close handler now calls it.

Test plan

  • ruff check / ruff format --check
  • Full pytest suite (372 passed, 1 skipped)
  • New regression tests that fail against the old unclosed-connection pattern and pass against the fix (test_repeated_reads_do_not_leak_connection_handles, test_repeated_pref_reads_do_not_leak_connection_handles)
  • New test for #34 (test_close_directory_and_delete_cache_clears_last_opened)
  • ./scripts/smoke-ui-exit.sh — no Qt shutdown errors
  • ./scripts/check-version-bump.sh — VERSION bumped 0.1.37 → 0.1.38
  • Confirmed by the reporter against a real Windows build

Closes #27, #34.

## Summary - **#27** — "Close Directory and Delete Cache" reported the cache file as still in use on Windows. Root cause: every `WorkingDB`/`GlobalConfigDB` read or write opened a SQLite connection via a bare `with self._connect() as conn:`, which only commits/rolls back the transaction — it never closes the connection. A local repro confirmed this leaks file descriptors even after an explicit `gc.collect()` (200 connections → ~100 leaked fds), explaining a Windows `handle64.exe` dump showing dozens of simultaneous open handles to the working DB and global config DB. Every call site now routes through the existing properly-closing `transaction()` helper in both DB classes. Kept the earlier WAL-checkpoint + Windows-mmap-disable mitigation as defense in depth. - **#34** — Closing a directory via "Close Directory and Delete Cache" didn't clear it from the recently-opened list, so it kept getting auto-reopened on next launch when "open last directory on startup" is enabled. A `_remove_from_recent()` helper already existed but was dead code; the close handler now calls it. ## Test plan - [x] `ruff check` / `ruff format --check` - [x] Full `pytest` suite (372 passed, 1 skipped) - [x] New regression tests that fail against the old unclosed-connection pattern and pass against the fix (`test_repeated_reads_do_not_leak_connection_handles`, `test_repeated_pref_reads_do_not_leak_connection_handles`) - [x] New test for #34 (`test_close_directory_and_delete_cache_clears_last_opened`) - [x] `./scripts/smoke-ui-exit.sh` — no Qt shutdown errors - [x] `./scripts/check-version-bump.sh` — VERSION bumped 0.1.37 → 0.1.38 - [x] Confirmed by the reporter against a real Windows build Closes #27, #34.
Skip mmap'ing the local working DB on Windows (same reasoning the
share-side pragmas already use) and run a best-effort WAL checkpoint +
journal-mode reset right before "Close Directory and Delete Cache"
attempts to unlink the file, so the cache deletion isn't left racing a
lingering memory-mapped file handle.
fix(ui): clear last-opened directory on Close Directory and Delete Cache (fixes #34)
All checks were successful
CI / lint (push) Successful in 8s
CI / test (push) Successful in 1m46s
CI / appimage (push) Successful in 8m18s
CI / build-appimage (push) Successful in 0s
CI / windows-exe (push) Successful in 12m29s
CI / build-windows-exe (push) Successful in 0s
955552c27c
_remove_from_recent() existed but was never called from the close
handler, so the GlobalConfigDB recent_dirs entry (and therefore the
auto-open-last-directory feature) kept pointing at a workspace whose
cache had just been deleted.
fix(db): explicitly close every short-lived connection (fixes #27)
All checks were successful
CI / lint (push) Successful in 9s
CI / test (push) Successful in 1m54s
CI / windows-exe (push) Successful in 7m52s
CI / build-windows-exe (push) Successful in 0s
CI / appimage (push) Successful in 8m23s
CI / build-appimage (push) Successful in 0s
cc4b1d7d05
sqlite3.Connection's own __enter__/__exit__ only manage the transaction
(commit/rollback) — they never close the connection. Every WorkingDB and
GlobalConfigDB read/write opened a fresh connection via a bare
"with self._connect() as conn:" and relied on CPython refcounting to
release it, which a local repro confirmed does *not* happen reliably:
200 short-lived connections opened that way leaked ~100 file descriptors
even after an explicit gc.collect(), while the same loop using explicit
conn.close() stayed flat. On Windows this leaves dozens of simultaneous
handles to the working DB (and its -wal/-shm companions) and to the
global config.db open for the life of the session — confirmed against a
handle64.exe dump from a real repro — which is what made "Close Directory
and Delete Cache" keep reporting the cache file as still in use well past
the existing 10s retry window.

Both classes already had a properly-closing transaction() context
manager; every bare _connect() call site now routes through it instead.
The earlier WAL-checkpoint/mmap-on-Windows mitigation (previous commit)
stays in place as defense in depth, but this is the actual root cause.

Threading notes: no behavior change for worker threads — each call still
opens and now properly closes its own short-lived connection, consistent
with the existing "connections are short-lived, never shared" contract.
docs: PR-prep for issues 27 and 34 - bump to 0.1.38
All checks were successful
CI / lint (push) Successful in 8s
CI / test (push) Successful in 1m54s
CI / appimage (push) Successful in 8m29s
CI / build-appimage (push) Successful in 0s
CI / windows-exe (push) Successful in 8m58s
CI / build-windows-exe (push) Successful in 0s
bc85674c12
ValleyGeek deleted branch 0.1.37/issue-27-34-close-directory 2026-06-20 07:41:26 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
ai-collab/bulk-image-organizer!112
No description provided.