Skip to content

FIX: Bound pooled connection checkout on unresponsive sockets - #859

Merged
Jahnvi Thakkar (jahnvi480) merged 5 commits into
mainfrom
jahnvi/pooled-checkout-timeout
Oct 28, 2026
Merged

Jahnvi Thakkar (jahnvi480) merged 5 commits into
mainfrom
jahnvi/pooled-checkout-timeout

Conversation

@jahnvi480

Copy link
Copy Markdown
Contributor

Work Item / Issue Reference

GitHub Issue: #854


Summary

Bound pooled connection reset and autocommit configuration using a temporary native connection timeout. Use the positive login timeout (explicit attrs_before takes precedence), or a 15-second validation default, and restore the previous connection timeout on success. After validation fails, open a replacement rather than probing every stale idle socket sequentially. Preserve isolation/transaction cleanup and document that cancellation, cleanup, and replacement login can add time: this is not an end-to-end connect deadline.

Add stalled-socket regressions covering both autocommit modes, one/three stale sockets, timeout precedence, the finite default, and timeout restoration. Extend isolation reset coverage to SQL SET statements and adjust close-error mocks to inject failures after construction.

Validation (Linux x64, Python 3.13, SQL Server 2022):

  • bash build.sh and subsequent cmake --build /work/mssql_python/pybind/build --config Release -j2: passed; rebuilt native extension used for testing.
  • python -m pytest -v --tb=short: 5,537 passed, 167 skipped, 42 deselected. Linux test copy used LF shell scripts to avoid Windows checkout CRLF failures.
  • python -m pre_commit run black-check --all-files --hook-stage pre-push: passed.
  • Actual TCP DROP reproduction: approximately 3 seconds with timeout=1 for one/three stale connections in both autocommit modes; replacement queries succeeded.
  • Windows/macOS validation not run. An initial combined packet-drop run exited with code 139; isolated and repeated runs subsequently passed, but the initial crash was not conclusively explained.

Fixes: #854

Apply a temporary native connection timeout during pooled reset and autocommit configuration, then restore it on success. Replace a failed candidate instead of probing all idle sockets sequentially.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 06:34
@github-actions github-actions Bot added the pr-size: medium Moderate update size label Oct 22, 2026
@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

PR Performance Report

✅ No regression detected

No consistent slowdowns detected across all 2 environments.

0 IMPROVEMENTS 0 SLOWDOWNS 2/2 ENVIRONMENTS

Coverage: 2 of 2 environments completed. Advisory result; does not block merging.

Performance diagnostics

Phase times are inclusive diagnostics and must not be added together. They identify where measured time changed, not why it changed.

Unix / SQL Server 2022

Connection opening: ddbc::ConnectionPoolManager::acquireConnection +0.336 ms; ddbc::ConnectionHandle::ConnectionHandle +0.336 ms; ddbc::ConnectionPool::acquire +0.332 ms. Call changes: ddbc::Connection::setAutocommit (added, removed, or intermittent); ddbc::ConnectionHandle::setAutocommit (added, removed, or intermittent).

Unix / SQL Server 2025

Connection opening: no measured phase delta. Call changes: ddbc::Connection::setAutocommit (added, removed, or intermittent); ddbc::ConnectionHandle::setAutocommit (added, removed, or intermittent).

All database tasks and timings

Unix / SQL Server 2022

Database task Before After Paired change Result
Connection opening 10.415 ms 10.596 ms +0.9% no signal
SELECT queries 1.081 ms 1.064 ms -0.1% no signal
Row insertion 34.639 ms 34.914 ms +2.1% no signal
Executemany inserts 158.305 ms 157.215 ms +0.9% no signal
Fetch-all queries 122.369 ms 122.358 ms -0.2% no signal
Row-by-row fetching 14.440 ms 14.310 ms -0.3% no signal
Batched row fetching 119.083 ms 117.401 ms -0.7% no signal
Transaction commit and rollback 115.720 ms 115.572 ms -0.2% no signal
Arrow row fetching 94.378 ms 93.122 ms -1.3% no signal
100,000-row insertion 460.094 ms 466.233 ms -0.5% no signal
Row fetching in batches of 100 120.952 ms 123.330 ms +2.5% no signal
Row fetching in batches of 10,000 146.025 ms 130.700 ms -6.1% no signal
Repeated positional queries 34.445 ms 34.433 ms +2.0% no signal
Repeated named-parameter queries 37.107 ms 36.952 ms +1.1% no signal
Legacy 100,000-row insertion 357.572 ms 363.263 ms +0.5% no signal
Insertion with explicit input sizes 508.969 ms 512.842 ms +2.9% no signal
Joined aggregation queries 176.831 ms 177.176 ms +0.4% no signal
Large joined-result fetching 196.752 ms 189.124 ms -3.6% no signal
1.2-million-row fetching 3495.706 ms 3470.694 ms -0.4% no signal
Common table expression queries 5.580 ms 5.526 ms -0.4% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.330 ms 1.365 ms +5.4% no signal
10,000 scalar values / fetchval() (debug disabled) 108.771 ms 107.936 ms +0.4% no signal

Unix / SQL Server 2025

Database task Before After Paired change Result
Connection opening 99.348 ms 97.481 ms -1.9% no signal
SELECT queries 1.071 ms 1.101 ms +0.1% no signal
Row insertion 34.959 ms 34.657 ms +2.0% no signal
Executemany inserts 151.066 ms 151.884 ms +0.2% no signal
Fetch-all queries 118.826 ms 119.722 ms +1.6% no signal
Row-by-row fetching 14.321 ms 14.797 ms +1.2% no signal
Batched row fetching 115.565 ms 118.685 ms +2.8% no signal
Transaction commit and rollback 115.180 ms 119.699 ms +5.0% no signal
Arrow row fetching 92.549 ms 92.604 ms -0.0% no signal
100,000-row insertion 446.365 ms 440.780 ms -2.3% no signal
Row fetching in batches of 100 119.695 ms 120.676 ms +1.0% no signal
Row fetching in batches of 10,000 131.330 ms 124.135 ms -3.0% no signal
Repeated positional queries 33.580 ms 34.064 ms +0.8% no signal
Repeated named-parameter queries 36.134 ms 36.300 ms +0.6% no signal
Legacy 100,000-row insertion 354.013 ms 359.369 ms +0.7% no signal
Insertion with explicit input sizes 472.639 ms 480.093 ms +1.0% no signal
Joined aggregation queries 161.458 ms 162.395 ms -0.0% no signal
Large joined-result fetching 178.862 ms 180.788 ms +1.2% no signal
1.2-million-row fetching 3463.517 ms 3426.558 ms -0.6% no signal
Common table expression queries 5.146 ms 5.039 ms -1.0% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.472 ms 1.493 ms +0.2% no signal
10,000 scalar values / fetchval() (debug disabled) 106.745 ms 107.547 ms -2.0% no signal
Build and measurement details

ADO build 186593

PR head: 8301e6398abb921275b8e1538d5d95d915b85ca4
Base: 185bc651fd2626aa4058de1be7658e52aad4c413
Measured merge: 3e6a379df154dfddb4e32201fbde731507375e22

  • Unix / SQL Server 2022: Python 3.12.3, x86_64, SQL 16.0.4305.5; 5 paired comparisons and 1 warmup.
  • Unix / SQL Server 2025: Python 3.12.3, x86_64, SQL 17.0.5035.5; 5 paired comparisons and 1 warmup.

A consistent change requires more than 20% median paired movement, at least 1 ms between the median runtimes, and at least 80% of pairs exceeding the relative threshold in the same direction. A slowdown without enough pair agreement is reported as inconsistent.

The displayed change is the median of paired before-and-after ratios. It is not recalculated from the two displayed median runtimes.

Both revisions use profiling-enabled builds on the same agent and database, with alternating order and discarded warmups. Results are diagnostic and do not represent production-wheel latency.

Raw samples and logs are attached to the ADO run as profiler-* artifacts.

Copilot AI 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.

🔵 Needs a closer look

