Skip to content

SqlConnection.Open raises wrongly "error occurred during the pre-login handshake" due to thread starvation #3118

Description

@dmitriyse

Describe the bug

SqlConnection.Open raises:
A connection was successfully established with the server, but then an error occurred during the pre-login handshake

When the process experiences thread starvation.

Exception message:

Unhandled exception. Microsoft.Data.SqlClient.SqlException (0x80131904): A connection was successfully established with the server, but then an error occurred during the pre-login handshake. (provider: TCP Provider, error: 0 - Success)

Stack trace:
   at Microsoft.Data.SqlClient.SqlInternalConnection.OnError(SqlException exception, Boolean breakConnection, Action`1 wrapCloseInAction)
   at Microsoft.Data.SqlClient.TdsParser.ThrowExceptionAndWarning(TdsParserStateObject stateObj, SqlCommand command, Boolean callerHasConnectionLock, Boolean asyncClose)
   at Microsoft.Data.SqlClient.TdsParserStateObject.ThrowExceptionAndWarning(Boolean callerHasConnectionLock, Boolean asyncClose)
   at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniError(TdsParserStateObject stateObj, UInt32 error)
   at Microsoft.Data.SqlClient.TdsParserStateObject.ReadSniSyncOverAsync()
   at Microsoft.Data.SqlClient.TdsParserStateObject.TryReadNetworkPacket()
   at Microsoft.Data.SqlClient.TdsParser.ConsumePreLoginHandshake(SqlConnectionEncryptOption encrypt, Boolean trustServerCert, Boolean integratedSecurity, Boolean& marsCapable, Boolean& fedAuthRequired, Boolean tlsFirst, String serverCert)
   at Microsoft.Data.SqlClient.TdsParser.Connect(ServerInfo serverInfo, SqlInternalConnectionTds connHandler, TimeoutTimer timeout, SqlConnectionString connectionOptions, Boolean withFailover)
   at Microsoft.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, TimeoutTimer timeout, Boolean withFailover)
   at Microsoft.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(ServerInfo serverInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString connectionOptions, SqlCredential credential, TimeoutTimer timeout)
   at Microsoft.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(TimeoutTimer timeout, SqlConnectionString connectionOptions, SqlCredential credential, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance)
   at Microsoft.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandli
ng, String accessToken, DbConnectionPool pool, Func`3 accessTokenCallback)
   at Microsoft.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
   at Microsoft.Data.ProviderBase.DbConnectionFactory.CreateNonPooledConnection(DbConnection owningConnection, DbConnectionPoolGroup poolGroup, DbConnectionOptions userOptions)
   at Microsoft.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at Microsoft.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at Microsoft.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at Microsoft.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry, SqlConnectionOverrides overrides)
   at Microsoft.Data.SqlClient.SqlConnection.Open(SqlConnectionOverrides overrides)
   at Microsoft.Data.SqlClient.SqlConnection.Open()
   at Program.<>c__DisplayClass0_0.<<<Main>$>b__1>d.MoveNext() in /home/dmi/SqlClientTest/ProgramCool.cs:line 23
--- End of stack trace from previous location ---
   at Program.<>c__DisplayClass0_0.<<<Main>$>b__1>d.MoveNext() in /home/dmi/SqlClientTest/ProgramCool.cs:line 25
--- End of stack trace from previous location ---
   at System.Threading.Tasks.Parallel.<>c__53`1.<<ForEachAsync>b__53_0>d.MoveNext()
--- End of stack trace from previous location ---
   at Program.<Main>$(String[] args) in /home/dmi/SqlClientTest/ProgramCool.cs:line 17
   at Program.<Main>(String[] args)

To reproduce

Use Azure SQL Database

using Microsoft.Data.SqlClient;

ThreadPool.SetMinThreads(1, 1);
var connectionString =
    "Data Source={your-server}.windows.net;Initial Catalog={your-catalouge};User ID=usr;Password=pwd;Min Pool Size=3;Connect Timeout=60;Encrypt=Strict;Trust Server Certificate=False;Pooling=False";

// Thread eater 
Func<Task> threadEater = null;
threadEater = async () =>
{
    Thread.Sleep(800);
    Task.Run(threadEater);
    Task.Run(threadEater);
};
Task.Run(threadEater);

await Parallel.ForEachAsync(Enumerable.Range(0, 512), new ParallelOptions
{
    MaxDegreeOfParallelism = 16
}, async (i, _) =>
{
    await using var connection = new SqlConnection(connectionString);
    connection.Open();
    Console.Write(".");
    Thread.Sleep(1000);
});

Expected behavior

It should await long enough until the thread starvation condition disappears, or it should raise a proper exception.

Further technical details

Microsoft.Data.SqlClient version: 5.2.2
.NET target: .Net 8.0
SQL Server version: Azure SQL, Pool
Operating system: Ubuntu 24.04

Additional context
It is hard to reproduce; just thread starvation is not enough. Parameters should be fine-tuned, even Console.Write makes sense.
Tested on 2 CPU / 8 Gb RAM VM; Azure SQL was in the same Region (Australia East).

The problem is happening here and there in PROD, but nobody usually analyses the correlation with thread starvation.

Activity

  1. cheenamalhotra commented on Jan 21, 2025

    @cheenamalhotra
    Member

    This seems related to known design issues with Managed SNI, look at #422 for more detailed discussions. For now, we would recommend tuning up with threadpool size and providing stable conditions to apps running in Unix. In future releases, we hope to see some improvements in this space.

  2. moved this from To triage to Backlog in SqlClient Boardon Oct 28, 2025
  3. paulmedynski commented on Mar 27, 2026

    @paulmedynski
    Contributor

    AppContext switch warning (v7.0.0)

    Be aware that the MakeReadAsyncBlocking switch routes more reads through ReadSniSyncOverAsync — the same code path that fails under thread starvation in this issue. If you have MakeReadAsyncBlocking=true set (e.g. to mitigate async overhead from #1562), it may increase the likelihood of hitting this pre-login handshake error under thread pool pressure.

    // Avoid setting this if you are experiencing thread starvation errors:
    AppContext.SetSwitch("Switch.Microsoft.Data.SqlClient.MakeReadAsyncBlocking", true);
  4. marked Pre-Login timeout #4418 as a duplicate of this issue on Jul 6, 2026
  5. priyankatiwari08 commented on Sep 17, 2026

    @priyankatiwari08
    Contributor

    /triage

  6. github-actions commented on Sep 17, 2026

    @github-actions

    🔍 Triage Summary (on-demand re-triage)

    Check Result
    Issue type Bug
    Environment All required environment details provided for investigation (SqlClient 5.2.2, .NET 8.0, Azure SQL, Ubuntu 24.04)
    Area Area\Managed SNI
    Duplicates Potentially related: #422, #2152
    Regression Not indicated

    Analysis

    The reporter demonstrates that under severe thread-pool starvation, SqlConnection.Open() on Linux (Managed SNI path) surfaces a misleading SqlException — "error occurred during the pre-login handshake (provider: TCP Provider, error: 0 - Success)" — instead of a clearer timeout/starvation-related error. The underlying issue is TdsParserStateObject.ReadSniSyncOverAsync()/ConsumePreLoginHandshake relying on thread-pool availability during the synchronous login/pre-login sequence; when the pool is starved, the operation fails with an unhelpful, generic error rather than waiting appropriately or raising a diagnosable timeout. This aligns with the previously acknowledged Managed SNI design limitation (#422) and the broader thread-pool starvation problem tracked in #2152. Severity: P2 — not a crash or data-loss issue, but it produces confusing diagnostics and can affect production reliability under load, particularly on Unix/Linux where Managed SNI is used.

    Next Steps

    • Recommend assigning this issue to Copilot coding agent to investigate TdsParserStateObject.ReadSniSyncOverAsync() and TdsParser.ConsumePreLoginHandshake in the Managed SNI path (src/Microsoft.Data.SqlClient/src/Microsoft/Data/SqlClient/ManagedSni/), specifically to (1) surface a more diagnosable error/exception when the underlying cause is thread-pool exhaustion rather than an actual SNI failure, and (2) evaluate whether the pre-login handshake read path can avoid depending on ThreadPool queuing for its synchronous-over-async behavior.
    • Review linked issues Queries with MultipleActiveResultSets=True (MARS) are very slow / time out on Linux #422 (Managed SNI design discussion) and Possible lock contention/thread pool starvation when acquiring access token causing timeout exceptions #2152 (thread-pool starvation during token acquisition) for related root-cause analysis before starting investigation, since a shared fix or mitigation pattern may apply to all three.
    • Consider documenting the MakeReadAsyncBlocking AppContext switch interaction noted in this thread (it routes more reads through the same code path affected here) as a known caveat in the feature docs.

    Note: This triage summary is auto-generated by an AI agent. The analysis and suggestions above have not been verified by a human maintainer. Please treat as preliminary guidance only.

    Generated by SqlClient Issue Auto-Triage for #3118 · copilot · auto · 53.2 AIC · ⌖ 13.1 AIC · ⊞ 11.9K · ◷

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions