Skip to content

Close database connections on every entrypoint - #879

Merged
lewisjared merged 3 commits into
mainfrom
fix/database-connection-lifecycle
Aug 14, 2026
Merged

Close database connections on every entrypoint#879
lewisjared merged 3 commits into
mainfrom
fix/database-connection-lifecycle

Conversation

@lewisjared

@lewisjared lewisjared commented Aug 14, 2026

Copy link
Copy Markdown
Contributor

Audits how every entrypoint manages its database session and connection, then fixes the four problems that turned up. The CLI was already correct, and Database.session_scope holds up under concurrent threads, so neither changes here.

Celery workers opened a Database per task and never closed it. On SQLite the garbage collector eventually mopped up, so open files plateaued around ten. On Postgres it means a fresh connection for every result a worker handles. Workers now open one database per process and reuse it across tasks, keyed on the pid so a forked child opens its own rather than using connections it inherited. A worker_process_shutdown hook closes it. Measured over 100 tasks, open files drop from 10 to 1.

The three executors open a database of their own when the caller does not supply one, and never closed it. That path is not used in production, where the CLI always passes its database in, but it leaks for anyone constructing an executor by hand. Each now closes what it opened. The standalone alembic environment gets the same treatment.

LocalExecutor.join promised that outstanding executions are always marked failed-retryable before it returns or raises. Only the timeout path did this, so an ingestion failure mid-loop left the remaining rows with successful=None until the stale-execution reaper caught them six hours later. The promise now holds for every failure, and the pool is shut down on the way out.

Verified with three live experiments rather than by reading alone: file-descriptor counts across 100 simulated tasks, eight threads hammering session_scope on SQLite (0 errors), and a spy on Database.close confirming the CLI closes on success, on typer.Exit, and on an unexpected exception.

Adds three tests. packages/climate-ref and packages/climate-ref-celery pass in full at 1735 tests.

Summary by CodeRabbit

  • Bug Fixes

    • Failed in-flight executions are now recorded as failed and retryable when result processing encounters an error, allowing subsequent retries.
    • Improved cleanup of outstanding executions when local processing fails.
    • Database connections created automatically by executors and migrations are now closed reliably.
  • Performance

    • Worker processes now reuse database connections where appropriate, reducing repeated connection setup.
  • Documentation

    • Updated the changelog with details of retry handling and database connection reuse.

Celery workers opened a database per task and never closed it,
so a long-running worker churned a connection for every result it handled.
Workers now open one database per process and reuse it,
keyed on the pid so a forked child never uses connections it inherited.

Also closes the databases the executors open for themselves,
and the one the standalone alembic environment opens.

An execution still in flight when `LocalExecutor.join` raises is now marked failed-retryable,
which is what the docstring already promised.
Only the timeout path did this before,
so an ingestion failure left the remaining rows stuck with `successful=None`
until the stale-execution reaper caught them hours later.
@coderabbitai

coderabbitai Bot commented Aug 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@lewisjared, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 18 minutes

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 406a85a1-decd-4d4e-ba37-66ab94069fff

📥 Commits

Reviewing files that changed from the base of the PR and between a506ba5 and 121f830.

📒 Files selected for processing (5)
  • changelog/879.fix.md
  • packages/climate-ref/src/climate_ref/executor/local.py
  • packages/climate-ref/src/climate_ref/executor/result_handling.py
  • packages/climate-ref/src/climate_ref/migrations/env.py
  • packages/climate-ref/tests/unit/executor/test_local_executor.py
📝 Walkthrough

Walkthrough

The changes add process-aware database reuse for Celery workers, finalise internally created databases, and improve local executor cleanup. Local executor failures now mark outstanding executions as failed retryable before re-raising the error.

Changes

Resource and retry lifecycle

