Skip to content

fix(postgres-mcp-server): avoid binding PG-wire connection to a closed event loop - #4846

Merged
kennthhz merged 20 commits into
awslabs:mainfrom
mangeshraut712:fix/postgres-startup-closed-loop
Nov 9, 2026
Merged

kennthhz merged 20 commits into
awslabs:mainfrom
mangeshraut712:fix/postgres-startup-closed-loop

Conversation

@mangeshraut712

@mangeshraut712 mangeshraut712 commented Oct 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #4826

Summary

Changes

When PG-wire startup validation ran under asyncio.run(), the pool and aiorwlock were bound to a temporary loop that then closed. mcp.run() serves tools on a different loop, so the first run_query failed with a different-event-loop RuntimeError.

Move PG-wire (PsycopgPoolConnection) startup validation into the MCP server lifespan so the pool initializes on the serving loop, and clean up loop-local connection maps on shutdown. Non-PG-wire validation stays on the existing sync path.

User experience

Before: the first run_query after PG-wire startup could fail with a closed / different event loop error. After: validation runs on the serving loop, so the first query uses a live pool.

Checklist

  • I have reviewed the contributing guidelines
  • I have performed a self-review of this change
  • Changes have been tested
  • Changes are documented

Tests: regression covering main() → lifespan validation → first run_query with a fake wire pool. uv run pytest — 2501 passed in the server package context. Ruff and pyright clean on touched files.

Is this a breaking change? (Y/N) N

Acknowledgment

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.

@mangeshraut712

Copy link
Copy Markdown
Contributor Author

Fixes #4826

Why: PG-wire startup validation under asyncio.run() bound the pool to a temporary loop that then closed; first run_query failed on the serving loop.

What: Move PG-wire (PsycopgPoolConnection) startup validation into MCP server lifespan so the pool initializes on the serving loop; clean up loop-local maps on shutdown.

Test: Regression main() → lifespan validation → first run_query with fake wire pool. Package pytest green; ruff/pyright clean on touched files.

@mangeshraut712

Copy link
Copy Markdown
Contributor Author

CI is green. Avoids binding the PG-wire connection to a closed event loop. Please review and merge when you can.

@mangeshraut712
mangeshraut712 force-pushed the fix/postgres-startup-closed-loop branch from 84163f4 to 62ea937 Compare October 27, 2026 14:47
@codecov

codecov Bot commented Oct 28, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 96.47059% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 93.82%. Comparing base (2799534) to head (431ad8a).

Files with missing lines Patch % Lines
...s-mcp-server/awslabs/postgres_mcp_server/server.py 96.00% 1 Missing and 1 partial ⚠️
...ostgres_mcp_server/connection/db_connection_map.py 96.96% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #4846      +/-   ##
==========================================
+ Coverage   93.80%   93.82%   +0.01%     
==========================================
  Files        1080     1080              
  Lines       95085    95152      +67     
  Branches    15506    15514       +8     
==========================================
+ Hits        89197    89277      +80     
+ Misses       3511     3493      -18     
- Partials     2377     2382       +5     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@kennthhz kennthhz left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Diagnosis looks right to me, and the lifespan is the correct mechanism. I traced the root cause and it holds: every pool operation in PsycopgPoolConnection goes through self.rw_lock, and aiorwlock binds its loop lazily on first acquire, then raises on cross-loop use. asyncio.run() bound it to a loop that then closed.

Two things I'd want addressed before this merges, plus some smaller notes inline.

The one I care most about: nothing appears to test that the lifespan is actually registered. The new test calls server_lifespan(...) directly and patches mcp.run, so lifespan=server_lifespan in the constructor is unasserted. That matters more than it looks — see the inline comment.

I verified the framework behavior against mcp 2.0.0 and aiorwlock 1.5.0 locally. One claim I could not check: whether a test elsewhere in the suite already covers the lifespan registration. If so, ignore that comment.

kennthhz
kennthhz previously approved these changes Nov 7, 2026
@kennthhz
kennthhz enabled auto-merge November 9, 2026 18:34

@scottschreckengaust scottschreckengaust left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

LGTM

@kennthhz
kennthhz added this pull request to the merge queue Nov 9, 2026
Merged via the queue into awslabs:main with commit 730add8 Nov 9, 2026
138 checks passed
@github-project-automation github-project-automation Bot moved this from To triage to Done in awslabs/mcp Project Nov 9, 2026
mangeshraut712 added a commit to mangeshraut712/mcp that referenced this pull request Nov 9, 2026
awslabs#4846 moved loop-affine startup validation from main() into the MCP
lifespan, so the username-only IAM startup test now checks the
connection main() defers instead of an awaited validate_connection.

Signed-off-by: Mangesh Raut <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

postgres-mcp-server: startup PG-wire connection is bound to a closed event loop; first run_query fails

3 participants