Skip to content

FEAT: Built-in configurable connection and transient-fault retry logic #682

Description

Summary

Add first-class, configurable retry logic to mssql-python so applications get transient-fault resiliency without hand-rolling their own retry loops. Today the only retry surface is the ODBC-level ConnectRetryCount/ConnectRetryInterval keywords, which only silently reconnect a dropped idle connection. They do not retry a connect() that fails transiently, nor a query that fails with a recoverable error (deadlock victim, lock/query timeout, or Azure SQL throttling such as 40197/40501/49918). Every app has to reimplement this, and most get the backoff, jitter, and "which errors are retriable" classification subtly wrong.

Motivation

  • Parity with the .NET Microsoft.Data.SqlClient configurable retry providers (SqlRetryLogicBaseProvider, SqlConnection.RetryLogicProvider / SqlCommand.RetryLogicProvider) and the Azure SDK retry-policy conventions.
  • Azure SQL Database and SQL Managed Instance routinely surface transient errors during failover, scaling, and throttling; robust handling is effectively required for production.
  • Reduces copy-pasted, error-prone retry loops and centralizes the transient-error taxonomy in the driver where it can be maintained authoritatively.

Proposed API (for discussion)

A retry policy object, attachable at the connection level and overridable per cursor/execute:

frommssql_pythonimportRetryPolicy, connectpolicy=RetryPolicy(
max_attempts=3, # total tries, not extra retriesbackoff="exponential", # "exponential" | "fixed" | "none"base_delay=1.0, # secondsmax_delay=30.0, # cap per waitjitter=True, # full jitter to avoid thundering herd# Retriable classification, defaulted by the driver but overridable:# connect-scope errors -> fresh connection; query-scope -> same connection
)
conn=connect(conn_str, retry_policy=policy) # applies to connect() + queriescursor=conn.cursor(retry_policy=policy) # optional overridecursor.execute(sql, *params, retry_policy=policy) # optional per-call override

Key behaviors:

  • Two scopes, because recovery differs: connection-establishment/connection-loss errors need a fresh connection; connection-surviving errors (deadlock, query timeout, throttling) retry on the same connection.
  • Driver-maintained default retriable set (transient SQLSTATEs / native error numbers), overridable by the caller.
  • Idempotency guardrail: by default only retry statements the caller marks safe, or document clearly that writes must be wrapped in an explicit transaction. Never silently replay a non-idempotent write.
  • Observability: emit standard logging records on each retry (attempt count, error, delay) and on final give-up.
  • No behavior change by default (opt-in), so existing code is unaffected.

Alternatives considered

  • ODBC ConnectRetryCount/ConnectRetryInterval — only covers idle-connection reconnect, not connect() or query retries.
  • Application-level helper loops — what everyone does now; duplicative and easy to get wrong (backoff, jitter, error classification, connect-vs-query scope).

Additional context

Docs currently ship a sample connect_with_retry / execute_with_retry pattern that demonstrates exactly this behavior and would map cleanly onto a built-in RetryPolicy.

Metadata

Metadata

Assignees

No one assigned

    Labels

    discussionenhancementNew feature or requestinADOtriage doneIssues that are triaged by dev team and are in investigation.up for grabs 🙌Issues that are ready to be picked up for anyone interested. Please self-assign and remove the label

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions