Skip to content

Close PostgreSQL connection pool during graceful shutdown#82

Merged
wpak-ai merged 6 commits into
developfrom
fix/shutdown-pool-closeall
Jun 29, 2026
Merged

Close PostgreSQL connection pool during graceful shutdown#82
wpak-ai merged 6 commits into
developfrom
fix/shutdown-pool-closeall

Conversation

@henry0816191

@henry0816191 henry0816191 commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • Call pool.closeall() at the end of shutdown_services() so PostgreSQL connections are released on graceful exit instead of leaking until the OS reaps the process
  • Pass the connection pool from __main__.py into shutdown_services() via a new optional pool keyword argument
  • Add shutdown tests covering successful pool closure, error tolerance when closeall() raises, and the pool=None skip path; extend FakePool with closeall() for interface parity

Test plan

  • ./run check (404 passed, 93% coverage)
  • uv run pre-commit run --all-files

Related issues

Summary by CodeRabbit

  • Bug Fixes
    • Improved shutdown reliability by closing all database connections when a database pool is available, while continuing shutdown even if pool cleanup fails.
    • Updated the health endpoint’s database pool reporting to use a simplified reachability status (db_pool: { reachable: true/false }).
  • Tests
    • Added coverage to verify database pool shutdown calls closeall() when a pool is provided, skips it when absent, and continues on exceptions.
    • Expanded pool-status and health tests to match the new db_pool reachability output.

@henry0816191 henry0816191 self-assigned this Jun 29, 2026
@henry0816191 henry0816191 requested a review from wpak-ai as a code owner June 29, 2026 16:13
@coderabbitai

coderabbitai Bot commented Jun 29, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR adds a public pool-status helper, threads the live database pool through startup and shutdown, and updates tests for health output and pool teardown behavior.

Changes

DB Pool Health and Shutdown

Layer / File(s) Summary
Pool status helper and health fields
src/paperscout/__main__.py, src/paperscout/db.py, tests/test_db.py, tests/test_health.py, tests/test_main_health_merge.py
pool_status() now reports pool reachability, and the health response and merge tests use the {"reachable": True} shape for db_pool.
Startup and shutdown pool wiring
src/paperscout/shutdown.py, src/paperscout/__main__.py
_async_main initializes and passes the live pool into shutdown, and shutdown_services closes the pool when provided.
Shutdown pool tests
tests/conftest.py, tests/test_shutdown.py
FakePool tracks closeall(), and shutdown tests cover success, failure, and pool=None paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

Possibly related PRs

Suggested reviewers

  • wpak-ai

Poem

🐇 Hop, hop, the pool is bright,
Health fields show the truth just right.
Close all cleanly, tests all sing,
Even failures won’t derail the spring.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses the pool API and health tests, but it doesn't show the required Bolt shutdown change or version comment for #81. Update Bolt shutdown to use app.stop() or an equivalent public API, add the required version comment, and verify all acceptance criteria for #81.
Docstring Coverage ⚠️ Warning Docstring coverage is 33.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly states the main change: closing the PostgreSQL connection pool during shutdown.
Description check ✅ Passed The description matches the template with Summary, Test plan, and Related issues sections filled in.
Out of Scope Changes check ✅ Passed The changes stay focused on pool shutdown, health probing, and related tests without introducing unrelated functionality.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/shutdown-pool-closeall

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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/paperscout/__main__.py (1)

309-321: 🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Widen the try/finally to cover pool creation.

pool=pool only helps after Line 286. If init_db(pool), ProbeState(pool), WG21Index(pool, cfg=settings), create_app(), or mq.start() raises before then, the fresh pool is never closed and this new shutdown path is skipped entirely.

