Skip to content

PERF: Optimize pooled connection return while preserving transaction safety - #820

Open
Sumit Sarabhai (sumitmsft) wants to merge 12 commits into
mainfrom
sumitmsft/revert-777-pooling-performance-20260925
Open

Sumit Sarabhai (sumitmsft) wants to merge 12 commits into
mainfrom
sumitmsft/revert-777-pooling-performance-20260925

Conversation

@sumitmsft

@sumitmsft Sumit Sarabhai (sumitmsft) commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

AB#48076

Summary

This PR now replaces the earlier revert with a targeted pooling optimization. It retains the transaction, failure-discard, pool-capacity and native handle-lifetime protections from #777.

  • Skip pool sanitation only when a prior successful rollback/autocommit restoration established clean state and no subsequent statement allocation or uncertain operation invalidated it. A new login or deferred reset alone never establishes clean state.
  • Retained statement aliases, raw-handle exposure and unknown connection attributes remain conservative. Valid scalar login timeouts such as timeout=30 do not permanently disable the fast path.
  • Move close-time transaction handling into native code and consolidate required sanitation into one autocommit probe, metadata invalidation and GIL-release scope. Never turn autocommit on after a failed rollback.
  • Preserve cleanup gates, discard/capacity recovery, pool-generation and shutdown protections. Extend the existing explicit-transaction integration cases and add deterministic native failure/call-count coverage.

Scope and limitations

Used or uncertain connections still execute full transaction sanitation. This does not assume autocommit=True means there cannot be an explicit SQL transaction, and it does not remove safety synchronization to improve timings. End-to-end performance improvements and live SQL behavior remain subject to validation; no blanket regression-elimination claim is made.

Validation

  • Windows x64 Release extension and native test executable built successfully.
  • 43 deterministic native sanitation cases passed; CTest passed.
  • 24 targeted Python tests passed using the freshly built extension.
  • Positive-timeout coverage verifies that timeout 30 reaches physical login and that 100 subsequent unused pooled leases issue no sanitation calls.
  • Three expanded live-SQL integration cases collected successfully but were not executed locally. Normal cross-platform PR validation and performance evaluation are pending.

This reverts commit 2a86fc1 while preserving subsequent result-metadata changes. Restores the prior pooling behavior; the pooled-transaction correctness issue fixed by #777 will need a replacement fix.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings September 25, 2026 16:30
@github-actions github-actions Bot added the pr-size: large Substantial code update label Sep 25, 2026
@github-actions

github-actions Bot commented Sep 25, 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

SELECT queries: ddbc::SQLBindColums +0.014 ms; ddbc::FetchBatchData::SQLFetchScroll_call +0.002 ms; py::fetchall::row_wrap +0.001 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Fetch-all queries: ddbc::SQLBindColums +0.001 ms; ddbc::SQLNumResultCols_wrap +0.000 ms; ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Row-by-row fetching: ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Batched row fetching: ddbc::FetchBatchData +1.454 ms; ddbc::FetchMany_wrap +1.439 ms; ddbc::FetchBatchData::SQLFetchScroll_call +1.092 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Row fetching in batches of 100: ddbc::FetchBatchData::SQLFetchScroll_call +1.277 ms; py::fetchmany::cpp_call +1.060 ms; ddbc::FetchBatchData +0.926 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Row fetching in batches of 10,000: ddbc::SQLBindColums +8.981 ms; ddbc::FetchMany_wrap +6.985 ms; py::fetchmany::cpp_call +6.591 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Joined aggregation queries: ddbc::SQLExecDirect_wrap +0.599 ms; py::execute::cpp_call +0.598 ms; ddbc::FetchBatchData::construct_rows +0.013 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Large joined-result fetching: py::fetchall::row_wrap +0.394 ms; ddbc::FetchBatchData::construct_rows +0.100 ms; ddbc::SQLBindColums +0.001 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
1.2-million-row fetching: py::fetchall::row_wrap +12.030 ms; ddbc::FetchBatchData::SQLFetchScroll_call +9.984 ms; py::execute::cpp_call +0.067 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Common table expression queries: py::execute::cpp_call +0.037 ms; ddbc::SQLExecDirect_wrap +0.035 ms; ddbc::SQLBindColums +0.001 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
256 KiB VARCHAR(MAX) / fetchall(): py::fetchall::row_wrap +0.001 ms; ddbc::SQLNumResultCols_wrap +0.000 ms; ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
10,000 scalar values / fetchval() (debug disabled): py::fetchone::cpp_call +0.968 ms; ddbc::FetchOne_wrap +0.581 ms; ddbc::SQLGetData_wrap +0.531 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).