Layer / File(s) Summary
Worker database reuse and shutdown
packages/climate-ref-celery/src/climate_ref_celery/worker_tasks.py, packages/climate-ref-celery/tests/unit/test_worker_tasks.py
Celery tasks use a process-aware shared database. The wrapper reuses connections for matching URLs, replaces stale connections, closes connections during shutdown, and is covered by reuse tests.
Executor and migration database finalisation
packages/climate-ref/src/climate_ref/executor/hpc.py, packages/climate-ref/src/climate_ref/executor/local.py, packages/climate-ref/src/climate_ref/executor/synchronous.py, packages/climate-ref/src/climate_ref/migrations/env.py
Executors and migrations close databases that they create internally. Caller-provided databases remain open.
Join failure cleanup and retry status
packages/climate-ref/src/climate_ref/executor/local.py, packages/climate-ref/tests/unit/executor/test_local_executor.py, changelog/879.fix.md
LocalExecutor.join marks tracked executions as failed retryable, clears tracking, shuts down the pool, and re-raises ingestion errors. The regression test validates this behaviour.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the primary change: closing database connections across the entrypoints.
Description check ✅ Passed The description is detailed and covers the changes, tests, and changelog, although it does not reproduce the required headings or checklist.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/database-connection-lifecycle

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/climate-ref-celery/tests/unit/test_worker_tasks.py (1)

11-16: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Prove both database close paths in the tests.

The fixture calls _worker_database.close() directly, so it does not verify the worker_process_shutdown callback. The URL-change test verifies only replacement and the URL; it does not verify that the replaced Database was closed. Add a spy or mock for Database.close, then exercise URL replacement and _close_worker_database.

Also applies to: 25-32


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 072a6641-3d94-4589-921a-f91675087f2f

📥 Commits

Reviewing files that changed from the base of the PR and between edbd993 and a506ba5.

📒 Files selected for processing (8)
  • changelog/879.fix.md
  • packages/climate-ref-celery/src/climate_ref_celery/worker_tasks.py
  • packages/climate-ref-celery/tests/unit/test_worker_tasks.py
  • packages/climate-ref/src/climate_ref/executor/hpc.py
  • packages/climate-ref/src/climate_ref/executor/local.py
  • packages/climate-ref/src/climate_ref/executor/synchronous.py
  • packages/climate-ref/src/climate_ref/migrations/env.py
  • packages/climate-ref/tests/unit/executor/test_local_executor.py

@codecov

codecov Bot commented Aug 14, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.05882% with 1 line in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
...ckages/climate-ref/src/climate_ref/executor/hpc.py 75.00% 0 Missing and 1 partial ⚠️
Flag Coverage Δ
core 93.75% <97.05%> (+<0.01%) ⬆️
providers 87.70% <ø> (ø)

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
...-ref-celery/src/climate_ref_celery/worker_tasks.py 100.00% <100.00%> (ø)
...ages/climate-ref/src/climate_ref/executor/local.py 97.05% <100.00%> (+0.18%) ⬆️
...te-ref/src/climate_ref/executor/result_handling.py 96.27% <100.00%> (+0.04%) ⬆️
...limate-ref/src/climate_ref/executor/synchronous.py 100.00% <100.00%> (ø)
...ckages/climate-ref/src/climate_ref/executor/hpc.py 63.98% <75.00%> (+0.03%) ⬆️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

An execution abandoned before a worker picked it up has no output directory,
so the missing log file is expected rather than a system error worth shouting about.
Reports it at debug in that case, and keeps the error for an execution that did start.

Also adds the missing space between the two halves of the uncollected-execution warning.
time.sleep(refresh_time)
except BaseException:
# Handle Ctrl-C and SystemExit exceptions to mark in-flight executions as retryable
self._fail_outstanding(results, t)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@fuchsi-huber This should resolve the Ctrl-C interrupt leaving the in flight diagnostics requirinng manual intervention

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, nice! That is definitely a QoL improvement :)

@lewisjared
lewisjared merged commit f825918 into main Aug 14, 2026
28 checks passed
@lewisjared
lewisjared deleted the fix/database-connection-lifecycle branch August 14, 2026 05:05
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.

2 participants