Suggested fix
-    pool = init_pool(settings.database_url)
-    init_db(pool)
-
-    state = ProbeState(pool)
-    user_watchlist = UserWatchlist(pool)
-    index = WG21Index(pool, cfg=settings)
-    prober = ISOProber(index, state, user_watchlist)
-    sources: list[DataSource] = [index, prober]
-    if settings.enable_open_std:
-        sources.append(OpenStdSource())
-    app = create_app()
-    mq = MessageQueue(app)
-    mq.start()
+    pool = None
+    try:
+        pool = init_pool(settings.database_url)
+        init_db(pool)
+
+        state = ProbeState(pool)
+        user_watchlist = UserWatchlist(pool)
+        index = WG21Index(pool, cfg=settings)
+        prober = ISOProber(index, state, user_watchlist)
+        sources: list[DataSource] = [index, prober]
+        if settings.enable_open_std:
+            sources.append(OpenStdSource())
+        app = create_app()
+        mq = MessageQueue(app)
+        mq.start()
 
-    try:
         register_handlers(app, user_watchlist, state, paper_count_fn, launch_time)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/paperscout/__main__.py` around lines 309 - 321, Widen the shutdown
protection in __main__.py so the new pool is always cleaned up if setup fails
before the current finally block. Move the try/finally boundary to include pool
creation and the early startup steps in main, so failures in init_db,
ProbeState, WG21Index, create_app, or mq.start still reach shutdown_services
with pool available. Keep using the existing shutdown_services path and the pool
argument to ensure the connection pool is closed on any startup exception.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/paperscout/__main__.py`:
- Around line 309-321: Widen the shutdown protection in __main__.py so the new
pool is always cleaned up if setup fails before the current finally block. Move
the try/finally boundary to include pool creation and the early startup steps in
main, so failures in init_db, ProbeState, WG21Index, create_app, or mq.start
still reach shutdown_services with pool available. Keep using the existing
shutdown_services path and the pool argument to ensure the connection pool is
closed on any startup exception.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8a1f82af-8f7b-466b-a50a-dfe40611ea7b

📥 Commits

Reviewing files that changed from the base of the PR and between 3a7ef30 and 296af94.

📒 Files selected for processing (4)
  • src/paperscout/__main__.py
  • src/paperscout/shutdown.py
  • tests/conftest.py
  • tests/test_shutdown.py