The reported native crash remains unexplained, and Windows/macOS timeout and cleanup behavior still needs validation.

1 open issue
What changed in this PR

Addresses #854 by limiting pooled-connection validation on unresponsive sockets without imposing an end-to-end connect() deadline.

Changes:

  • Applies a temporary native timeout during reset and autocommit configuration, restoring it after successful checkout.
  • Opens a replacement after failed validation instead of probing every stale idle connection.
  • Adds stalled-socket and isolation-reset regressions, adjusts exception mocks, and documents timeout behavior.
File Description
tests/​test_009_pooling.py Adds stalled-checkout, timeout-restoration, and SQL isolation-reset coverage.
tests/​test_006_exceptions.py Injects close failures after connection construction.
README.md Documents checkout timeout selection and limitations.
mssql_python/​pybind/​connection/​connection.h Extends reset to accept timeout and autocommit settings.
mssql_python/​pybind/​connection/​connection.cpp Applies and restores the temporary native timeout.
mssql_python/​pybind/​connection/​connection_pool.cpp Selects validation settings and replaces failed candidates directly.
mssql_python/​connection.py Passes autocommit into native checkout and documents timeout semantics.

Review effort: Balanced

Comment thread mssql_python/pybind/connection/connection.cpp
Read SQL_ATTR_CONNECTION_TIMEOUT through a private native accessor and check zero and nonzero values on the same reused SQL Server session. Mutation tests reject omitted restoration and unconditional zero.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 22, 2026 06:54

Copilot AI 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.

🔵 Needs a closer look

The reported native crash remains unexplained, and Windows/macOS validation is outstanding for these connection-lifecycle changes.

1 open issue

Review effort: Balanced

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

82%


🎯 Overall Coverage

85%


📈 Total Lines Covered: 9507 out of 11164
📁 Project: mssql-python


Diff Coverage

Diff: main...HEAD, staged and unstaged changes

  • mssql_python/connection.py (100%)
  • mssql_python/pybind/connection/connection.cpp (88.4%): Missing lines 941-943,950-951
  • mssql_python/pybind/connection/connection_pool.cpp (70.4%): Missing lines 190-193,273-276

Summary

  • Total: 74 lines
  • Missing: 13 lines
  • Coverage: 82%

mssql_python/pybind/connection/connection.cpp

Lines 937-947

  937 SQLULEN ConnectionHandle::getConnectionTimeout() const {
  938     if (!_conn) {
  939         ThrowStdException("Connection object is not initialized");
  940     }
! 941     SQLULEN timeout = 0;
! 942     const auto& handle = _conn->getDbcHandle();
! 943     SQLRETURN ret = SQLGetConnectAttr_ptr(handle->get(), SQL_ATTR_CONNECTION_TIMEOUT,
  944                                          &timeout, 0, nullptr);
  945     if (!SQL_SUCCEEDED(ret)) {
  946         ErrorInfo error = SQLCheckError_Wrap(SQL_HANDLE_DBC, handle, ret);
  947         ThrowStdException("Failed to read connection timeout: " + error.ddbcErrorMsg);

Lines 946-955

  946         ErrorInfo error = SQLCheckError_Wrap(SQL_HANDLE_DBC, handle, ret);
  947         ThrowStdException("Failed to read connection timeout: " + error.ddbcErrorMsg);
  948     }
  949     return timeout;
! 950 }
! 951 
  952 py::object ConnectionHandle::getInfo(SQLUSMALLINT infoType) const {
  953     PERF_TIMER("ConnectionHandle::getInfo");
  954     if (!_conn) {
  955         ThrowStdException("Connection object is not initialized");

mssql_python/pybind/connection/connection_pool.cpp

Lines 186-197

  186                     // expiry) before the threshold, so the safe-reuse path above
  187                     // is taken in practice. We favor never handing out a token
  188                     // that may expire mid-query over avoiding the churn.
  189                     candidate->setTokenExpiry(fresh_expiry);
! 190                     if (candidate->isAlive()) {
! 191                         validation_attempted = true;
! 192                         reuse_candidate = candidate->reset(validation_timeout, autocommit);
! 193                     }
  194                     if (!reuse_candidate) {
  195                         // The token is still valid but the socket is dead
  196                         // (isAlive()/reset() failed). We already minted the
  197                         // fresh attrs, so carry them forward and let Phase 3

Lines 269-280

  269                 ++_current_size;
  270                 needs_connect = true;
  271                 break;
  272             }
! 273             if (validation_attempted) {
! 274                 throw std::runtime_error(
! 275                     "ConnectionPool::acquire: pool size limit reached");
! 276             }
  277             // A concurrent acquire reserved the slot freed by token rotation.
  278             // Retry token comparison with another idle candidate.
  279         }
  280     }


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.performance_counter.hpp: 0.7%
mssql_python.pybind.logger_bridge.cpp: 57.9%
mssql_python.pybind.ddbc_bindings.h: 62.6%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.connection.connection_pool.cpp: 81.3%
mssql_python.pybind.connection.connection.cpp: 84.1%
mssql_python.logging.py: 86.2%
mssql_python.pooling.py: 90.1%
mssql_python.pybind.fetch_temporal.hpp: 92.1%
mssql_python.cursor.py: 92.5%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

