Skip to content

Commit 35c4e2f

Browse files
SNOW-2044393 Fix for returning cancelled sessions to the connection pool (#1150)
1 parent 191db84 commit 35c4e2f

File tree

8 files changed

+63
-29
lines changed

8 files changed

+63
-29
lines changed

Snowflake.Data.Tests/IntegrationTests/ConnectionMultiplePoolsIT.cs

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -440,5 +440,55 @@ private async Task<SnowflakeDbConnection> OpenConnectionAsync(string connectionS
440440
await connection.OpenAsync().ConfigureAwait(false);
441441
return connection;
442442
}
443+
444+
[Test]
445+
public void TestReturningCancelledSessionsToThePool([Values]bool cancelAsync)
446+
{
447+
var connectionString = ConnectionString + "minPoolSize=0;maxPoolSize=2;application=TestReturningCancelledSessionsToThePool";
448+
449+
var pool = SnowflakeDbConnectionPool.ConnectionManager.GetPool(connectionString);
450+
pool.ClearSessions();
451+
452+
// pool is empty
453+
Assert.AreEqual(0, pool.GetCurrentState().IdleSessionsCount);
454+
Assert.AreEqual(0, pool.GetCurrentState().BusySessionsCount);
455+
456+
var cts = new CancellationTokenSource();
457+
var task = Task.Run(async () =>
458+
{
459+
using (var connection = new SnowflakeDbConnection(connectionString))
460+
{
461+
await connection.OpenAsync(cts.Token);
462+
using (var command = connection.CreateCommand())
463+
{
464+
command.CommandText = $"SELECT SYSTEM$WAIT(20)";
465+
await command.ExecuteNonQueryAsync(cts.Token);
466+
}
467+
}
468+
}, CancellationToken.None);
469+
470+
Thread.Sleep(2000);
471+
472+
// one busy session
473+
Assert.AreEqual(0, pool.GetCurrentState().IdleSessionsCount);
474+
Assert.AreEqual(1, pool.GetCurrentState().BusySessionsCount);
475+
476+
if (cancelAsync)
477+
#if NET8_0_OR_GREATER
478+
cts.CancelAsync();
479+
#else
480+
cts.Cancel();
481+
#endif
482+
else
483+
cts.Cancel();
484+
485+
// operation cancelled properly
486+
var thrown = Assert.Throws<AggregateException>(() => task.Wait());
487+
Assert.IsInstanceOf<OperationCanceledException>(thrown.InnerException);
488+
489+
// one idle session
490+
Assert.AreEqual(1, pool.GetCurrentState().IdleSessionsCount);
491+
Assert.AreEqual(0, pool.GetCurrentState().BusySessionsCount);
492+
}
443493
}
444494
}

Snowflake.Data.Tests/IntegrationTests/ConnectionPoolCommonIT.cs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ public void TestTransactionStatusNotTrackedForNonExplicitTransactionCalls()
178178
}
179179
}
180180

181-
[Test]
181+
[Test]
182182
public void TestRollbackTransactionOnPooledWhenConnectionClose()
183183
{
184184
var connectionString = SetPoolWithOneElement();
@@ -215,7 +215,6 @@ public void TestRollbackTransactionOnPooledWhenConnectionClose()
215215
Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool");
216216
}
217217

218-
219218
private string SetPoolWithOneElement()
220219
{
221220
if (_connectionPoolTypeUnderTest == ConnectionPoolType.SingleConnectionCache)
@@ -230,6 +229,5 @@ private int ExpectedPoolCountAfterOpen()
230229
{
231230
return _connectionPoolTypeUnderTest == ConnectionPoolType.SingleConnectionCache ? 0 : 1;
232231
}
233-
234232
}
235233
}

Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1950,20 +1950,12 @@ public void TestCancelLoginBeforeTimeout()
19501950
conn.ConnectionString = infiniteLoginTimeOut;
19511951

19521952
Assert.AreEqual(conn.State, ConnectionState.Closed);
1953-
// At this point the connection string has not been parsed, it will return the
1954-
// default value
1955-
//Assert.AreEqual(SFSessionHttpClientProperties.s_retryTimeoutDefault, conn.ConnectionTimeout);
19561953

19571954
CancellationTokenSource connectionCancelToken = new CancellationTokenSource();
19581955
Task connectTask = conn.OpenAsync(connectionCancelToken.Token);
19591956

1960-
// Sleep for more than the default timeout to make sure there are no false positive)
1961-
Thread.Sleep(SFSessionHttpClientProperties.DefaultRetryTimeout.Add(TimeSpan.FromSeconds(10)));
1962-
19631957
Assert.AreEqual(ConnectionState.Connecting, conn.State);
19641958

1965-
// Cancel the connection because it will never succeed since there is no
1966-
// connection_timeout defined
19671959
logger.Debug("connectionCancelToken.Cancel ");
19681960
connectionCancelToken.Cancel();
19691961

Snowflake.Data.Tests/Mock/MockSnowflakeDbConnection.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ public override void Open()
4545

4646
public override Task OpenAsync(CancellationToken cancellationToken)
4747
{
48-
registerConnectionCancellationCallback(cancellationToken);
48+
cancellationToken.Register(() => { _connectionState = ConnectionState.Closed; });
4949

5050
SetMockSession();
5151

Snowflake.Data/Client/SnowflakeDbConnection.cs

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,6 @@ public override Task OpenAsync(CancellationToken cancellationToken)
314314
logger.Debug($"Open with a connection already opened: {_connectionState}");
315315
return Task.CompletedTask;
316316
}
317-
registerConnectionCancellationCallback(cancellationToken);
318317
OnSessionConnecting();
319318
FillConnectionStringFromTomlConfigIfNotSet();
320319
return SnowflakeDbConnectionPool
@@ -423,20 +422,6 @@ protected override void Dispose(bool disposing)
423422
base.Dispose(disposing);
424423
}
425424

426-
427-
/// <summary>
428-
/// Register cancel callback. Two factors: either external cancellation token passed down from upper
429-
/// layer or timeout reached. Whichever comes first would trigger query cancellation.
430-
/// </summary>
431-
/// <param name="externalCancellationToken">cancellation token from upper layer</param>
432-
internal void registerConnectionCancellationCallback(CancellationToken externalCancellationToken)
433-
{
434-
if (!externalCancellationToken.IsCancellationRequested)
435-
{
436-
externalCancellationToken.Register(() => { _connectionState = ConnectionState.Closed; });
437-
}
438-
}
439-
440425
public bool IsStillRunning(QueryStatus status)
441426
{
442427
return QueryStatusExtensions.IsStillRunning(status);

Snowflake.Data/Client/SnowflakeDbConnectionPool.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ public class SnowflakeDbConnectionPool
1616
private static IConnectionManager s_connectionManager;
1717
internal const ConnectionPoolType DefaultConnectionPoolType = ConnectionPoolType.MultipleConnectionPool;
1818

19-
private static IConnectionManager ConnectionManager
19+
internal static IConnectionManager ConnectionManager
2020
{
2121
get
2222
{

Snowflake.Data/Core/SFStatement.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -432,7 +432,12 @@ internal async Task<SFBaseResultSet> ExecuteAsync(int timeout, string sql, Dicti
432432

433433
return BuildResultSet(response, cancellationToken);
434434
}
435-
catch
435+
catch (OperationCanceledException ex)
436+
{
437+
logger.Warn($"Query execution canceled.");
438+
throw;
439+
}
440+
catch (Exception ex)
436441
{
437442
logger.Error("Query execution failed.");
438443
throw;

Snowflake.Data/Core/Session/SessionPoolState.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ public SessionPoolState(int idleSessionsCount, int busySessionsCount, int sessio
1919

2020
public int Count() => _idleSessionsCount + _busySessionsCount + _sessionCreationsCount;
2121

22+
public int IdleSessionsCount { get => _idleSessionsCount; }
23+
24+
public int BusySessionsCount { get => _busySessionsCount; }
25+
2226
public override string ToString()
2327
{
2428
return _extensiveFormat

0 commit comments

Comments
 (0)