Skip to content

LoginWithFailover missing parser state check causes transient errors to trigger failover instead of retry #4139

@paulmedynski

Description

@paulmedynski

Description

LoginWithFailover() in SqlConnectionInternal.cs is missing a parser state check that LoginNoFailover() already has. This causes transient errors (e.g. 40613, 42108, 42109) to be treated as network errors during failover scenarios, triggering failover alternation instead of throwing immediately so the outer ConnectRetryCount loop can handle retries.

Example error here: https://sqlclientdrivers.visualstudio.com/public/_build/results?buildId=146283&view=logs&j=4ad43083-dc29-517d-32db-fc6c44c7aa78&t=65e01f4b-b2d9-5e27-3295-390e31b6ce41

Root Cause

When the server sends a transient error (an explicit TDS error token), the TDS parser remains open (State != Closed). In LoginNoFailover(), there is a check:

if (_parser?.State is not TdsParserState.Closed ||
    IsDoNotRetryConnectError(sqlex) ||
    timeout.IsExpired)
{
    throw;
}

This correctly distinguishes login-phase errors (parser open) from network errors (parser closed). However, LoginWithFailover() is missing this check, so login-phase transient errors fall through and trigger the failover alternation logic.

The code comment in LoginWithFailover() even notes: "The logic in this method is paralleled by the logic in LoginNoFailover. Changes to either one should be examined to see if they need to be reflected in the other."

Impact

  • With ConnectRetryCount = 0 and a user-provided failover partner, transient errors cause connection.Open() to succeed (via failover) when it should fail
  • The unit test TransientFault_WithUserProvidedPartner_RetryDisabled_ShouldFail fails because the connection succeeds instead of throwing

Fix

Add the same _parser?.State is not TdsParserState.Closed check to LoginWithFailover() catch block. This ensures:

  • Login-phase errors (transient errors, explicit server rejections): throw immediately, handled by outer retry loop
  • Network errors (parser closed): continue failover alternation as before

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Status

    In progress

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions