Skip to content

FIX: Context manager commits on clean exit, rolls back on exception#639

Merged
gargsaumya merged 7 commits into
mainfrom
bewithgaurav/fix-635-context-manager-commit
Jul 9, 2026
Merged

FIX: Context manager commits on clean exit, rolls back on exception#639
gargsaumya merged 7 commits into
mainfrom
bewithgaurav/fix-635-context-manager-commit

Conversation

@bewithgaurav

@bewithgaurav bewithgaurav commented Jun 22, 2026

Copy link
Copy Markdown
Collaborator

Work Item / Issue Reference

AB#45829

GitHub Issue: #635


Summary

Fixes the Connection context manager (__exit__) to implement commit-on-success / rollback-on-exception semantics, matching the documented behavior in the wiki, the DevBlogs post, and the docstrings.

Previously __exit__ only called close(), which always rolled back uncommitted changes regardless of whether the block exited cleanly or via exception. Now:

  • Clean exit + autocommit=False: commits the transaction
  • Exception + autocommit=False: rolls back the transaction
  • autocommit=True: no explicit commit/rollback (same as before)
  • Connection always closed on exit

close() is unchanged. Its internal rollback becomes a harmless no-op on an already-resolved transaction.

24 subprocess-isolated tests cover: commit/rollback semantics, autocommit toggling, manual commit/rollback inside block, DDL+DML, pending cursor results, doomed transactions (XACT_ABORT), KeyboardInterrupt, generator abandonment, killed SPID, 10k-row transactions, double-exit idempotency, and GC safety. Resource-intensive tests (rapid connection cycling, 10k inserts) are marked @pytest.mark.stress.

@github-actions github-actions Bot added the pr-size: large Substantial code update label Jun 22, 2026
Comment thread tests/test_024_context_manager_transaction.py Fixed
@bewithgaurav bewithgaurav force-pushed the bewithgaurav/fix-635-context-manager-commit branch from 7a7d0d5 to 9489153 Compare June 22, 2026 07:25
Comment thread mssql_python/connection.py
@github-actions

github-actions Bot commented Jun 22, 2026

Copy link
Copy Markdown

📊 Code Coverage Report

🔥 Diff Coverage

36%


🎯 Overall Coverage

80%


📈 Total Lines Covered: 6743 out of 8343
📁 Project: mssql-python


Diff Coverage

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

  • mssql_python/connection.py (36.8%): Missing lines 1723,1729-1733,1739-1741,1744-1746

Summary

  • Total: 19 lines
  • Missing: 12 lines
  • Coverage: 36%

mssql_python/connection.py

Lines 1719-1727

  1719         (rollback or close) are suppressed so the original user exception
  1720         propagates unchanged.
  1721         """
  1722         if self._closed:
! 1723             return
  1724         try:
  1725             if not self.autocommit:
  1726                 if exc_type is None:
  1727                     self.commit()

Lines 1725-1737

  1725             if not self.autocommit:
  1726                 if exc_type is None:
  1727                     self.commit()
  1728                 else:
! 1729                     self.rollback()
! 1730         except Exception:
! 1731             try:
! 1732                 self.close()
! 1733             except Exception:
  1734                 logger.warning(
  1735                     "Failed to close connection after failed "
  1736                     "commit/rollback in context manager.",
  1737                     exc_info=True,

Lines 1735-1750

  1735                     "Failed to close connection after failed "
  1736                     "commit/rollback in context manager.",
  1737                     exc_info=True,
  1738                 )
! 1739             if exc_type is None:
! 1740                 raise
! 1741             return
  1742         try:
  1743             self.close()
! 1744         except Exception:
! 1745             if exc_type is None:
! 1746                 raise
  1747             logger.warning(
  1748                 "Failed to close connection in context manager.",
  1749                 exc_info=True,
  1750             )


📋 Files Needing Attention

📉 Files with overall lowest coverage (click to expand)
mssql_python.pybind.logger_bridge.cpp: 59.2%
mssql_python.pybind.ddbc_bindings.h: 59.9%
mssql_python.pybind.logger_bridge.hpp: 70.8%
mssql_python.pybind.connection.connection.cpp: 76.2%
mssql_python.pybind.ddbc_bindings.cpp: 76.2%
mssql_python.__init__.py: 77.3%
mssql_python.row.py: 77.6%
mssql_python.ddbc_bindings.py: 79.6%
mssql_python.connection.py: 83.6%
mssql_python.logging.py: 85.5%

🔗 Quick Links

⚙️ Build Summary 📋 Coverage Details

View Azure DevOps Build

Browse Full Coverage Report

Fixes #635. The __exit__ implementation previously only called close(),
which always rolled back uncommitted changes. This contradicted the
documented behavior in the wiki, the DevBlogs post, and the docstrings.

The fix adds commit-on-success / rollback-on-exception semantics to
__exit__, matching the standard pattern used by sqlite3, psycopg2, and
other Python DB-API drivers. close() remains unchanged; its internal
rollback is a harmless no-op on an already-resolved transaction.

24 subprocess-isolated tests cover: clean commit, exception rollback,
autocommit modes, manual commit/rollback inside block, DDL+DML,
pending results, doomed transactions, KeyboardInterrupt, generator
abandonment, killed SPID, large transactions, and double-exit
idempotency.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@bewithgaurav bewithgaurav force-pushed the bewithgaurav/fix-635-context-manager-commit branch from 9489153 to b3c915a Compare June 22, 2026 07:41
@bewithgaurav bewithgaurav marked this pull request as ready for review June 22, 2026 08:33
Copilot AI review requested due to automatic review settings June 22, 2026 08:33

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.

Pull request overview

This pull request updates the Connection context manager behavior to match documented transaction semantics: commit on clean exit and rollback on exception when autocommit=False, while always closing the connection on exit.

Changes:

  • Implemented commit-on-success / rollback-on-exception logic in Connection.__exit__.
  • Added a new subprocess-isolated test suite to validate context manager transaction behavior and crash-safety edge cases.
  • Marked resource-intensive context-manager tests as @pytest.mark.stress (excluded by default via pytest.ini).

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 4 comments.

File Description
mssql_python/connection.py Updates Connection.__exit__ to commit/rollback based on exit condition and autocommit, then close the connection.
tests/test_024_context_manager_transaction.py Adds subprocess-based tests covering commit/rollback semantics, exception propagation, and various hardening scenarios.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread mssql_python/connection.py Outdated
Comment thread tests/test_024_context_manager_transaction.py Outdated
Comment thread tests/test_024_context_manager_transaction.py Outdated
Comment thread tests/test_024_context_manager_transaction.py Outdated
bewithgaurav and others added 3 commits June 22, 2026 14:32
…eoutcomments

- Wrap final self.close() in try/except so cleanup failures don't mask
  the user's original exception on the exception path
- Remove hardcoded fallback connection string from tests (tests now
  properly skip when DB_CONNECTION_STRING is unset)
- Replace signal.Signals._value2member_map_ (private API) with
  try/except ValueError for stable cross-version behavior
- Bump subprocess timeout to 120s for 10k-insert stress test

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Comment thread mssql_python/connection.py
Comment thread mssql_python/connection.py
sumitmsft
sumitmsft previously approved these changes Jul 6, 2026

@sumitmsft sumitmsft 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.

Approved with minor comments

Log warnings when close() fails on exception-exit paths so failures
aren't silently swallowed. Consistent with __del__ pattern.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@gargsaumya gargsaumya merged commit 2377277 into main Jul 9, 2026
29 checks passed
@gargsaumya gargsaumya mentioned this pull request Jul 10, 2026
bewithgaurav pushed a commit that referenced this pull request Jul 10, 2026
Release mssql-python v1.11.0.


[AB#46332](https://sqlclientdrivers.visualstudio.com/c6d89619-62de-46a0-8b46-70b92a84d85e/_workitems/edit/46332)

### Summary

Version bump to 1.11.0. Updates `mssql_python/__init__.py`, `setup.py`,
and `PyPI_Description.md`. Bundled `mssql_py_core` bumped from 0.1.5 to
0.1.6.

#### Bug Fixes

- **SSH-tunnel / in-process forwarder deadlock** — Release the GIL
around blocking ODBC round-trips in the teardown path and
`SQLDescribeParam`, so `close()` and parametrized queries with `None`
values no longer deadlock in-process TCP forwarder setups (#604, issue
#565).
- **BINARY/VARBINARY NULL parameters in temp tables and table
variables** — Proactively resolve unknown NULL parameter types before
binding and emit actionable `setinputsizes()` guidance on `SQL_VARCHAR`
fallback (#654, issue #627).
- **Context manager transaction semantics** — `Connection.__exit__` now
commits on clean exit and rolls back on exception when
`autocommit=False`, instead of always rolling back (#639, issue #635).
- **macOS Apple Silicon import failure** — `configure_dylibs.sh`
rewrites bundled ODBC dylib dependencies to `@loader_path` for every
architecture in the universal2 wheel, fixing `import mssql_python` on
clean Apple Silicon machines (#661, issue #656).
- **Service Principal bulk copy freeze** — Fixed a GIL-deadlock in the
Rust core that froze bulk copy under `ActiveDirectoryServicePrincipal`
auth; picked up via the `mssql_py_core` 0.1.6 bump (#666, issue #662).

#### Version Bump

- `mssql_python/__init__.py`: `__version__ = "1.11.0"`
- `setup.py`: `version="1.11.0"`
- `PyPI_Description.md`: `## What's new in v1.11.0` section refreshed.
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