Unix / SQL Server 2025

SELECT queries: ddbc::SQLExecDirect_wrap +0.025 ms; py::execute::cpp_call +0.025 ms; ddbc::SQLBindColums +0.004 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Fetch-all queries: ddbc::FetchBatchData::SQLFetchScroll_call +0.921 ms; ddbc::FetchBatchData +0.362 ms; ddbc::FetchAll_wrap +0.356 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Row-by-row fetching: py::fetchone::row_wrap +0.009 ms; ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Batched row fetching: py::fetchmany::row_wrap +0.072 ms; ddbc::SQLDescribeCol::driver_call +0.000 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Row fetching in batches of 100: ddbc::FetchMany_wrap +0.920 ms; ddbc::FetchBatchData +0.889 ms; py::fetchmany::cpp_call +0.852 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Row fetching in batches of 10,000: ddbc::FetchBatchData::SQLFetchScroll_call +0.771 ms; ddbc::FetchBatchData::construct_rows +0.675 ms; ddbc::SQLBindColums +0.257 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Joined aggregation queries: ddbc::SQLExecDirect_wrap +0.063 ms; py::execute::cpp_call +0.063 ms; ddbc::SQLNumResultCols_wrap +0.000 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).
Large joined-result fetching: ddbc::FetchBatchData::SQLFetchScroll_call +0.149 ms; py::execute::param_prep +0.000 ms; ddbc::SQLNumResultCols_wrap +0.000 ms. Call changes: ddbc::AppendDiagRecords::SQLGetDiagRec_call (added, removed, or intermittent).

4 additional diagnostic rows are available in the raw ADO artifacts.

All database tasks and timings

Unix / SQL Server 2022

Database task Before After Paired change Result
Connection opening 9.973 ms 9.982 ms -0.6% no signal
SELECT queries 1.033 ms 0.958 ms -7.2% no signal
Row insertion 34.119 ms 34.244 ms -0.3% no signal
Executemany inserts 155.125 ms 154.647 ms +0.5% no signal
Fetch-all queries 130.217 ms 119.305 ms -8.4% no signal
Row-by-row fetching 14.885 ms 14.329 ms -1.2% no signal
Batched row fetching 115.785 ms 116.494 ms +0.6% no signal
Transaction commit and rollback 113.026 ms 112.285 ms +0.5% no signal
Arrow row fetching 93.582 ms 94.284 ms +0.5% no signal
100,000-row insertion 438.554 ms 438.101 ms -0.4% no signal
Row fetching in batches of 100 120.023 ms 120.963 ms +0.7% no signal
Row fetching in batches of 10,000 128.126 ms 133.360 ms +4.1% no signal
Repeated positional queries 33.991 ms 33.319 ms -2.0% no signal
Repeated named-parameter queries 35.961 ms 36.008 ms +0.8% no signal
Legacy 100,000-row insertion 375.195 ms 344.430 ms -7.6% no signal
Insertion with explicit input sizes 481.275 ms 481.544 ms -0.3% no signal
Joined aggregation queries 177.456 ms 177.875 ms +0.2% no signal
Large joined-result fetching 177.716 ms 177.035 ms -0.1% no signal
1.2-million-row fetching 3444.903 ms 3448.869 ms +0.2% no signal
Common table expression queries 5.247 ms 5.178 ms -1.0% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.301 ms 1.163 ms -8.0% no signal
10,000 scalar values / fetchval() (debug disabled) 107.303 ms 110.224 ms +2.7% no signal

Unix / SQL Server 2025

Database task Before After Paired change Result
Connection opening 96.569 ms 97.312 ms +0.8% no signal
SELECT queries 1.110 ms 1.024 ms -6.4% no signal
Row insertion 34.887 ms 34.759 ms -0.7% no signal
Executemany inserts 155.600 ms 154.545 ms -3.5% no signal
Fetch-all queries 121.984 ms 122.162 ms +0.3% no signal
Row-by-row fetching 14.619 ms 14.273 ms -1.8% no signal
Batched row fetching 119.955 ms 118.329 ms -1.5% no signal
Transaction commit and rollback 115.788 ms 115.992 ms -0.1% no signal
Arrow row fetching 104.833 ms 95.401 ms -9.0% no signal
100,000-row insertion 467.663 ms 445.157 ms -3.0% no signal
Row fetching in batches of 100 122.074 ms 123.195 ms -2.0% no signal
Row fetching in batches of 10,000 143.720 ms 137.284 ms -4.5% no signal
Repeated positional queries 34.007 ms 34.749 ms +3.2% no signal
Repeated named-parameter queries 36.548 ms 42.986 ms +19.8% no signal
Legacy 100,000-row insertion 356.533 ms 344.115 ms -3.0% no signal
Insertion with explicit input sizes 483.382 ms 485.650 ms +0.9% no signal
Joined aggregation queries 161.009 ms 160.998 ms +0.3% no signal
Large joined-result fetching 195.507 ms 186.298 ms -4.5% no signal
1.2-million-row fetching 3559.486 ms 3547.149 ms -0.0% no signal
Common table expression queries 5.243 ms 5.083 ms -0.4% no signal
256 KiB VARCHAR(MAX) / fetchall() 1.563 ms 1.432 ms -8.4% no signal
10,000 scalar values / fetchval() (debug disabled) 110.541 ms 107.894 ms -3.5% no signal
Build and measurement details

ADO build 182475

PR head: 04e13fdaefe6481a4cd8eb769399b0e6fd6457eb
Base: d7a4a9928d00ebd88fcfae5a3fdd4dc5c5ae7d46
Measured merge: 6bedf883f3aafcd979c2f42c981a1c1b8533d9c7

  • Unix / SQL Server 2022: Python 3.12.3, x86_64, SQL 16.0.4295.3; 5 paired comparisons and 1 warmup.
  • Unix / SQL Server 2025: Python 3.12.3, x86_64, SQL 17.0.5005.3; 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.

Copilot review overview

🟡 Changes recommended

Unresolved critical cleanup, destructor, handle-lifecycle, and disconnect-synchronization issues block approval.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 4 High severity · 1 Medium severity

Open (5)
What changed in this PR

Reverts PR #777 to restore prior pooled-connection behavior while retaining result-metadata invalidation.

Changes:

  • Removes pooled transaction sanitization and related regression tests.
  • Restores previous native connection, handle cleanup, and pool-return behavior.
  • Removes the reverted changelog entry.
File Summary
tests/​test_009_pooling.py Removes pooling and native lifecycle regression tests.
tests/​test_006_exceptions.py Removes close-failure tests.
mssql_python/​pybind/​ddbc_bindings.h Removes cleanup-state APIs.
mssql_python/​pybind/​ddbc_bindings.cpp Restores earlier handle-management behavior.
mssql_python/​pybind/​connection/​connection.h Removes pool-sanitation interfaces.
mssql_python/​pybind/​connection/​connection.cpp Restores prior disconnect and pool-return behavior.
mssql_python/​pybind/​connection/​connection_pool.h Removes discard/origin-pool APIs.
mssql_python/​pybind/​connection/​connection_pool.cpp Restores key-based pool returns.
mssql_python/​connection.py Restores the previous close flow.
CHANGELOG.md Removes the reverted fix entry.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread mssql_python/connection.py Outdated
Comment thread mssql_python/pybind/connection/connection.cpp Outdated
Comment thread mssql_python/pybind/connection/connection.cpp Outdated
Comment thread mssql_python/pybind/ddbc_bindings.cpp Outdated
Comment thread mssql_python/pybind/connection/connection_pool.cpp Outdated
@github-actions

github-actions Bot commented Sep 25, 2026 •

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

80%


🎯 Overall Coverage

84%


📈 Total Lines Covered: 9551 out of 11238
📁 Project: mssql-python


Diff Coverage

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

  • mssql_python/connection.py (100%)
  • mssql_python/pybind/connection/connection.cpp (80.9%): Missing lines 291,310,331,354-355,375-376,482-486,490-491,658,711-712,723,775-780,804-805,952,1009,1011-1012
  • mssql_python/pybind/ddbc_bindings.cpp (80.0%): Missing lines 1593-1595,1678

Summary

  • Total: 178 lines
  • Missing: 34 lines
  • Coverage: 80%

mssql_python/pybind/connection/connection.cpp

Lines 287-295

  287 
  288 void Connection::commit() {
  289     PERF_TIMER("Connection::commit");
  290     _poolClean = false;
! 291     _poolSessionReset = false;
  292     if (!_dbcHandle) {
  293         ThrowStdException("Connection handle not allocated");
  294     }
  295     updateLastUsed();

Lines 306-314

  306 
  307 void Connection::rollback() {
  308     PERF_TIMER("Connection::rollback");
  309     _poolClean = false;
! 310     _poolSessionReset = false;
  311     if (!_dbcHandle) {
  312         ThrowStdException("Connection handle not allocated");
  313     }
  314     updateLastUsed();

Lines 327-335

  327     PERF_TIMER("Connection::setAutocommit");
  328     if (!enable) {
  329         _poolClean = false;
  330         _poolSessionReset = false;
! 331     }
  332     if (!_dbcHandle) {
  333         ThrowStdException("Connection handle not allocated");
  334     }
  335     clearResultMetadata();

Lines 350-359

  350         py::gil_scoped_release release;
  351         ret = SQLSetConnectAttr_ptr(_dbcHandle->get(), SQL_ATTR_AUTOCOMMIT,
  352                                     reinterpret_cast<SQLPOINTER>(static_cast<SQLULEN>(value)), 0);
  353     }
! 354     if (!SQL_SUCCEEDED(ret)) {
! 355         _poolClean = false;
  356         checkError(ret);
  357     }
  358     if (value == SQL_AUTOCOMMIT_ON) {
  359         LOG("Autocommit enabled");

Lines 371-380

  371     SQLINTEGER value;
  372     SQLINTEGER string_length;
  373     SQLRETURN ret = SQLGetConnectAttr_ptr(_dbcHandle->get(), SQL_ATTR_AUTOCOMMIT, &value,
  374                                           sizeof(value), &string_length);
! 375     if (!SQL_SUCCEEDED(ret)) {
! 376         _poolClean = false;
  377         checkError(ret);
  378     }
  379     return value == SQL_AUTOCOMMIT_ON;
  380 }

Lines 478-495

  478     }
  479 
  480     if (py::isinstance<py::int_>(value)) {
  481         // Get the integer value
! 482         int64_t longValue;
! 483         try {
! 484             longValue = value.cast<int64_t>();
! 485         } catch (const py::cast_error&) {
! 486             _poolProofDisabled = true;
  487             throw;
  488         } catch (const py::error_already_set&) {
  489             _poolProofDisabled = true;
! 490             throw;
! 491         }
  492         if (scalarLoginTimeout &&
  493             (longValue < 0 ||
  494              static_cast<uint64_t>(longValue) > std::numeric_limits<SQLUINTEGER>::max())) {
  495             _poolProofDisabled = true;

Lines 654-662

  654 bool Connection::reset() {
  655     if (!_dbcHandle) {
  656         ThrowStdException("Connection handle not allocated");
  657     }
! 658     if (_cleanupState->childFreeFailed) {
  659         _poolClean = false;
  660         _poolSessionReset = false;
  661         LOG("Cannot reuse connection after a statement handle failed to free");
  662         return false;

Lines 707-716

  707         LOG("Failed to reset transaction isolation level (ret=%d). Marking as dead.", ret);
  708         return false;
  709     }
  710 
! 711     // A retained child can finish cleanup while either attribute call releases the GIL.
! 712     if (_cleanupState->childFreeFailed) {
  713         _poolClean = false;
  714         _poolSessionReset = false;
  715         LOG("Cannot reuse connection after a statement handle failed to free");
  716         return false;

Lines 719-727

  719     return true;
  720 }
  721 
  722 void Connection::prepareForPool() {
! 723     PERF_TIMER("Connection::prepareForPool");
  724     if (!_dbcHandle) {
  725         ThrowStdException("Connection handle not allocated");
  726     }
  727     if (_cleanupState->childFreeFailed) {

Lines 771-784

  771                                             reinterpretU16stringAsSqlWChar(rollbackQuery), SQL_NTS);
  772                     if (!SQL_SUCCEEDED(ret)) {
  773                         ErrorInfo error = SQLReadError(SQL_HANDLE_STMT, statement, ret);
  774                         statementError = error.sqlState.length() == 5
! 775                             ? "SQLSTATE:" + error.sqlState + ":" + error.ddbcErrorMsg
! 776                             : error.ddbcErrorMsg;
! 777                     }
! 778                     SQLRETURN freeRet = SQLFreeHandle_ptr(SQL_HANDLE_STMT, statement);
! 779                     if (SQL_SUCCEEDED(ret) && !SQL_SUCCEEDED(freeRet)) {
! 780                         ErrorInfo error = SQLReadError(SQL_HANDLE_STMT, statement, freeRet);
  781                         statementError = error.sqlState.length() == 5
  782                             ? "SQLSTATE:" + error.sqlState + ":" + error.ddbcErrorMsg
  783                             : error.ddbcErrorMsg;
  784                         ret = freeRet;

Lines 800-809

  800     }
  801     if (!statementError.empty()) {
  802         ThrowStdException(statementError);
  803     }
! 804     checkError(ret);
! 805     // A weak_ptr expires before its destructor finishes. Read the count before
  806     // the failure latch so a completed destructor cannot hide a failed native free.
  807     const bool noChildren = _cleanupState->childStatementCount == 0;
  808     if (_cleanupState->childFreeFailed) {
  809         ThrowStdException("Cannot pool connection after a statement handle failed to free");

Lines 948-956

  948         // SQLDisconnect failure using the existing connection and children.
  949         if (!_usePool && !rollbackBeforeDisconnect) {
  950             throw;
  951         }
! 952         // Never retain a connection whose transaction state could not be
  953         // sanitized. Release capacity and preserve the original cleanup error.
  954         try {
  955             ConnectionPoolManager::getInstance().discardConnection(_originPool, _conn);
  956         } catch (...) {

Lines 1005-1016

  1005     return conn->allocStatementHandle();
  1006 }
  1007 
  1008 py::object Connection::getInfo(SQLUSMALLINT infoType) const {
! 1009     _poolClean = false;
  1010     _poolSessionReset = false;
! 1011     if (infoType == SQL_DRIVER_HDBC || infoType == SQL_DRIVER_HENV ||
! 1012         infoType == SQL_DRIVER_HSTMT || infoType == SQL_DRIVER_HLIB) {
  1013         _poolProofDisabled = true;
  1014     }
  1015     if (!_dbcHandle) {
  1016         ThrowStdException("Connection handle not allocated");

mssql_python/pybind/ddbc_bindings.cpp

Lines 1589-1599

  1589 
  1590 // SqlHandle definition
  1591 SqlHandle::SqlHandle(SQLSMALLINT type, SQLHANDLE rawHandle,
  1592                      std::shared_ptr<ConnectionCleanupState> cleanupState)
! 1593     : _type(type), _handle(rawHandle), _cleanupState(std::move(cleanupState)) {
! 1594     if (_type == SQL_HANDLE_STMT && _cleanupState) {
! 1595         ++_cleanupState->childStatementCount;
  1596     }
  1597 }
  1598 
  1599 SqlHandle::~SqlHandle() {

Lines 1674-1682

  1674         if (_implicitly_freed || (_cleanupState && _cleanupState->disconnected)) {
  1675             _handle = nullptr;
  1676             return SQL_SUCCESS;
  1677         }
! 1678         SQLRETURN ret = SQLFreeHandle_ptr ? SQLFreeHandle_ptr(_type, _handle) : SQL_INVALID_HANDLE;
  1679         if (SQL_SUCCEEDED(ret)) {
  1680             _handle = nullptr;
  1681         } else if (_type == SQL_HANDLE_STMT && _cleanupState) {
  1682             _cleanupState->childFreeFailed = true;


📋 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.cpp: 81.4%
mssql_python.pybind.connection.connection_pool.cpp: 82.3%
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

Replace the PR 777 revert with conservative clean-state tracking and consolidated native sanitation. Used or uncertain connections still roll back before parking; only previously sanitized unused connections can skip cleanup. Preserve scalar login timeouts without trusting arbitrary attributes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 25, 2026 19:11
@sumitmsft Sumit Sarabhai (sumitmsft) changed the title PERF: Revert pooled connection cleanup from PR #777 PERF: Optimize pooled connection return while preserving transaction safety Sep 25, 2026
Comment thread tests/native/pool_sanitation.cpp Fixed
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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.

Copilot review overview

🟡 Changes recommended

Native tests are not part of standard validation, and exception tests no longer cover the failure paths their names describe.

Get a fresh assessment by requesting another Copilot review.

Review effort: Lite
Findings: 2 Medium severity

Open (2)
Resolved since last review (5)

Comment thread mssql_python/pybind/CMakeLists.txt
Comment thread tests/test_006_exceptions.py
Copilot AI review requested due to automatic review settings September 25, 2026 19:18

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.

Copilot review overview

🔵 Needs a closer look

Enable the native sanitation regression suite in normal CI.

Review effort: Lite
Findings: 2 Medium severity

Open (2)

Replace redundant autocommit transitions with a reset-triggered rollback batch and preserve proof so checkout can skip duplicate session reset work.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings September 26, 2026 09:39

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.

Copilot review overview

🔵 Needs a closer look

Address the manual-lease state bug, restore Python-boundary failure coverage, and run native sanitation tests in CI.

Review effort: Lite
Findings: 2 Medium severity

Open (2)
Previously missed (1)

In code that hasn't changed since last review

Low severity Autocommit read failure coverage was removed

tests/​test_006_exceptions.py:304

This replacement likewise removes the simulated get_autocommit failure, so the test named test_close_cleans_up_after_autocommit_read_failure no longer covers that error path. Because the probe now runs inside native close, add a Python-boundary test that can inject/observe a native probe failure, or rename this test so the suite does not falsely claim coverage it no longer provides.

Preserve native sanitation proof and original diagnostic enumeration on uncertain header results. Add deterministic ODBC-call and diagnostic-preservation regression coverage.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings October 7, 2026 06:40

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.

Copilot review overview

🟡 Changes recommended

Unresolved raw-handle cleanup and validation/test consistency issues must be addressed.

Review effort: Lite
Findings: 1 High severity · 2 Medium severity

Open (3)

Comment thread mssql_python/pybind/connection/connection.cpp
Teach the mixed-diagnostic interceptor the SQL_DIAG_NUMBER header contract while retaining SQLSTATE and message assertions. Run the existing native sanitation CTest target as a blocking job in standard PR validation.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings October 9, 2026 10:37

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

Moderate cleanup ownership and session-reset state issues remain unresolved.

3 open findings

🧠 Review effort: Lite

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.

🟡 Changes recommended

Set _poolSessionReset after successful reset() sanitation to avoid repeated checkout cleanup.

4 open findings

🧠 Review effort: Lite

Comment thread mssql_python/pybind/connection/connection.cpp
Replace the test-only raw copy with a validated portable byte copy and cover short, null, and unaligned buffers. No production driver changes or alert suppressions.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

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

A critical native handle-lifetime cleanup issue remains unresolved, with broader validation still pending.

1 open finding
4 resolved since last review

🧠 Review effort: Lite

Comment thread mssql_python/pybind/connection/connection.cpp Outdated
Copilot AI lite review requested due to automatic review settings October 12, 2026 08:49

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

Unresolved native handle-lifetime and rollback-failure coverage concerns require changes and human review.

1 open finding

🧠 Review effort: Lite

Preserve failed statement frees beyond wrapper destruction and reject affected connections on check-in and checkout. Count child lifetimes through cleanup completion before enabling the clean fast path. Cover orphan reclamation, retained aliases, cleanup races, and pool capacity recovery.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI lite review requested due to automatic review settings October 12, 2026 11:01

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

A moderate failure-path coverage gap remains, diagnostic behavior needs documentation and validation, and broader validation is pending.

0 open findings

1 resolved since last review

🧠 Review effort: Lite

Copilot AI lite review requested due to automatic review settings October 13, 2026 04:47

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

Native rollback and autocommit-read failure coverage remain unresolved, with broader validation still pending.

0 open findings

🧠 Review effort: Lite

Comment thread eng/pipelines/pr-validation-pipeline.yml
Copilot AI lite review requested due to automatic review settings October 13, 2026 10:25

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

A moderate temporary-handle cleanup issue and misleading failure-test naming remain unresolved.

0 open findings

🧠 Review effort: Lite

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.

6 participants