@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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/paperscout/__main__.py`:
- Around line 261-270: The `_pool_status()` helper in `__main__.py` is still
introspecting psycopg2 private pool internals via `p._used` and `p._pool`, which
violates the public-API-only requirement. Update the health reporting logic to
use only public `ThreadedConnectionPool` data, keeping `_pool_status()` limited
to safe attributes such as `maxconn` or other documented fields. If you still
need in-use/available metrics, add them through your own connection pool wrapper
by tracking `getconn()` and `putconn()` calls rather than reading private
members.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 10e2ae3a-2c2f-4e3a-b33d-60f6d2585a64

📥 Commits

Reviewing files that changed from the base of the PR and between 296af94 and 82e86ad.

📒 Files selected for processing (1)
  • src/paperscout/__main__.py

Comment thread src/paperscout/__main__.py Outdated
…g only public attributes: minconn, maxconn, closed

@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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_db.py (1)

68-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Constrain the fake pool to the public API surface.

This MagicMock will happily invent private attributes, so the test can still pass if pool_status() regresses to _pool/_used access. Use a stub or MagicMock(spec_set=["minconn", "maxconn", "closed"]) so the test actually enforces the “public API only” requirement from the linked issue.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_db.py` around lines 68 - 73, The test for pool_status currently
uses an unrestricted MagicMock, which can hide regressions to private pool
internals. Update test_pool_status_uses_public_api_only to use a constrained
stub or MagicMock with spec_set limited to minconn, maxconn, and closed so it
only permits the public API. Keep the assertion against pool_status() unchanged,
and make sure the test still verifies that the function reads only those public
attributes rather than _pool or _used.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/paperscout/db.py`:
- Around line 52-58: The pool_status helper is masking incompatible pool shapes
by using getattr(..., None), which can silently return null health metrics.
Update pool_status to access the ThreadedConnectionPool attributes directly for
minconn, maxconn, and closed, or explicitly raise an error if any required
metric is missing, so incompatible pool objects fail fast instead of producing
misleading /health output.

---

Nitpick comments:
In `@tests/test_db.py`:
- Around line 68-73: The test for pool_status currently uses an unrestricted
MagicMock, which can hide regressions to private pool internals. Update
test_pool_status_uses_public_api_only to use a constrained stub or MagicMock
with spec_set limited to minconn, maxconn, and closed so it only permits the
public API. Keep the assertion against pool_status() unchanged, and make sure
the test still verifies that the function reads only those public attributes
rather than _pool or _used.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 5e73addb-6fe7-4856-ab82-70c518d43f19

📥 Commits

Reviewing files that changed from the base of the PR and between 82e86ad and c3962ff.

📒 Files selected for processing (4)
  • src/paperscout/__main__.py
  • src/paperscout/db.py
  • tests/test_db.py
  • tests/test_health.py
✅ Files skipped from review due to trivial changes (1)
  • tests/test_health.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/paperscout/main.py

Comment thread src/paperscout/db.py Outdated

@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.

♻️ Duplicate comments (1)
src/paperscout/db.py (1)

52-57: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

pool_status() still relies on undocumented psycopg2 pool attributes.

The official psycopg2.pool docs describe getconn(), putconn(), and closeall(), but not minconn, maxconn, or closed, so this helper still depends on implementation details rather than the public pool API. The docstring on Line 53 is therefore overstating the guarantee as written. (psycopg.org)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/paperscout/db.py` around lines 52 - 57, `pool_status()` is still reading
non-public psycopg2 pool attributes, so update the helper to rely only on the
documented `ThreadedConnectionPool` API. In `pool_status`, remove the direct use
of `minconn`, `maxconn`, and `closed`, and adjust the returned metrics and
docstring to reflect only information that can be obtained safely from public
methods like `getconn()`, `putconn()`, and `closeall()`. Keep the change
localized to `pool_status` in `db.py` so the status helper no longer implies
unsupported guarantees.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@src/paperscout/db.py`:
- Around line 52-57: `pool_status()` is still reading non-public psycopg2 pool
attributes, so update the helper to rely only on the documented
`ThreadedConnectionPool` API. In `pool_status`, remove the direct use of
`minconn`, `maxconn`, and `closed`, and adjust the returned metrics and
docstring to reflect only information that can be obtained safely from public
methods like `getconn()`, `putconn()`, and `closeall()`. Keep the change
localized to `pool_status` in `db.py` so the status helper no longer implies
unsupported guarantees.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9732e635-2cf0-4c72-b647-b694744d30d7

📥 Commits

Reviewing files that changed from the base of the PR and between c3962ff and fe0be3c.

📒 Files selected for processing (2)
  • src/paperscout/db.py
  • tests/test_db.py
🚧 Files skipped from review as they are similar to previous changes (1)
  • tests/test_db.py

@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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/paperscout/db.py`:
- Around line 64-67: The connection cleanup in the putconn handling swallows
failures without fully releasing a bad borrowed connection. Update the exception
branch around p.putconn(conn) in the db connectivity helper to call conn.close()
as a fallback before returning, so a failed return to the pool does not leave
conn lingering; use the existing connection/pool handling path in
src/paperscout/db.py to locate it.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 9088fe47-2788-4de1-a2b9-68fe95d61d53

📥 Commits

Reviewing files that changed from the base of the PR and between fe0be3c and b89667a.

📒 Files selected for processing (4)
  • src/paperscout/db.py
  • tests/test_db.py
  • tests/test_health.py
  • tests/test_main_health_merge.py

Comment thread src/paperscout/db.py
@wpak-ai wpak-ai merged commit 4f843db into develop Jun 29, 2026
10 checks passed
@wpak-ai wpak-ai deleted the fix/shutdown-pool-closeall branch June 29, 2026 17:48
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.

MQ health coupling: replace private psycopg2/Bolt API access with public interfaces

2 participants