Close PostgreSQL connection pool during graceful shutdown#82
Conversation
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe 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. ChangesDB Pool Health and Shutdown
Estimated code review effort🎯 2 (Simple) | ⏱️ ~10 minutes Possibly related issues
Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 winWiden the
try/finallyto cover pool creation.
pool=poolonly helps after Line 286. Ifinit_db(pool),ProbeState(pool),WG21Index(pool, cfg=settings),create_app(), ormq.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
📒 Files selected for processing (4)
src/paperscout/__main__.pysrc/paperscout/shutdown.pytests/conftest.pytests/test_shutdown.py
There was a problem hiding this comment.
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
📒 Files selected for processing (1)
src/paperscout/__main__.py
…g only public attributes: minconn, maxconn, closed
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
tests/test_db.py (1)
68-73: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winConstrain the fake pool to the public API surface.
This
MagicMockwill happily invent private attributes, so the test can still pass ifpool_status()regresses to_pool/_usedaccess. Use a stub orMagicMock(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
📒 Files selected for processing (4)
src/paperscout/__main__.pysrc/paperscout/db.pytests/test_db.pytests/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
There was a problem hiding this comment.
♻️ 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.pooldocs describegetconn(),putconn(), andcloseall(), but notminconn,maxconn, orclosed, 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
📒 Files selected for processing (2)
src/paperscout/db.pytests/test_db.py
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/test_db.py
There was a problem hiding this comment.
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
📒 Files selected for processing (4)
src/paperscout/db.pytests/test_db.pytests/test_health.pytests/test_main_health_merge.py
Summary
pool.closeall()at the end ofshutdown_services()so PostgreSQL connections are released on graceful exit instead of leaking until the OS reaps the process__main__.pyintoshutdown_services()via a new optionalpoolkeyword argumentcloseall()raises, and thepool=Noneskip path; extendFakePoolwithcloseall()for interface parityTest plan
./run check(404 passed, 93% coverage)uv run pre-commit run --all-filesRelated issues
Summary by CodeRabbit
db_pool: { reachable: true/false }).closeall()when a pool is provided, skips it when absent, and continues on exceptions.db_poolreachability output.