Copilot AI balanced review requested due to automatic review settings October 22, 2026 08:54

Copilot AI 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.

🔵 Needs a closer look

The reported native crash remains unexplained, and Windows/macOS validation of the changed pooling lifecycle is outstanding.

No open issues 🎉

1 resolved since last review

Review effort: Balanced

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.

Reviewed the PR for correctness, security, reliability, performance, test coverage, repository conventions, and applicable architecture and design specifications. No actionable issues were identified. The implementation is consistent with repository standards and the applicable approved design requirements.

Recommendation: Approve

Comment thread mssql_python/pybind/connection/connection.cpp
Comment thread mssql_python/pybind/connection/connection_pool.cpp
Comment thread mssql_python/pybind/connection/connection_pool.cpp
@subrata-ms

Copy link
Copy Markdown
Contributor
  • Windows/macOS validation not run. An initial combined packet-drop run exited with code 139; isolated and repeated runs subsequently passed, but the initial crash was not conclusively explained.

Thanks for documenting the above validation results, including the initial exit-code-139 run (. Could you please share the exact scenario/command that triggered it and whether it reproduces under the same conditions? If it occurs again, a native backtrace or core dump would help identify the cause.

One additional recovery scenario worth covering is:

  • A pooled connection becomes unresponsive.
  • Checkout validation times out.
  • The driver attempts a replacement connection, but that login also fails—for example, because the server temporarily stops accepting new connections.

In this case, replacement creation throws before the normal discarded-connection cleanup loop, so cleanup relies on the exception/destructor path. Could we add coverage confirming that the caller receives a normal connection exception, the process does not crash, and pool capacity remains available for subsequent successful connections?

This scenario is not known to have caused the reported crash; it would strengthen coverage of the changed recovery path. I have not identified a memory-safety defect from source inspection.

Copilot AI balanced review requested due to automatic review settings October 28, 2026 04:56

Copilot AI 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.

🔵 Needs a closer look

The unexplained exit-139 crash during native packet-drop testing requires maintainer investigation and supported-platform validation, as isolated passing retries do not establish its cause or resolution.

No open issues 🎉

Review effort: Balanced

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
Copilot AI balanced review requested due to automatic review settings October 28, 2026 05:18
@github-actions github-actions Bot removed the pr-size: medium Moderate update size label Oct 28, 2026

Copilot AI 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.

🔵 Needs a closer look

The unexplained exit-139 crash during packet-drop testing and outstanding Windows/macOS validation require runtime investigation of native timeout and cleanup behavior that this source-only review cannot provide.

No open issues 🎉

Review effort: Balanced

@jahnvi480
Jahnvi Thakkar (jahnvi480) merged commit 21187de into main Oct 28, 2026
36 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: large Substantial code update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Pooled connection checkout has no timeout: connect() hangs ~16 min on a dead socket

4 participants