[Fix][Observability]: Fix duplicate DB session in observability middleware#3600
Conversation
00f8363 to
06a28e7
Compare
|
Hi @smrutisahoo10, Good PR, please read through this AI generated review and fix/reject the suggested changes with justification. Executive Summary✅ Overall Assessment: APPROVE WITH CHANGES REQUIRED The PR successfully addresses the core issue of duplicate database session creation but requires critical fixes for connection invalidation to ensure production reliability, especially with PostgreSQL/PgBouncer deployments. ✅ Core Requirements MetThe PR successfully addresses all main requirements from issue #3467:
🔴 Critical Issues (Must Fix)1. Missing Connection Invalidation in Main Error PathLocation: Issue: The middleware's exception handler only calls Current Code: # Rollback the shared session on error
try:
db.rollback()
except Exception as rollback_error:
logger.warning(f"Failed to rollback database session: {rollback_error}")Required Fix: # Rollback the shared session on error
try:
db.rollback()
except Exception as rollback_error:
logger.warning(f"Failed to rollback database session: {rollback_error}")
# Connection is broken - invalidate to remove from pool
# This handles cases like PgBouncer query_wait_timeout where
# the connection is dead and rollback itself fails
try:
db.invalidate()
except Exception:
pass # Best effort cleanup on connection failureRationale: This matches the established pattern in 2. Missing Invalidation in Trace Setup Error PathLocation: Issue: The trace setup cleanup path also needs connection invalidation for consistency and reliability. Current Code: except Exception as e:
# If trace setup failed, log and continue without tracing
logger.warning(f"Failed to setup observability trace: {e}")
# Close db if it was created
if db:
try:
db.rollback() # Error path - rollback any partial transaction
db.close()
except Exception as close_error:
logger.debug(f"Failed to close database session during cleanup: {close_error}")Required Fix: except Exception as e:
# If trace setup failed, log and continue without tracing
logger.warning(f"Failed to setup observability trace: {e}")
# Close db if it was created
if db:
try:
db.rollback() # Error path - rollback any partial transaction
except Exception as rollback_error:
logger.debug(f"Failed to rollback during cleanup: {rollback_error}")
# Connection is broken - invalidate to remove from pool
try:
db.invalidate()
except Exception:
pass # Best effort cleanup
try:
db.close()
except Exception as close_error:
logger.debug(f"Failed to close database session during cleanup: {close_error}")3. Potential Double-Commit RiskLocation: Issue: The codebase has 399 instances of Current Code: # Commit the shared session (used by both observability and route handler)
# Only commit if the transaction is still active
if db.is_active:
db.commit()Recommended Fix: # Commit the shared session (used by both observability and route handler)
# Only commit if the transaction is still active AND has uncommitted changes
if db.is_active and db.in_transaction():
db.commit()Rationale: The
|
| File | Lines Changed | Purpose |
|---|---|---|
mcpgateway/middleware/observability_middleware.py |
+32, -17 | Session sharing implementation |
mcpgateway/main.py |
+23, -1 | get_db() reuse logic |
tests/unit/mcpgateway/middleware/test_observability_middleware.py |
+306, -4 | Test coverage |
mcpgateway/admin.py |
+4, -1 | Code formatting (unrelated) |
Key Design Decisions
- Request State Storage: Uses
request.state.dbas the shared session container - Ownership Tracking:
session_owned_by_middlewareflag ensures proper cleanup - Backward Compatibility: get_db() falls back to creating its own session when middleware doesn't provide one
- Lifecycle Management: Middleware handles commit/rollback/close for shared sessions
🎯 Verification Checklist
- Eliminates duplicate session creation
- Maintains backward compatibility
- Proper error handling (needs fixes)
- Comprehensive test coverage
- No breaking changes to API
- Connection invalidation on broken connections (MISSING - CRITICAL)
- Double-commit prevention (NEEDS IMPROVEMENT)
- Production-appropriate logging levels (NEEDS FIX)
- Documentation updates (MISSING - NON-BLOCKING)
There was a problem hiding this comment.
additional validation needed as per the comments #3600 (comment)
510ae8d to
ff824bb
Compare
ff824bb to
56f7a41
Compare
|
Hi @ja8zyjits, Addressed the review comment. ✅ Review Feedback AddressedAll critical issues from the code review have been resolved: 🔴 Critical Fixes1. Connection Invalidation for PostgreSQL/PgBouncerIssue: Broken connections stayed in pool causing cascading failures
Pattern: Matches established 2. Double-Commit PreventionIssue: Potential double-commit when handlers already committed
|
|
Thanks @smrutisahoo10 — fixes duplicate DB session in observability middleware. Related to #3622. Targeting 1.1.0. |
MohanLaksh
left a comment
There was a problem hiding this comment.
PR #3600 Review Summary
Core Implementation (Excellent)
- Successfully eliminates duplicate session creation - The middleware now creates a request-scoped session stored in
request.state.db, whichget_db()reuses - 50% reduction in database connections for traced requests
- Prevents SQLite StaticPool segfaults under concurrent load
- Maintains backward compatibility -
get_db()falls back to creating its own session when observability is disabled
Code Quality
- Comprehensive test coverage - Added 4 new tests + updated 8 existing tests
- Proper error handling - Includes rollback, invalidation, and cleanup logic
- Good documentation - Clear comments explaining the session sharing pattern
Review Response
The author has addressed all critical review comments from @ja8zyjits:
- ✅ Added connection invalidation in both error paths (trace setup + main exception handler)
- ✅ Added
db.in_transaction()check to prevent double-commit issues - ✅ Changed logging from INFO to DEBUG level
- ✅ Achieved 100% test coverage (up from 76%)
🎯 Current State
Files Changed (3)
mcpgateway/middleware/observability_middleware.py(+45, -5 lines)mcpgateway/main.py(+22, -1 lines)tests/unit/mcpgateway/middleware/test_observability_middleware.py(+384, -10 lines)
Key Design Decisions
- Session ownership tracking via
session_owned_by_middlewareflag - Request state storage using
request.state.db - Lifecycle management - Middleware handles commit/rollback/close for shared sessions
- Safe commit logic - Only commits if
db.is_active and db.in_transaction()
🔍 Technical Review
Architecture Alignment ✅
- Follows the synchronous SQLAlchemy pattern (per AGENTS.md design decision)
- Consistent with existing error handling patterns in
get_db() - Respects the two-layer security model (token scoping + RBAC)
Error Handling ✅
- Connection invalidation properly implemented in both error paths
- Graceful degradation - Continues without tracing if setup fails
- Proper cleanup - Removes
request.state.dbto prevent stale references
Testing ✅
- Integration test verifies single session per request (critical assertion)
- Error path coverage includes rollback failures, invalidation failures
- Session reuse tests confirm
get_db()behavior in both scenarios
⚠️ Minor Observation (Non-Blocking):
- Documentation Gap - No updates to architecture docs (mentioned as "Future Work" in review)
- Consider updating
docs/docs/architecture/multitenancy.mdwith session lifecycle info - Could add note to
docs/docs/manage/observability.mdabout session sharing
- Consider updating
🚦 Recommendation
APPROVE - This PR is ready to merge.
Rationale
- ✅ All critical review comments have been addressed
- ✅ 100% test coverage with comprehensive edge case testing
- ✅ Production-ready error handling (connection invalidation, double-commit prevention)
- ✅ Proper logging levels for production use
- ✅ Maintains backward compatibility
- ✅ Solves the stated problem (duplicate sessions) effectively
Post-Merge Recommendations
- Monitor production metrics for session usage reduction
- Consider adding session lifecycle documentation in a follow-up PR
- Watch for any edge cases with services that explicitly commit (399 instances across 53 files)
📊 Impact Assessment
Risk Level: LOW
Performance Impact: POSITIVE (50% reduction in session usage)
Breaking Changes: NONE
Deployment Notes: Safe to deploy - graceful degradation if issues occur
Final Verdict: This is a well-implemented fix with excellent test coverage and proper error handling. The author has been responsive to feedback and addressed all critical concerns. Ready for merge. 🚀
56f7a41 to
6489ca0
Compare
Signed-off-by: Smruti Sahoo <talktodaisy19@gmail.com>
Signed-off-by: Smruti Sahoo <talktodaisy19@gmail.com>
…ervability Signed-off-by: Smruti Sahoo <talktodaisy19@gmail.com>
Signed-off-by: Smruti Sahoo <talktodaisy19@gmail.com>
Remove duplicate mock_request fixture that was overriding the original and dropping the traceparent header, which caused lines 99-102 of observability_middleware.py to lose test coverage. Also remove unused asyncio import and add missing trailing newline. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Bandit flags try/except/pass as B110. These two sites are intentional best-effort cleanup when db.invalidate() fails after a broken connection — there is nothing useful to do with the exception. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
Signed-off-by: Mihai Criveti <crivetimihai@gmail.com>
51454d7 to
d4ae9b5
Compare
Review CompleteRebased onto main (clean, no conflicts), reviewed, tested, and added 3 fixup commits. Review FindingsDesign: Correct. The middleware owns the full session lifecycle (create/commit/rollback/close). The reuse path in Logic: All code paths verified correct:
Security: No issues. Session sharing is internal between middleware and route handlers. Performance: Reduces DB sessions from 2→1 per request for 50+ routes using Coverage: 100% on Fixup Commits Added
E2E Verification (localhost:8080, docker-compose stack)
|
crivetimihai
left a comment
There was a problem hiding this comment.
Reviewed, rebased, tested end-to-end. Fix is correct and complete for its scope. All code paths verified, 100% differential coverage, bandit clean, full E2E passing against docker-compose stack.
PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
… sessions (#3731) (#3813) * fix(db): restore transaction control to get_db() for middleware sessions PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * test(db): add coverage for double-failure edge case in get_db() Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(tests): clean up lint violations in transaction control tests Remove unused AsyncMock import and unused variable assignments flagged by ruff (F401, F841). Apply isort/black formatting. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
… sessions (#3731) (#3813) * fix(db): restore transaction control to get_db() for middleware sessions PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * test(db): add coverage for double-failure edge case in get_db() Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(tests): clean up lint violations in transaction control tests Remove unused AsyncMock import and unused variable assignments flagged by ruff (F401, F841). Apply isort/black formatting. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
… sessions (#3731) (#3813) * fix(db): restore transaction control to get_db() for middleware sessions PR #3600 introduced a transaction management violation where ObservabilityMiddleware commits the shared database session instead of get_db(), breaking the established contract where get_db() controls transaction boundaries. This creates data integrity risks where failed validations can be committed to the database. This fix restores the correct behavior: - Middleware manages session lifecycle (create/close) - get_db() manages transactions (commit/rollback) Changes: - Remove commit logic from ObservabilityMiddleware (observability_middleware.py:210-216) - Add commit/rollback handling to get_db() for middleware sessions (main.py:3137-3164) - Update get_db() docstring to document transaction control responsibility - Update 2 existing tests to reflect new behavior - Add 7 comprehensive tests for transaction semantics Security implications: - Fixes data integrity bug where invalid data could be committed - Maintains proper transaction isolation per request - Preserves connection invalidation on broken connections - No impact on auth/RBAC (middleware runs before route handlers) Trade-offs: - Observability data (traces/spans) is rolled back on errors (acceptable - best-effort tracing) Closes #3731 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * test(db): add coverage for double-failure edge case in get_db() Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(tests): clean up lint violations in transaction control tests Remove unused AsyncMock import and unused variable assignments flagged by ruff (F401, F841). Apply isort/black formatting. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com>
#3886) * fix: eliminate duplicate DB sessions in auth and RBAC middleware Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix: ensure auth logs are committed in hard-deny paths Fixes critical bug where auth failure logs were lost when API requests received hard-deny responses (401/403) that return JSONResponse immediately without reaching get_db(). **Root Cause:** - Auth middleware writes logs to session but delegated commit to get_db() - Hard-deny API responses return JSONResponse immediately (lines 243-247) - Route handler never runs, get_db() never called, logs never committed - Browser requests work fine (continue to route handler at line 236) **Fix:** - Added db.commit() after logging in both success and failure paths - Logs persist immediately, even if request doesn't reach get_db() - For requests that continue to route handler, get_db() commits again (no-op) - SQLAlchemy allows multiple commits - second commit is safe **Test Coverage:** - Added regression test: test_auth_failure_logs_committed_before_hard_deny_api_response - All 29 auth middleware tests pass - Maintained 100% code coverage Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(build): use system uv binary consistently in Makefile Replace hardcoded 'uv' commands with $(UV_BIN) variable reference across all targets to ensure consistent resolution of the uv binary path. Export UV_BIN to make it available to recursive make calls. This fixes build failures where make targets tried to execute 'uv' from inside the activated virtual environment, where it doesn't exist. The uv tool is a system-level package manager and should be resolved from the system PATH or ~/.local/bin. Changes: - Use $(UV_BIN) in install, install-dev, install-db, update targets - Use $(UV_BIN) in ensure_pip_package macro (fixes recursive make) - Use $(UV_BIN) in sbom, alembic, pypiserver, maturin targets - Export UV_BIN for visibility in child make processes Benefits: - Fixes "No such file or directory" errors for uv - Makes Makefile more robust across different uv installations - Maintains existing fallback logic (PATH or ~/.local/bin/uv) - No breaking changes for existing setups Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(auth): prevent stale session reference and ensure log persistence Fix two bugs in auth middleware session handling: 1. _get_or_create_session() stored newly created sessions in request.state.db but then closed them after logging, leaving a stale closed-session reference for downstream get_db() to find. Remove the request.state.db assignment for owned sessions so route handlers create their own sessions via get_db(). 2. Generic Exception path (non-HTTP auth failures) did not commit security logs before closing the owned session, silently losing log entries. Add db.commit() consistent with the success and hard-deny paths. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(auth): rollback shared session on logging failure and rewrite integration tests Address two findings from code review: 1. When security logging raises an exception (commit failure, connection error), the shared session from ObservabilityMiddleware is left in PendingRollbackError state. Downstream call_next()/get_db() then inherits a broken session. Add db.rollback() (with invalidate() fallback) in all three exception handlers — matching the pattern used by ObservabilityMiddleware and main.py:get_db(). 2. Rewrite integration tests: the originals targeted nonexistent routes (/api/v1/servers, /admin/llm/providers) and had assertions too weak to catch regressions (session_count <= 1 passes when 0 sessions are created). New tests directly exercise _get_or_create_session(), rbac.get_db() reuse, shared-session rollback, and the full middleware stack via /health. Test coverage: 100% (460 statements, 0 missing) across both auth middleware and rbac modules. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: update secrets baseline after rebase Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: exclude .npmrc from sdist to fix check-manifest Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
#3886) * fix: eliminate duplicate DB sessions in auth and RBAC middleware Implements session reuse pattern from PR #3600 and PR #3813 to achieve 1 shared database session per request across all middleware layers. **Changes:** - Auth middleware: Added _get_or_create_session() helper to reuse request.state.db from ObservabilityMiddleware (lines 134, 159, 213) - RBAC middleware: Updated deprecated get_db() to accept optional request parameter and reuse middleware session when available - Transaction control: Delegated all commit/rollback to get_db() per PR #3813 (removed db.commit() from auth middleware) - Added 7 unit tests for auth session reuse patterns - Added 7 unit tests for RBAC get_db() deprecation - Added 6 integration tests for end-to-end session sharing validation **Impact:** - Reduces session creation from 4-6 per request to 1 per request - Prevents connection pool exhaustion under load - Achieves 100% test coverage (435 statements, 0 missing) **Security:** - Transaction isolation maintained (get_db() controls all commits) - Connection invalidation for PgBouncer compatibility - Backwards compatible (existing dependency overrides work) Closes #3622 Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix: ensure auth logs are committed in hard-deny paths Fixes critical bug where auth failure logs were lost when API requests received hard-deny responses (401/403) that return JSONResponse immediately without reaching get_db(). **Root Cause:** - Auth middleware writes logs to session but delegated commit to get_db() - Hard-deny API responses return JSONResponse immediately (lines 243-247) - Route handler never runs, get_db() never called, logs never committed - Browser requests work fine (continue to route handler at line 236) **Fix:** - Added db.commit() after logging in both success and failure paths - Logs persist immediately, even if request doesn't reach get_db() - For requests that continue to route handler, get_db() commits again (no-op) - SQLAlchemy allows multiple commits - second commit is safe **Test Coverage:** - Added regression test: test_auth_failure_logs_committed_before_hard_deny_api_response - All 29 auth middleware tests pass - Maintained 100% code coverage Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(build): use system uv binary consistently in Makefile Replace hardcoded 'uv' commands with $(UV_BIN) variable reference across all targets to ensure consistent resolution of the uv binary path. Export UV_BIN to make it available to recursive make calls. This fixes build failures where make targets tried to execute 'uv' from inside the activated virtual environment, where it doesn't exist. The uv tool is a system-level package manager and should be resolved from the system PATH or ~/.local/bin. Changes: - Use $(UV_BIN) in install, install-dev, install-db, update targets - Use $(UV_BIN) in ensure_pip_package macro (fixes recursive make) - Use $(UV_BIN) in sbom, alembic, pypiserver, maturin targets - Export UV_BIN for visibility in child make processes Benefits: - Fixes "No such file or directory" errors for uv - Makes Makefile more robust across different uv installations - Maintains existing fallback logic (PATH or ~/.local/bin/uv) - No breaking changes for existing setups Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> * fix(auth): prevent stale session reference and ensure log persistence Fix two bugs in auth middleware session handling: 1. _get_or_create_session() stored newly created sessions in request.state.db but then closed them after logging, leaving a stale closed-session reference for downstream get_db() to find. Remove the request.state.db assignment for owned sessions so route handlers create their own sessions via get_db(). 2. Generic Exception path (non-HTTP auth failures) did not commit security logs before closing the owned session, silently losing log entries. Add db.commit() consistent with the success and hard-deny paths. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * fix(auth): rollback shared session on logging failure and rewrite integration tests Address two findings from code review: 1. When security logging raises an exception (commit failure, connection error), the shared session from ObservabilityMiddleware is left in PendingRollbackError state. Downstream call_next()/get_db() then inherits a broken session. Add db.rollback() (with invalidate() fallback) in all three exception handlers — matching the pattern used by ObservabilityMiddleware and main.py:get_db(). 2. Rewrite integration tests: the originals targeted nonexistent routes (/api/v1/servers, /admin/llm/providers) and had assertions too weak to catch regressions (session_count <= 1 passes when 0 sessions are created). New tests directly exercise _get_or_create_session(), rbac.get_db() reuse, shared-session rollback, and the full middleware stack via /health. Test coverage: 100% (460 statements, 0 missing) across both auth middleware and rbac modules. Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: update secrets baseline after rebase Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> * chore: exclude .npmrc from sdist to fix check-manifest Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> --------- Signed-off-by: Mohan Lakshmaiah <mohan.economist@gmail.com> Signed-off-by: Mihai Criveti <crivetimihai@gmail.com> Co-authored-by: Mihai Criveti <crivetimihai@gmail.com>
🔗 Related Issue
Closes #3467
📝 Summary
What does this PR do?
Eliminates duplicate database session creation in observability middleware by implementing request-scoped session sharing between middleware and route handlers.
Why?
Technical Changes:
mcpgateway/middleware/observability_middleware.py:request.state.dbmcpgateway/main.py(get_db()function):request.state.dband reuses if presenttests/unit/mcpgateway/middleware/test_observability_middleware.py:should_skip_observabilityfor proper coverage🏷️ Type of Change
🧪 Verification
make lintmake testmake coverage✅ Checklist
make black isort pre-commit)📓 Notes (optional)
Manual Testing Results:
With OBSERVABILITY_ENABLED=true, single session ID confirmed:
[OBSERVABILITY] DB session created: 139870311167472
[GET_DB] Reusing session from middleware: 139870311167472 ← Same ID ✅
Before fix (two different session IDs):
[OBSERVABILITY] DB session created: 4709458000
[GET_DB] DB session created: 4710405520 ← Different ID ❌