Skip to content

FIX: Release GIL around blocking SQLSetConnectAttr calls (#565)#568

Draft
saurabh500 wants to merge 2 commits into
mainfrom
dev/saurabh/fix-565-gil-release-conn-attrs
Draft

FIX: Release GIL around blocking SQLSetConnectAttr calls (#565)#568
saurabh500 wants to merge 2 commits into
mainfrom
dev/saurabh/fix-565-gil-release-conn-attrs

Conversation

@saurabh500
Copy link
Copy Markdown
Contributor

@saurabh500 saurabh500 commented May 11, 2026

Work Item / Issue Reference

GitHub Issue: #565


Summary

Fixes the indefinite hang of mssql_python.connect() when the target server is reached through an in-process SSH tunnel created with paramiko + sshtunnel (issue #565, originally reported as #491).

Root cause

PR #541 released the GIL around SQLDriverConnect, SQLDisconnect, and SQLEndTran, but a handful of SQLSetConnectAttr call sites in mssql_python/pybind/connection/connection.cpp were left holding the GIL:

  • Connection::setAutocommitSQL_ATTR_AUTOCOMMIT. This is the call that hangs in the issue's repro: it runs by default immediately after every connect().
  • Connection::setAttribute (int / wide-string / bytes paths) — user-facing set_attr().
  • Connection::resetSQL_ATTR_RESET_CONNECTION and SQL_ATTR_TXN_ISOLATION, called by the connection pool on acquire.

When the GIL is held during a call that ends up doing blocking work, paramiko's transport thread cannot run and so cannot forward bytes through the SSH channel — the driver waits forever for a response that never comes. pyodbc does not exhibit this because it releases the GIL around its blocking ODBC calls.

Fix

Wrap each of these call sites in py::gil_scoped_release, matching the pattern PR #541 already established for SQLDriverConnect / SQLEndTran. Local-only attribute reads (SQLGetConnectAttr for SQL_ATTR_CONNECTION_DEAD / SQL_ATTR_AUTOCOMMIT, SQLGetInfo, SQLAllocHandle) are intentionally left alone.

Verification

Reproduced #565 locally with a loopback sshd and the exact paramiko==3.5.1 / sshtunnel==0.4.0 setup from the issue. Before the fix, mssql_python.connect() hung indefinitely (killed at the 30s timeout); pyodbc.connect() succeeded in ~0.4s through the same tunnel. After the fix, mssql_python.connect() returns in ~0.31s and the subsequent SELECT round-trips successfully.

Diff: 1 file changed, +42 / -12 in mssql_python/pybind/connection/connection.cpp.

PR #541 released the GIL around SQLDriverConnect / SQLDisconnect /
SQLEndTran, but several SQLSetConnectAttr sites in connection.cpp
were left holding the GIL:

  - Connection::setAutocommit (SQL_ATTR_AUTOCOMMIT)
  - Connection::setAttribute (user-facing set_attr)
  - Connection::reset (SQL_ATTR_RESET_CONNECTION,
    SQL_ATTR_TXN_ISOLATION) — called by the pool on acquire.

setAutocommit is the call that hangs in the issue #565 repro: it
runs by default immediately after connect, and when the network
path goes through another Python thread (e.g. an in-process SSH
tunnel via paramiko + sshtunnel), that thread cannot make progress
while the GIL is held — deadlock.

Wrap each of these calls with py::gil_scoped_release, matching the
pattern PR #541 introduced for the other ODBC entry points.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@saurabh500 saurabh500 force-pushed the dev/saurabh/fix-565-gil-release-conn-attrs branch from 74814ca to 204849c Compare May 11, 2026 17:45
@github-actions github-actions Bot added pr-size: small Minimal code update pr-size: medium Moderate update size labels May 11, 2026
Comment thread tests/test_023_ssh_tunnel_gil_release.py Fixed
@saurabh500 saurabh500 force-pushed the dev/saurabh/fix-565-gil-release-conn-attrs branch from 827eea5 to 92a568b Compare May 11, 2026 18:35
@github-actions github-actions Bot removed the pr-size: small Minimal code update label May 11, 2026
Routes mssql_python.connect()+SELECT through a pure-Python TCP
forwarder running in a subprocess, with a hard external watchdog
(Popen.communicate(timeout=...)).

The forwarder's _pipe loop calls dst.sendall(...) on every chunk;
that bound-method dispatch needs the GIL, which is exactly the
deadlock condition from issue #565: with the GIL held across
SQLSetConnectAttr(SQL_ATTR_AUTOCOMMIT), the forwarder thread
cannot run, the driver waits forever for a reply, and the
subprocess hangs.

Verified that the test fails (in 30s, with the issue-#565 diagnostic
message) against the unfixed binary on main, and passes (~0.2s)
once the GIL release on setAutocommit/setAttribute/reset is in place.

Linux-only and runs in the default functional suite (not stress).
A subprocess + external watchdog is required because once the
worker thread deadlocks while holding the GIL, the entire Python
interpreter is starved — an in-process watchdog thread cannot
make progress either.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@saurabh500 saurabh500 force-pushed the dev/saurabh/fix-565-gil-release-conn-attrs branch from 92a568b to 0f31307 Compare May 11, 2026 18:42
Comment thread tests/test_023_ssh_tunnel_gil_release.py Dismissed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr-size: medium Moderate update size

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants