Skip to content

TLS stream corruption under concurrent threaded reads on macOS arm64 #668

Description

@Torben1977

Summary

Concurrent threaded reads with Encrypt=yes can corrupt the TDS/TLS stream on macOS arm64. The issue reproduces with mssql-python 1.10.0 and also with pyodbc + Microsoft ODBC Driver 18, while the same workload is clean with Encrypt=no.

This does not appear to be a Python application pooling issue: the repro uses one private connection per thread, no SQLAlchemy pool, no shared connection/cursor objects, and synthetic constant data.

Environment

  • OS: macOS arm64, Apple Silicon M5 Pro
  • Python: 3.12
  • mssql-python: 1.10.0
  • pyodbc: 5.3.0, for comparison only
  • Microsoft ODBC Driver: msodbcsql18 18.6.2.1, for comparison only
  • unixODBC: 2.3.14, for comparison only
  • OpenSSL: 3.6.3
  • SQL Server: SQL Server 2025 RTM-CU6 in local Docker, x86_64 container under Rosetta
  • Connection: local localhost,1433, TrustServerCertificate=yes

Expected behavior

Two Python threads, each using its own independent database connection and cursor, should be able to fetch large result sets concurrently without corrupting returned row data or the protocol stream.

Actual behavior

With mssql-python and Encrypt=yes, repeated runs show intermittent failures and hangs. Examples include:

Driver Error: Communication link failure; DDBC Error: [Microsoft]The connection is no longer usable because the server response for a previously executed statement was incorrectly formatted.

In a small isolated process matrix using the same workload:

mssql-python Encrypt=yes: 4/10 clean, 4/10 corrupt, 2/10 timeout
mssql-python Encrypt=no:  10/10 clean, 0/10 corrupt, 0/10 timeout

The comparable pyodbc/msodbcsql18 repro shows the same pattern:

pyodbc + Encrypt=yes + threads: corrupt intermittently
pyodbc + Encrypt=no  + threads: clean
pyodbc + Encrypt=yes + multiprocessing: clean

Typical pyodbc comparison errors include:

[Microsoft][ODBC Driver 18 for SQL Server]Protocol error in TDS stream (0) (SQLGetData)
[Microsoft][ODBC Driver 18 for SQL Server]Unknown token received from SQL Server (0) (SQLGetData)
SSL Provider: wrong version number / record layer failure

Minimal mssql-python repro

from __future__ import annotations

import os
import sys
import threading

from mssql_python import connect

PWD = os.environ["DB_PASSWORD"]
CONN = (
    "Server=localhost,1433;"
    "Database=orglith;"
    f"UID=sa;PWD={PWD};"
    "Encrypt=yes;"
    "TrustServerCertificate=yes;"
)
Q = "SELECT TOP 200 REPLICATE(CAST('A' AS NVARCHAR(MAX)), 50000) FROM sys.all_objects a CROSS JOIN sys.all_objects b"

errors: list[tuple[str, str, str]] = []


def worker(who: str) -> None:
    try:
        with connect(CONN) as conn:
            for i in range(5):
                cur = conn.cursor()
                cur.execute(Q)
                rows = cur.fetchall()
                for n, row in enumerate(rows):
                    value = row[0]
                    if len(value) != 50000 or value.count("A") != 50000:
                        raise ValueError(f"[{who} it={i} row={n}] corrupt: len={len(value)} count={value.count('A')}")
                cur.close()
    except Exception as exc:
        errors.append((who, type(exc).__name__, str(exc)[:200]))


threads = [threading.Thread(target=worker, args=(f"t{i}",)) for i in range(2)]
for thread in threads:
    thread.start()
for thread in threads:
    thread.join()

print("RESULT:", f"CORRUPT {errors}" if errors else "CLEAN")
sys.exit(1 if errors else 0)

Run several times, or from a parent script with subprocess timeouts, because failures are intermittent.

Control observations

  • Changing only Encrypt=yes to Encrypt=no makes the workload clean in repeated runs.
  • Running the same logical workload in separate processes instead of threads is clean with TLS enabled.
  • A pure Python/OpenSSL AES-GCM stress test and a Python ssl TLS echo stress test are clean on the same machine.
  • FreeTDS/pymssql and pytds TLS comparison reads are clean against the same SQL Server.
  • Disabling pyodbc pooling does not explain the issue; the repro uses private connections and also reproduces in mssql-python.
  • MARS_Connection=Yes, PacketSize=8192, and OPENSSL_armcap=0 were not reliable fixes under larger samples.

Why I am filing this here

The issue reproduces with mssql-python 1.10.0, so it is not isolated to pyodbc or unixODBC. Because mssql-python uses DDBC and the Microsoft native SQL Server/TDS stack, this looks like a lower-level TLS/TDS stream integrity issue on this macOS arm64 path.

I have not yet confirmed whether x86_64 Linux is affected; current confirmation is macOS arm64 only.

Metadata

Metadata

Assignees

No one assigned

    Labels

    triage neededFor new issues, not triaged yet.

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions