Skip to content

fix: reuse the DuckDB connection across calls, fix a cache download race - #1

Merged
aaronkanzer merged 1 commit into
mainfrom
fix/reuse-connection-and-cache-race
Aug 29, 2026
Merged

aaronkanzer merged 1 commit into
mainfrom
fix/reuse-connection-and-cache-race

Conversation

@aaronkanzer

Copy link
Copy Markdown
Member

Summary

  • connect() opened a brand new duckdb.connect() on every call from measurements(), chembl_bridge(), dti_pairs() and query(), re-running INSTALL/LOAD httpfs, re-creating the S3 secret, and re-registering all five core views every time. Now reuses a lazily-created, per-release base connection and hands out a cursor() per call (thread-safe, verified). Measured: 8 repeat calls to measurements() went from 318s (39.8s/call) to 5.3s (0.66s/call), ~60x. Same fix already shipped in scigantic-pubchem's bridge.py.
  • cache.py's atomic download used a temp filename derived only from the cache key, so two threads racing to fill the same key raced each other's os.replace() too. Reproduced directly: 11/16 threads failed with FileNotFoundError in every trial before the fix. Fixed with a uuid-suffixed temp name per call, mirroring scigantic-pubchem's cache.py fix for the identical bug.
  • Added concurrency regression tests for both, updated the README with the measured numbers, bumped to 0.2.2.

Test plan

  • pytest -q — 38 passed
  • mypy src/scigantic_bindingdb (strict) — clean
  • Reproduced the pre-fix connection overhead (39.8s/call) and the pre-fix cache race (11/16 threads raising) directly, confirmed both are gone after the fix

🤖 Generated with Claude Code

https://claude.ai/code/session_01L4ztUhMgoMFRicB8uiYx76

connect() opened a brand new duckdb.connect() on every call, including
from inside measurements(), chembl_bridge(), dti_pairs() and query(),
re-running INSTALL/LOAD httpfs, re-creating the S3 secret, and
re-registering all five core views every time. Measured: 8 repeat calls
to measurements() went from 318s (39.8s/call) to 5.3s (0.66s/call, ~60x)
after switching to a cursor() on a shared, lazily-created base connection
per release, the same fix already shipped in scigantic-pubchem's bridge.py
for the same reason.

cache.py's atomic download used a temp filename derived only from the
cache key, so two threads racing to fill the same key raced each other's
os.replace() too; reproduced directly, 11/16 threads failed with
FileNotFoundError in every trial. Fixed with a uuid-suffixed temp name per
call, mirroring scigantic-pubchem's cache.py fix for the identical bug.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L4ztUhMgoMFRicB8uiYx76
@aaronkanzer
aaronkanzer merged commit bc023bb into main Aug 29, 2026
6 checks passed
@aaronkanzer
aaronkanzer deleted the fix/reuse-connection-and-cache-race branch August 29, 2026 14:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant