Skip to content

SqlConnectionInternal starts a fresh ConnectTimeout for in-open round trips instead of honoring the caller's remaining timeout #4582

Description

@priyankatiwari08

Summary

Several call sites in SqlConnectionInternal issue a TDS round trip during connection open using a fresh ConnectionOptions.ConnectTimeout, rather than the TimeoutTimer already counting down for the in-flight Open(). Time already spent (pool wait, login) therefore does not count against the caller's budget, so a connection string with Connect Timeout=15 can block substantially longer than 15 seconds.

Raised by @mdaigle during review of #4335: #4335 (comment)

Affected call sites

All in src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/Connection/SqlConnectionInternal.cs:

Line Call
757 ChangeDatabase
2128 GetDTCAddress
2154 PropagateTransactionCookie
2419 ReassertSessionIsolationLevel (added by #4335)
2795 Transaction manager request

Each passes ConnectionOptions.ConnectTimeout. Downstream, TdsParser.TdsExecuteSQLBatch calls TdsParserStateObject.SetTimeoutSeconds, which restarts _timeoutTime from the current time.

Example

With Connect Timeout=15:

  • 10s waiting for a pooled connection
  • 4s login
  • the in-open batch then starts a new 15s budget

Worst case is roughly 29s against a documented 15s limit.

Why this is not a one-line fix

Activate(Transaction) is an abstract override on DbConnectionInternal with no timeout parameter. It is reached through DbConnectionInternal.ActivateConnection(Transaction) from both WaitHandleDbConnectionPool and ChannelDbConnectionPool. Threading a TimeoutTimer down requires changing the base signature and both pool implementations.

Existing precedent

SqlConnectionInternal.ResolveLoginTimeout already models exactly this choice for the login phase:

internal static TimeoutTimer ResolveLoginTimeout(TimeoutTimer callerTimeout, int connectTimeoutSeconds)
    => LocalAppContextSwitches.UseOverallConnectTimeoutForPoolWait
        ? callerTimeout
        : TimeoutTimer.StartNew(TimeSpan.FromSeconds(connectTimeoutSeconds));

Switch.Microsoft.Data.SqlClient.UseOverallConnectTimeoutForPoolWait defaults to false, i.e. the driver currently restarts the clock by default. Any fix here should decide whether these five call sites follow that switch or change unconditionally.

Suggested scope

  1. Thread the live TimeoutTimer through ActivateConnection / Activate and both pool implementations.
  2. Apply the change consistently across all five call sites.
  3. Decide switch-gated vs. unconditional, consistent with UseOverallConnectTimeoutForPoolWait.

Note: the connection pool implementations are under active rewrite, so this likely wants to sequence after that work.

Metadata

Metadata

Assignees

No one assigned

    Labels

    🆕 Triage NeededArea\EngineeringUse this for issues that are targeted for changes in the 'eng' folder or build systems.

    Type

    No type

    Projects

    Status
    To triage

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions