From 682b4c8191fd68d95aade67566523ae66cd51b4e Mon Sep 17 00:00:00 2001 From: Krzysztof Nozderko Date: Wed, 24 Apr 2024 07:38:33 +0000 Subject: [PATCH] SNOW-1344341 Set new connection pool as default --- .../ConnectionMultiplePoolsAsyncIT.cs | 107 ++++++ .../ConnectionPoolCommonIT.cs | 107 ++++++ .../ConnectionSinglePoolCacheAsyncIT.cs | 217 ++++++++++++ .../IntegrationTests/SFConnectionIT.cs | 35 +- .../SFConnectionPoolAsyncIT.cs | 323 ------------------ .../UnitTests/ConnectionPoolManagerTest.cs | 95 +++--- Snowflake.Data.Tests/Util/PoolConfig.cs | 11 +- .../Client/SnowflakeDbConnectionPool.cs | 14 +- .../Core/Session/ConnectionPoolManager.cs | 44 +-- 9 files changed, 530 insertions(+), 423 deletions(-) create mode 100644 Snowflake.Data.Tests/IntegrationTests/ConnectionSinglePoolCacheAsyncIT.cs delete mode 100644 Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs diff --git a/Snowflake.Data.Tests/IntegrationTests/ConnectionMultiplePoolsAsyncIT.cs b/Snowflake.Data.Tests/IntegrationTests/ConnectionMultiplePoolsAsyncIT.cs index c656015b7..8f7d3c0ab 100644 --- a/Snowflake.Data.Tests/IntegrationTests/ConnectionMultiplePoolsAsyncIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/ConnectionMultiplePoolsAsyncIT.cs @@ -1,9 +1,11 @@ +using System; using System.Data.Common; using System.Threading; using System.Threading.Tasks; using Moq; using NUnit.Framework; using Snowflake.Data.Client; +using Snowflake.Data.Core; using Snowflake.Data.Core.Session; using Snowflake.Data.Tests.Mock; using Snowflake.Data.Tests.Util; @@ -29,6 +31,93 @@ public class ConnectionMultiplePoolsAsyncIT: SFBaseTestAsync _previousPoolConfig.Reset(); } + [Test] + public async Task TestAddToPoolOnOpenAsync() + { + // arrange + var connection = new SnowflakeDbConnection(ConnectionString + "minPoolSize=1"); + + // act + await connection.OpenAsync().ConfigureAwait(false); + + // assert + var pool = SnowflakeDbConnectionPool.GetPool(connection.ConnectionString); + Assert.AreEqual(1, pool.GetCurrentPoolSize()); + + // cleanup + await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false); + } + + [Test] + public async Task TestDoNotAddToPoolInvalidConnectionAsync() + { + // arrange + var invalidConnectionString = ";connection_timeout=123"; + var connection = new SnowflakeDbConnection(invalidConnectionString); + + // act + try + { + await connection.OpenAsync().ConfigureAwait(false); + Assert.Fail("OpenAsync should fail for invalid connection string"); + } + catch {} + + // assert + var pool = SnowflakeDbConnectionPool.GetPool(connection.ConnectionString); + Assert.Less(pool.GetCurrentPoolSize(), SFSessionHttpClientProperties.DefaultMinPoolSize); // for invalid connection string it is used default min pool size + + // cleanup + await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false); + } + + [Test] + public void TestConnectionPoolWithInvalidOpenAsync() + { + // make the connection string unique so it won't pick up connection + // pooled by other test cases. + string connStr = ConnectionString + "minPoolSize=0;maxPoolSize=10;application=conn_pool_test_invalid_openasync2"; + using (var connection = new SnowflakeDbConnection()) + { + connection.ConnectionString = connStr; + // call openAsync but do not wait and destroy it direct + // so the session is initialized with empty token + connection.OpenAsync(); + } + + // use the same connection string to make a new connection + // to ensure the invalid connection made previously is not pooled + using (var connection1 = new SnowflakeDbConnection()) + { + connection1.ConnectionString = connStr; + // this will not open a new session but get the invalid connection from pool + connection1.Open(); + // Now run query with connection1 + var command = connection1.CreateCommand(); + command.CommandText = "select 1, 2, 3"; + + try + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; i++) + { + // Process each column as appropriate + reader.GetFieldValue(i); + } + } + } + } + catch (SnowflakeDbException) + { + // fail the test case if anything wrong. + Assert.Fail(); + } + } + } + [Test] public async Task TestMinPoolSizeAsync() { @@ -88,5 +177,23 @@ public async Task TestReleaseConnectionWhenRollbackFailsAsync() // assert Assert.AreEqual(0, pool.GetCurrentPoolSize(), "Should not return connection to the pool"); } + + [Test(Description = "test connection pooling with concurrent connection using async calls")] + public void TestConcurrentConnectionPoolingAsync() + { + // add test case name in connection string to make in unique for each test case + // set short expiration timeout to cover the case that connection expired + string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingAsync2;ExpirationTimeout=3"; + ConnectionSinglePoolCacheAsyncIT.ConcurrentPoolingAsyncHelper(connStr, true, 7, 100, 2); + } + + [Test(Description = "test connection pooling with concurrent connection and using async calls no close call for connection. Connection is closed when Dispose() is called by framework.")] + public void TestConcurrentConnectionPoolingDisposeAsync() + { + // add test case name in connection string to make in unique for each test case + // set short expiration timeout to cover the case that connection expired + string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingDisposeAsync2;ExpirationTimeout=3"; + ConnectionSinglePoolCacheAsyncIT.ConcurrentPoolingAsyncHelper(connStr, false, 7, 100, 2); + } } } diff --git a/Snowflake.Data.Tests/IntegrationTests/ConnectionPoolCommonIT.cs b/Snowflake.Data.Tests/IntegrationTests/ConnectionPoolCommonIT.cs index eb296636c..e05e342f6 100644 --- a/Snowflake.Data.Tests/IntegrationTests/ConnectionPoolCommonIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/ConnectionPoolCommonIT.cs @@ -121,5 +121,112 @@ public void TestFailWhenPreventingFromReturningToPoolNotOpenedConnection() // assert Assert.That(thrown.Message, Does.Contain("Session not yet created for this connection. Unable to prevent the session from pooling")); } + + [Test] + public void TestRollbackTransactionOnPooledWhenExceptionOccurred() + { + var connectionString = SetPoolWithOneElement(); + object firstOpenedSessionId; + using (var connection = new SnowflakeDbConnection(connectionString)) + { + connection.Open(); + firstOpenedSessionId = connection.SfSession.sessionId; + connection.BeginTransaction(); + Assert.AreEqual(true, connection.HasActiveExplicitTransaction()); + Assert.Throws(() => + { + using (var command = connection.CreateCommand()) + { + command.CommandText = "invalid command will throw exception and leave session with an unfinished transaction"; + command.ExecuteNonQuery(); + } + }); + } + + using (var connectionWithSessionReused = new SnowflakeDbConnection(connectionString)) + { + connectionWithSessionReused.Open(); + + Assert.AreEqual(firstOpenedSessionId, connectionWithSessionReused.SfSession.sessionId); + Assert.AreEqual(false, connectionWithSessionReused.HasActiveExplicitTransaction()); + using (var cmd = connectionWithSessionReused.CreateCommand()) + { + cmd.CommandText = "SELECT CURRENT_TRANSACTION()"; + Assert.AreEqual(DBNull.Value, cmd.ExecuteScalar()); + } + } + + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be reused and any pending transaction rolled back before it gets back to the pool"); + } + + [Test] + public void TestTransactionStatusNotTrackedForNonExplicitTransactionCalls() + { + var connectionString = SetPoolWithOneElement(); + using (var connection = new SnowflakeDbConnection(connectionString)) + { + connection.Open(); + using (var command = connection.CreateCommand()) + { + command.CommandText = "BEGIN"; // in general can be put as a part of a multi statement call and mixed with commit as well + command.ExecuteNonQuery(); + Assert.AreEqual(false, connection.HasActiveExplicitTransaction()); + } + } + } + + [Test] + public void TestRollbackTransactionOnPooledWhenConnectionClose() + { + var connectionString = SetPoolWithOneElement(); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); + + string firstOpenedSessionId; + using (var connection1 = new SnowflakeDbConnection(connectionString)) + { + connection1.Open(); + Assert.AreEqual(ExpectedPoolCountAfterOpen(), SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session is added to the pool after close connection"); + connection1.BeginTransaction(); + Assert.AreEqual(true, connection1.HasActiveExplicitTransaction()); + using (var command = connection1.CreateCommand()) + { + firstOpenedSessionId = connection1.SfSession.sessionId; + command.CommandText = "SELECT CURRENT_TRANSACTION()"; + Assert.AreNotEqual(DBNull.Value, command.ExecuteScalar()); + } + } + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); + + using (var connection2 = new SnowflakeDbConnection(connectionString)) + { + connection2.Open(); + Assert.AreEqual(ExpectedPoolCountAfterOpen(), SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session should be now removed from the pool"); + Assert.AreEqual(false, connection2.HasActiveExplicitTransaction()); + using (var command = connection2.CreateCommand()) + { + Assert.AreEqual(firstOpenedSessionId, connection2.SfSession.sessionId); + command.CommandText = "SELECT CURRENT_TRANSACTION()"; + Assert.AreEqual(DBNull.Value, command.ExecuteScalar()); + } + } + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); + } + + + private string SetPoolWithOneElement() + { + if (_connectionPoolTypeUnderTest == ConnectionPoolType.SingleConnectionCache) + { + SnowflakeDbConnectionPool.SetMaxPoolSize(1); + return ConnectionString; + } + return ConnectionString + "maxPoolSize=1;minPoolSize=0"; + } + + private int ExpectedPoolCountAfterOpen() + { + return _connectionPoolTypeUnderTest == ConnectionPoolType.SingleConnectionCache ? 0 : 1; + } + } } diff --git a/Snowflake.Data.Tests/IntegrationTests/ConnectionSinglePoolCacheAsyncIT.cs b/Snowflake.Data.Tests/IntegrationTests/ConnectionSinglePoolCacheAsyncIT.cs new file mode 100644 index 000000000..1b0ac0cf8 --- /dev/null +++ b/Snowflake.Data.Tests/IntegrationTests/ConnectionSinglePoolCacheAsyncIT.cs @@ -0,0 +1,217 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Threading; +using System.Threading.Tasks; +using NUnit.Framework; +using Snowflake.Data.Client; +using Snowflake.Data.Core.Session; +using Snowflake.Data.Tests.Mock; +using Snowflake.Data.Tests.Util; + +namespace Snowflake.Data.Tests.IntegrationTests +{ + [TestFixture] + [NonParallelizable] + public class ConnectionSinglePoolCacheAsyncIT: SFBaseTestAsync + { + private readonly PoolConfig _previousPoolConfig = new PoolConfig(); + + [SetUp] + public new void BeforeTest() + { + SnowflakeDbConnectionPool.SetConnectionPoolVersion(ConnectionPoolType.SingleConnectionCache); + SnowflakeDbConnectionPool.ClearAllPools(); + } + + [TearDown] + public new void AfterTest() + { + _previousPoolConfig.Reset(); + } + + + [Test] + public async Task TestPutConnectionToPoolOnCloseAsync() + { + // arrange + using (var conn = new SnowflakeDbConnection(ConnectionString)) + { + Assert.AreEqual(conn.State, ConnectionState.Closed); + CancellationTokenSource connectionCancelToken = new CancellationTokenSource(); + await conn.OpenAsync(connectionCancelToken.Token).ConfigureAwait(false); + + // act + await conn.CloseAsync(connectionCancelToken.Token).ConfigureAwait(false); + + // assert + Assert.AreEqual(ConnectionState.Closed, conn.State); + Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + } + } + + [Test] + public async Task TestDoNotPutInvalidConnectionToPoolAsync() + { + // arrange + var invalidConnectionString = ";connection_timeout=0"; + using (var conn = new SnowflakeDbConnection(invalidConnectionString)) + { + Assert.AreEqual(conn.State, ConnectionState.Closed); + CancellationTokenSource connectionCancelToken = new CancellationTokenSource(); + try + { + await conn.OpenAsync(connectionCancelToken.Token).ConfigureAwait(false); + Assert.Fail("OpenAsync should throw exception"); + } + catch {} + + // act + await conn.CloseAsync(connectionCancelToken.Token).ConfigureAwait(false); + + // assert + Assert.AreEqual(ConnectionState.Closed, conn.State); + Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); + } + } + + [Test] + public void TestConnectionPoolWithInvalidOpenAsync() + { + SnowflakeDbConnectionPool.SetMaxPoolSize(10); + // make the connection string unique so it won't pick up connection + // pooled by other test cases. + string connStr = ConnectionString + "application=conn_pool_test_invalid_openasync"; + using (var connection = new SnowflakeDbConnection()) + { + connection.ConnectionString = connStr; + // call openAsync but do not wait and destroy it direct + // so the session is initialized with empty token + connection.OpenAsync(); + } + + // use the same connection string to make a new connection + // to ensure the invalid connection made previously is not pooled + using (var connection1 = new SnowflakeDbConnection()) + { + connection1.ConnectionString = connStr; + // this will not open a new session but get the invalid connection from pool + connection1.Open(); + // Now run query with connection1 + var command = connection1.CreateCommand(); + command.CommandText = "select 1, 2, 3"; + + try + { + using (var reader = command.ExecuteReader()) + { + while (reader.Read()) + { + for (int i = 0; i < reader.FieldCount; i++) + { + // Process each column as appropriate + reader.GetFieldValue(i); + } + } + } + } + catch (SnowflakeDbException) + { + // fail the test case if anything wrong. + Assert.Fail(); + } + } + } + + [Test(Description = "test connection pooling with concurrent connection using async calls")] + public void TestConcurrentConnectionPoolingAsync() + { + // add test case name in connection string to make in unique for each test case + string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingAsync"; + SnowflakeDbConnectionPool.SetMaxPoolSize(10); + SnowflakeDbConnectionPool.SetTimeout(3); // set short pooling timeout to cover the case that connection expired + ConcurrentPoolingAsyncHelper(connStr, true, 12, 100, 100); + SnowflakeDbConnectionPool.SetTimeout(3600); + } + + [Test(Description = "test connection pooling with concurrent connection and using async calls no close call for connection. Connection is closed when Dispose() is called by framework.")] + public void TestConcurrentConnectionPoolingDisposeAsync() + { + // add test case name in connection string to make in unique for each test case + string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingDisposeAsync"; + SnowflakeDbConnectionPool.SetMaxPoolSize(10); + SnowflakeDbConnectionPool.SetTimeout(3); // set short pooling timeout to cover the case that connection expired + ConcurrentPoolingAsyncHelper(connStr, false, 12, 100, 100); + SnowflakeDbConnectionPool.SetTimeout(3600); + } + + public static void ConcurrentPoolingAsyncHelper(string connectionString, bool closeConnection, int tasksCount, int connectionsInTask, int abandonedConnectionsCount) + { + var tasks = new Task[tasksCount + 1]; + for (int i = 0; i < tasksCount; i++) + { + tasks[i] = QueryExecutionTaskAsync(connectionString, closeConnection, connectionsInTask); + } + // cover the case of invalid sessions to ensure that won't + // break connection pooling + tasks[tasksCount] = InvalidConnectionTaskAsync(connectionString, abandonedConnectionsCount); + Task.WaitAll(tasks); + } + + // task to execute query with new connection in a loop + static async Task QueryExecutionTaskAsync(string connectionString, bool closeConnection, int times) + { + for (int i = 0; i < times; i++) + { + using (var conn = new SnowflakeDbConnection(connectionString)) + { + await conn.OpenAsync().ConfigureAwait(false); + using (DbCommand cmd = conn.CreateCommand()) + { + cmd.CommandText = "select 1, 2, 3"; + try + { + using (DbDataReader reader = await cmd.ExecuteReaderAsync().ConfigureAwait(false)) + { + while (await reader.ReadAsync().ConfigureAwait(false)) + { + for (int j = 0; j < reader.FieldCount; j++) + { + // Process each column as appropriate + await reader.GetFieldValueAsync(j).ConfigureAwait(false); + } + } + } + } + catch (Exception e) + { + Assert.Fail("Caught unexpected exception: " + e); + } + } + if (closeConnection) + { + await conn.CloseAsync(new CancellationTokenSource().Token).ConfigureAwait(false); + } + } + } + } + + // task to generate invalid(not finish open) connections in a loop + static async Task InvalidConnectionTaskAsync(string connectionString, int times) + { + for (int i = 0; i < times; i++) + { + using (var conn = new SnowflakeDbConnection(connectionString)) + { + // intentionally not using await so the connection + // will be disposed with invalid underlying session + conn.OpenAsync(); + }; + // wait 100ms each time so the invalid sessions are generated + // roughly at the same speed as connections for query tasks + await Task.Delay(100).ConfigureAwait(false); + } + } + + } +} diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs index 34b754aa4..18bb2f80c 100644 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs +++ b/Snowflake.Data.Tests/IntegrationTests/SFConnectionIT.cs @@ -4,6 +4,7 @@ using System.Data.Common; using System.Net; +using Snowflake.Data.Core.Session; using Snowflake.Data.Tests.Util; namespace Snowflake.Data.Tests.IntegrationTests @@ -165,7 +166,6 @@ public void TestConnectionIsNotMarkedAsOpenWhenWasNotCorrectlyOpenedBefore(bool [Test] public void TestConnectionIsNotMarkedAsOpenWhenWasNotCorrectlyOpenedWithUsingClause() { - SnowflakeDbConnectionPool.SetPooling(true); for (int i = 0; i < 2; ++i) { s_logger.Debug($"Running try #{i}"); @@ -1713,8 +1713,7 @@ public void TestEscapeChar() { using (IDbConnection conn = new SnowflakeDbConnection()) { - SnowflakeDbConnectionPool.SetPooling(false); - conn.ConnectionString = ConnectionString + "key1=test\'password;key2=test\"password;key3=test==password"; + conn.ConnectionString = ConnectionString + "poolingEnabled=false;key1=test\'password;key2=test\"password;key3=test==password"; conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); @@ -1740,8 +1739,7 @@ public void TestEscapeChar1() { using (IDbConnection conn = new SnowflakeDbConnection()) { - SnowflakeDbConnectionPool.SetPooling(false); - conn.ConnectionString = ConnectionString + "key==word=value; key1=\"test;password\"; key2=\"test=password\""; + conn.ConnectionString = ConnectionString + "poolingEnabled=false;key==word=value; key1=\"test;password\"; key2=\"test=password\""; conn.Open(); Assert.AreEqual(ConnectionState.Open, conn.State); @@ -1765,9 +1763,8 @@ public void TestEscapeChar1() [Ignore("Ignore this test. Please run this manually, since it takes 4 hrs to finish.")] public void TestHeartBeat() { - SnowflakeDbConnectionPool.SetPooling(false); var conn = new SnowflakeDbConnection(); - conn.ConnectionString = ConnectionString + ";CLIENT_SESSION_KEEP_ALIVE=true"; + conn.ConnectionString = ConnectionString + "poolingEnabled=false;CLIENT_SESSION_KEEP_ALIVE=true"; conn.Open(); Thread.Sleep(TimeSpan.FromSeconds(14430)); // more than 4 hrs @@ -1786,12 +1783,9 @@ public void TestHeartBeat() public void TestHeartBeatWithConnectionPool() { SnowflakeDbConnectionPool.ClearAllPools(); - SnowflakeDbConnectionPool.SetMaxPoolSize(2); - SnowflakeDbConnectionPool.SetTimeout(14800); - SnowflakeDbConnectionPool.SetPooling(true); var conn = new SnowflakeDbConnection(); - conn.ConnectionString = ConnectionString + ";CLIENT_SESSION_KEEP_ALIVE=true"; + conn.ConnectionString = ConnectionString + "maxPoolSize=2;minPoolSize=0;expirationTimeout=14800;CLIENT_SESSION_KEEP_ALIVE=true"; conn.Open(); conn.Close(); @@ -1818,10 +1812,9 @@ public void TestKeepAlive() { // create 100 connections, one per second var connCount = 100; - // pooled connectin expire in 5 seconds so after 5 seconds, + // pooled connection expires in 5 seconds so after 5 seconds, // one connection per second will be closed - SnowflakeDbConnectionPool.SetTimeout(5); - SnowflakeDbConnectionPool.SetMaxPoolSize(20); + var connectionString = ConnectionString + "maxPoolSize=20;ExpirationTimeout=5;CLIENT_SESSION_KEEP_ALIVE=true"; // heart beat interval is validity/4 so send out per 5 seconds HeartBeatBackground.setValidity(20); try @@ -1830,7 +1823,7 @@ public void TestKeepAlive() { using (var conn = new SnowflakeDbConnection()) { - conn.ConnectionString = ConnectionString + ";CLIENT_SESSION_KEEP_ALIVE=true"; + conn.ConnectionString = connectionString; conn.Open(); } Thread.Sleep(TimeSpan.FromSeconds(1)); @@ -2225,7 +2218,8 @@ public void TestNativeOktaSuccess() Assert.AreEqual(ConnectionState.Open, conn.State); } } -[Test] + + [Test] public void TestConnectStringWithQueryTag() { using (var conn = new SnowflakeDbConnection()) @@ -2243,6 +2237,15 @@ public void TestConnectStringWithQueryTag() } } + [Test] + public void TestUseMultiplePoolsConnectionPoolByDefault() + { + // act + var poolVersion = SnowflakeDbConnectionPool.GetConnectionPoolVersion(); + + // assert + Assert.AreEqual(ConnectionPoolType.MultipleConnectionPool, poolVersion); + } } } diff --git a/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs b/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs deleted file mode 100644 index a7fd20822..000000000 --- a/Snowflake.Data.Tests/IntegrationTests/SFConnectionPoolAsyncIT.cs +++ /dev/null @@ -1,323 +0,0 @@ -/* - * Copyright (c) 2012-2023 Snowflake Computing Inc. All rights reserved. - */ - -using Snowflake.Data.Tests.Util; -using System; -using System.Data; -using System.Data.Common; -using System.Threading; -using System.Threading.Tasks; -using Snowflake.Data.Client; -using Snowflake.Data.Tests.Mock; -using NUnit.Framework; - -namespace Snowflake.Data.Tests.IntegrationTests -{ - [TestFixture, NonParallelizable] - class SFConnectionPoolITAsync : SFBaseTestAsync - { - private static PoolConfig s_previousPoolConfigRestorer; - - [OneTimeSetUp] - public static void BeforeAllTests() - { - s_previousPoolConfigRestorer = new PoolConfig(); - } - - [SetUp] - public new void BeforeTest() - { - SnowflakeDbConnectionPool.SetPooling(true); - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [TearDown] - public new void AfterTest() - { - s_previousPoolConfigRestorer.Reset(); - } - - [OneTimeTearDown] - public static void AfterAllTests() - { - SnowflakeDbConnectionPool.ClearAllPools(); - } - - [Test] - public void TestConnectionPoolWithAsync() - { - using (var conn = new MockSnowflakeDbConnection()) - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - - int timeoutSec = 0; - string infiniteLoginTimeOut = $";connection_timeout={timeoutSec}"; - - conn.ConnectionString = infiniteLoginTimeOut; - - Assert.AreEqual(conn.State, ConnectionState.Closed); - - CancellationTokenSource connectionCancelToken = new CancellationTokenSource(); - try - { - conn.OpenAsync(connectionCancelToken.Token); - } - catch (SnowflakeDbException ex) - { - conn.CloseAsync(connectionCancelToken.Token); - } - - Thread.Sleep(10 * 1000); - Assert.AreEqual(ConnectionState.Closed, conn.State); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize()); - } - } - - [Test] - public void TestConnectionPoolWithInvalidOpenAsync() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(10); - // make the connection string unique so it won't pick up connection - // pooled by other test cases. - string connStr = ConnectionString + ";application=conn_pool_test_invalid_openasync"; - using (var connection = new SnowflakeDbConnection()) - { - connection.ConnectionString = connStr; - // call openAsync but do not wait and destroy it direct - // so the session is initialized with empty token - connection.OpenAsync(); - } - - // use the same connection string to make a new connection - // to ensure the invalid connection made previously is not pooled - using (var connection1 = new SnowflakeDbConnection()) - { - connection1.ConnectionString = connStr; - // this will not open a new session but get the invalid connection from pool - connection1.Open(); - // Now run query with connection1 - var command = connection1.CreateCommand(); - command.CommandText = "select 1, 2, 3"; - - try - { - using (var reader = command.ExecuteReader()) - { - while (reader.Read()) - { - for (int i = 0; i < reader.FieldCount; i++) - { - // Process each column as appropriate - reader.GetFieldValue(i); - } - } - } - } - catch (SnowflakeDbException) - { - // fail the test case if anything wrong. - Assert.Fail(); - } - } - } - - [Test] - // test connection pooling with concurrent connection using async calls - public void TestConcurrentConnectionPoolingAsync() - { - // add test case name in connection string to make in unique for each test case - string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingAsync"; - ConcurrentPoolingAsyncHelper(connStr, true); - } - - [Test] - public void TestRollbackTransactionOnPooledWhenExceptionOccurred() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - - object firstOpenedSessionId; - using (var connection = new SnowflakeDbConnection()) - { - connection.ConnectionString = ConnectionString; - connection.Open(); - firstOpenedSessionId = connection.SfSession.sessionId; - connection.BeginTransaction(); - Assert.AreEqual(true, connection.HasActiveExplicitTransaction()); - Assert.Throws(() => - { - using (var command = connection.CreateCommand()) - { - command.CommandText = "invalid command will throw exception and leave session with an unfinished transaction"; - command.ExecuteNonQuery(); - } - }); - } - - using (var connectionWithSessionReused = new SnowflakeDbConnection()) - { - connectionWithSessionReused.ConnectionString = ConnectionString; - connectionWithSessionReused.Open(); - - Assert.AreEqual(firstOpenedSessionId, connectionWithSessionReused.SfSession.sessionId); - Assert.AreEqual(false, connectionWithSessionReused.HasActiveExplicitTransaction()); - using (var cmd = connectionWithSessionReused.CreateCommand()) - { - cmd.CommandText = "SELECT CURRENT_TRANSACTION()"; - Assert.AreEqual(DBNull.Value, cmd.ExecuteScalar()); - } - } - - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be reused and any pending transaction rolled back before it gets back to the pool"); - } - - [Test] - public void TestTransactionStatusNotTrackedForNonExplicitTransactionCalls() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - using (var connection = new SnowflakeDbConnection()) - { - connection.ConnectionString = ConnectionString; - connection.Open(); - using (var command = connection.CreateCommand()) - { - command.CommandText = "BEGIN"; // in general can be put as a part of a multi statement call and mixed with commit as well - command.ExecuteNonQuery(); - Assert.AreEqual(false, connection.HasActiveExplicitTransaction()); - } - } - } - - [Test] - public void TestRollbackTransactionOnPooledWhenConnectionClose() - { - SnowflakeDbConnectionPool.SetMaxPoolSize(1); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); - - string firstOpenedSessionId; - using (var connection1 = new SnowflakeDbConnection()) - { - connection1.ConnectionString = ConnectionString; - connection1.Open(); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session is added to the pool after close connection"); - connection1.BeginTransaction(); - Assert.AreEqual(true, connection1.HasActiveExplicitTransaction()); - using (var command = connection1.CreateCommand()) - { - firstOpenedSessionId = connection1.SfSession.sessionId; - command.CommandText = "SELECT CURRENT_TRANSACTION()"; - Assert.AreNotEqual(DBNull.Value, command.ExecuteScalar()); - } - } - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); - - using (var connection2 = new SnowflakeDbConnection()) - { - connection2.ConnectionString = ConnectionString; - connection2.Open(); - Assert.AreEqual(0, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection session should be now removed from the pool"); - Assert.AreEqual(false, connection2.HasActiveExplicitTransaction()); - using (var command = connection2.CreateCommand()) - { - Assert.AreEqual(firstOpenedSessionId, connection2.SfSession.sessionId); - command.CommandText = "SELECT CURRENT_TRANSACTION()"; - Assert.AreEqual(DBNull.Value, command.ExecuteScalar()); - } - } - Assert.AreEqual(1, SnowflakeDbConnectionPool.GetCurrentPoolSize(), "Connection should be returned to the pool"); - } - - [Test] - // test connection pooling with concurrent connection and using async calls no close - // call for connection. Connection is closed when Dispose() is called - // by framework. - public void TestConcurrentConnectionPoolingDisposeAsync() - { - // add test case name in connection string to make in unique for each test case - string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingDisposeAsync"; - ConcurrentPoolingAsyncHelper(connStr, false); - } - - static void ConcurrentPoolingAsyncHelper(string connectionString, bool closeConnection) - { - // task number a bit larger than pool size so some connections - // would fail on pooling while some connections could success - const int TaskNum = 12; - // set short pooling timeout to cover the case that connection expired - const int PoolTimeout = 3; - - // reset to default settings in case it changed by other test cases - SnowflakeDbConnectionPool.SetMaxPoolSize(10); - SnowflakeDbConnectionPool.SetTimeout(PoolTimeout); - - var tasks = new Task[TaskNum + 1]; - for (int i = 0; i < TaskNum; i++) - { - tasks[i] = QueryExecutionTaskAsync(connectionString, closeConnection); - } - // cover the case of invalid sessions to ensure that won't - // break connection pooling - tasks[TaskNum] = InvalidConnectionTaskAsync(connectionString); - Task.WaitAll(tasks); - - // set pooling timeout back to default to avoid impact on other test cases - SnowflakeDbConnectionPool.SetTimeout(3600); - } - - // task to execute query with new connection in a loop - static async Task QueryExecutionTaskAsync(string connectionString, bool closeConnection) - { - for (int i = 0; i < 100; i++) - { - using (var conn = new SnowflakeDbConnection(connectionString)) - { - await conn.OpenAsync(); - using (DbCommand cmd = conn.CreateCommand()) - { - cmd.CommandText = "select 1, 2, 3"; - try - { - using (DbDataReader reader = await cmd.ExecuteReaderAsync()) - { - while (await reader.ReadAsync()) - { - for (int j = 0; j < reader.FieldCount; j++) - { - // Process each column as appropriate - await reader.GetFieldValueAsync(j); - } - } - } - } - catch (Exception e) - { - Assert.Fail("Caught unexpected exception: " + e); - } - } - - if (closeConnection) - { - await conn.CloseAsync(new CancellationTokenSource().Token); - } - } - } - } - - // task to generate invalid(not finish open) connections in a loop - static async Task InvalidConnectionTaskAsync(string connectionString) - { - for (int i = 0; i < 100; i++) - { - using (var conn = new SnowflakeDbConnection(connectionString)) - { - // intentionally not using await so the connection - // will be disposed with invalid underlying session - conn.OpenAsync(); - }; - // wait 100ms each time so the invalid sessions are generated - // roughly at the same speed as connections for query tasks - await Task.Delay(100); - } - } - } -} diff --git a/Snowflake.Data.Tests/UnitTests/ConnectionPoolManagerTest.cs b/Snowflake.Data.Tests/UnitTests/ConnectionPoolManagerTest.cs index 71cdfe396..4e1d9f534 100644 --- a/Snowflake.Data.Tests/UnitTests/ConnectionPoolManagerTest.cs +++ b/Snowflake.Data.Tests/UnitTests/ConnectionPoolManagerTest.cs @@ -24,14 +24,14 @@ class ConnectionPoolManagerTest private readonly SecureString _password = new SecureString(); private static PoolConfig s_poolConfig; - [OneTimeSetUp] + [OneTimeSetUp] public static void BeforeAllTests() { s_poolConfig = new PoolConfig(); SnowflakeDbConnectionPool.SetConnectionPoolVersion(ConnectionPoolType.MultipleConnectionPool); SessionPool.SessionFactory = new MockSessionFactory(); } - + [OneTimeTearDown] public static void AfterAllTests() { @@ -50,49 +50,49 @@ public void TestPoolManagerReturnsSessionPoolForGivenConnectionString() { // Act var sessionPool = _connectionPoolManager.GetPool(ConnectionString1, _password); - + // Assert Assert.AreEqual(ConnectionString1, sessionPool.ConnectionString); Assert.AreEqual(_password, sessionPool.Password); } - + [Test] public void TestPoolManagerReturnsSamePoolForGivenConnectionString() { // Arrange var anotherConnectionString = ConnectionString1; - + // Act var sessionPool1 = _connectionPoolManager.GetPool(ConnectionString1, _password); var sessionPool2 = _connectionPoolManager.GetPool(anotherConnectionString, _password); - + // Assert Assert.AreEqual(sessionPool1, sessionPool2); } - + [Test] public void TestDifferentPoolsAreReturnedForDifferentConnectionStrings() { // Arrange Assert.AreNotSame(ConnectionString1, ConnectionString2); - + // Act var sessionPool1 = _connectionPoolManager.GetPool(ConnectionString1, _password); var sessionPool2 = _connectionPoolManager.GetPool(ConnectionString2, _password); - + // Assert Assert.AreNotSame(sessionPool1, sessionPool2); Assert.AreEqual(ConnectionString1, sessionPool1.ConnectionString); Assert.AreEqual(ConnectionString2, sessionPool2.ConnectionString); } - - + + [Test] public void TestGetSessionWorksForSpecifiedConnectionString() { // Act var sfSession = _connectionPoolManager.GetSession(ConnectionString1, _password); - + // Assert Assert.AreEqual(ConnectionString1, sfSession.ConnectionString); Assert.AreEqual(_password, sfSession.Password); @@ -103,7 +103,7 @@ public async Task TestGetSessionAsyncWorksForSpecifiedConnectionString() { // Act var sfSession = await _connectionPoolManager.GetSessionAsync(ConnectionString1, _password, CancellationToken.None); - + // Assert Assert.AreEqual(ConnectionString1, sfSession.ConnectionString); Assert.AreEqual(_password, sfSession.Password); @@ -114,21 +114,21 @@ public void TestCountingOfSessionProvidedByPool() { // Act _connectionPoolManager.GetSession(ConnectionString1, _password); - + // Assert var sessionPool = _connectionPoolManager.GetPool(ConnectionString1, _password); Assert.AreEqual(1, sessionPool.GetCurrentPoolSize()); } - + [Test] public void TestCountingOfSessionReturnedBackToPool() { // Arrange var sfSession = _connectionPoolManager.GetSession(ConnectionString1, _password); - + // Act _connectionPoolManager.AddSession(sfSession); - + // Assert var sessionPool = _connectionPoolManager.GetPool(ConnectionString1, _password); Assert.AreEqual(1, sessionPool.GetCurrentPoolSize()); @@ -146,7 +146,7 @@ public void TestSetMaxPoolSizeForAllPoolsDisabled() // Assert Assert.That(thrown.Message, Does.Contain("You cannot change connection pool parameters for all the pools. Instead you can change it on a particular pool")); } - + [Test] public void TestSetTimeoutForAllPoolsDisabled() { @@ -155,11 +155,11 @@ public void TestSetTimeoutForAllPoolsDisabled() // Act var thrown = Assert.Throws(() => _connectionPoolManager.SetTimeout(3000)); - + // Assert Assert.That(thrown.Message, Does.Contain("You cannot change connection pool parameters for all the pools. Instead you can change it on a particular pool")); - } - + } + [Test] public void TestSetPoolingForAllPoolsDisabled() { @@ -168,38 +168,25 @@ public void TestSetPoolingForAllPoolsDisabled() // Act var thrown = Assert.Throws(() => _connectionPoolManager.SetPooling(false)); - + // Assert Assert.That(thrown.Message, Does.Contain("You cannot change connection pool parameters for all the pools. Instead you can change it on a particular pool")); } [Test] - public void TestGetPoolingOnManagerLevelWhenNotAllPoolsEqual() + public void TestGetPoolingOnManagerLevelAlwaysTrue() { // Arrange var sessionPool1 = _connectionPoolManager.GetPool(ConnectionString1, _password); var sessionPool2 = _connectionPoolManager.GetPool(ConnectionString2, _password); sessionPool1.SetPooling(true); sessionPool2.SetPooling(false); - - // Act/Assert - var exception = Assert.Throws(() => _connectionPoolManager.GetPooling()); - Assert.IsNotNull(exception); - Assert.AreEqual(SFError.INCONSISTENT_RESULT_ERROR.GetAttribute().errorCode, exception.ErrorCode); - Assert.IsTrue(exception.Message.Contains("Multiple pools have different Pooling values")); - } - [Test] - public void TestGetPoolingOnManagerLevelWorksWhenAllPoolsEqual() - { - // Arrange - var sessionPool1 = _connectionPoolManager.GetPool(ConnectionString1, _password); - var sessionPool2 = _connectionPoolManager.GetPool(ConnectionString2, _password); - sessionPool1.SetPooling(true); - sessionPool2.SetPooling(true); - - // Act/Assert - Assert.AreEqual(true,_connectionPoolManager.GetPooling()); + // Act + var pooling = _connectionPoolManager.GetPooling(); + + // Assert + Assert.IsTrue(pooling); } [Test] @@ -210,7 +197,7 @@ public void TestGetTimeoutOnManagerLevelWhenNotAllPoolsEqual() var sessionPool2 = _connectionPoolManager.GetPool(ConnectionString2, _password); sessionPool1.SetTimeout(299); sessionPool2.SetTimeout(1313); - + // Act/Assert var exception = Assert.Throws(() => _connectionPoolManager.GetTimeout()); Assert.IsNotNull(exception); @@ -226,7 +213,7 @@ public void TestGetTimeoutOnManagerLevelWhenAllPoolsEqual() var sessionPool2 = _connectionPoolManager.GetPool(ConnectionString2, _password); sessionPool1.SetTimeout(3600); sessionPool2.SetTimeout(3600); - + // Act/Assert Assert.AreEqual(3600,_connectionPoolManager.GetTimeout()); } @@ -239,14 +226,14 @@ public void TestGetMaxPoolSizeOnManagerLevelWhenNotAllPoolsEqual() var sessionPool2 = _connectionPoolManager.GetPool(ConnectionString2, _password); sessionPool1.SetMaxPoolSize(1); sessionPool2.SetMaxPoolSize(17); - + // Act/Assert var exception = Assert.Throws(() => _connectionPoolManager.GetMaxPoolSize()); Assert.IsNotNull(exception); Assert.AreEqual(SFError.INCONSISTENT_RESULT_ERROR.GetAttribute().errorCode, exception.ErrorCode); Assert.IsTrue(exception.Message.Contains("Multiple pools have different Max Pool Size values")); } - + [Test] public void TestGetMaxPoolSizeOnManagerLevelWhenAllPoolsEqual() { @@ -255,25 +242,25 @@ public void TestGetMaxPoolSizeOnManagerLevelWhenAllPoolsEqual() var sessionPool2 = _connectionPoolManager.GetPool(ConnectionString2, _password); sessionPool1.SetMaxPoolSize(33); sessionPool2.SetMaxPoolSize(33); - + // Act/Assert Assert.AreEqual(33,_connectionPoolManager.GetMaxPoolSize()); } [Test] - public void TestGetCurrentPoolSizeThrowsExceptionWhenNotAllPoolsEqual() + public void TestGetCurrentPoolSizeReturnsSumOfPoolSizes() { // Arrange EnsurePoolSize(ConnectionString1, 2); EnsurePoolSize(ConnectionString2, 3); - - // Act/Assert - var exception = Assert.Throws(() => _connectionPoolManager.GetCurrentPoolSize()); - Assert.IsNotNull(exception); - Assert.AreEqual(SFError.INCONSISTENT_RESULT_ERROR.GetAttribute().errorCode, exception.ErrorCode); - Assert.IsTrue(exception.Message.Contains("Multiple pools have different Current Pool Size values")); + + // act + var poolSize = _connectionPoolManager.GetCurrentPoolSize(); + + // assert + Assert.AreEqual(5, poolSize); } - + private void EnsurePoolSize(string connectionString, int requiredCurrentSize) { var sessionPool = _connectionPoolManager.GetPool(connectionString, _password); diff --git a/Snowflake.Data.Tests/Util/PoolConfig.cs b/Snowflake.Data.Tests/Util/PoolConfig.cs index 078b6e359..4291c2f81 100644 --- a/Snowflake.Data.Tests/Util/PoolConfig.cs +++ b/Snowflake.Data.Tests/Util/PoolConfig.cs @@ -3,6 +3,7 @@ */ using Snowflake.Data.Client; +using Snowflake.Data.Core; using Snowflake.Data.Core.Session; namespace Snowflake.Data.Tests.Util @@ -16,15 +17,17 @@ class PoolConfig public PoolConfig() { - _maxPoolSize = SnowflakeDbConnectionPool.GetMaxPoolSize(); - _timeout = SnowflakeDbConnectionPool.GetTimeout(); - _pooling = SnowflakeDbConnectionPool.GetPooling(); - _connectionPoolType = SnowflakeDbConnectionPool.GetConnectionPoolVersion(); + _maxPoolSize = SFSessionHttpClientProperties.DefaultMaxPoolSize; + _timeout = (long) SFSessionHttpClientProperties.DefaultExpirationTimeout.TotalSeconds; + _pooling = SFSessionHttpClientProperties.DefaultPoolingEnabled; + _connectionPoolType = SnowflakeDbConnectionPool.DefaultConnectionPoolType; } public void Reset() { SnowflakeDbConnectionPool.SetConnectionPoolVersion(_connectionPoolType); + if (_connectionPoolType == ConnectionPoolType.MultipleConnectionPool) + return; // for multiple connection pool setting parameters for all the pools doesn't work by design SnowflakeDbConnectionPool.SetMaxPoolSize(_maxPoolSize); SnowflakeDbConnectionPool.SetTimeout(_timeout); SnowflakeDbConnectionPool.SetPooling(_pooling); diff --git a/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs b/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs index 0355ddd92..e3c21e20a 100644 --- a/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs +++ b/Snowflake.Data/Client/SnowflakeDbConnectionPool.cs @@ -17,7 +17,7 @@ public class SnowflakeDbConnectionPool private static readonly SFLogger s_logger = SFLoggerFactory.GetLogger(); private static readonly Object s_connectionManagerInstanceLock = new Object(); private static IConnectionManager s_connectionManager; - private const ConnectionPoolType DefaultConnectionPoolType = ConnectionPoolType.SingleConnectionCache; // TODO: set to MultipleConnectionPool once development of entire ConnectionPoolManager epic is complete + internal const ConnectionPoolType DefaultConnectionPoolType = ConnectionPoolType.MultipleConnectionPool; private static IConnectionManager ConnectionManager { @@ -29,13 +29,13 @@ private static IConnectionManager ConnectionManager return s_connectionManager; } } - + internal static SFSession GetSession(string connectionString, SecureString password) { s_logger.Debug($"SnowflakeDbConnectionPool::GetSession"); return ConnectionManager.GetSession(connectionString, password); } - + internal static Task GetSessionAsync(string connectionString, SecureString password, CancellationToken cancellationToken) { s_logger.Debug($"SnowflakeDbConnectionPool::GetSessionAsync"); @@ -47,7 +47,7 @@ internal static SessionPool GetPool(string connectionString) s_logger.Debug($"SnowflakeDbConnectionPool::GetPool"); return ConnectionManager.GetPool(connectionString); } - + internal static bool AddSession(SFSession session) { s_logger.Debug("SnowflakeDbConnectionPool::AddSession"); @@ -83,7 +83,7 @@ public static void SetTimeout(long connectionTimeout) s_logger.Debug("SnowflakeDbConnectionPool::SetTimeout"); ConnectionManager.SetTimeout(connectionTimeout); } - + public static long GetTimeout() { s_logger.Debug("SnowflakeDbConnectionPool::GetTimeout"); @@ -108,9 +108,9 @@ public static bool GetPooling() return ConnectionManager.GetPooling(); } - internal static void SetOldConnectionPoolVersion() // TODO: set to public once development of entire ConnectionPoolManager epic is complete + public static void SetOldConnectionPoolVersion() { - SetConnectionPoolVersion(ConnectionPoolType.SingleConnectionCache); + SetConnectionPoolVersion(ConnectionPoolType.SingleConnectionCache); } internal static void SetConnectionPoolVersion(ConnectionPoolType requestedPoolType) diff --git a/Snowflake.Data/Core/Session/ConnectionPoolManager.cs b/Snowflake.Data/Core/Session/ConnectionPoolManager.cs index fae78a014..388714876 100644 --- a/Snowflake.Data/Core/Session/ConnectionPoolManager.cs +++ b/Snowflake.Data/Core/Session/ConnectionPoolManager.cs @@ -27,7 +27,7 @@ internal ConnectionPoolManager() _pools = new Dictionary(); } } - + public SFSession GetSession(string connectionString, SecureString password) { s_logger.Debug($"ConnectionPoolManager::GetSession"); @@ -57,11 +57,11 @@ public void ClearAllPools() s_logger.Debug("ConnectionPoolManager::ClearAllPools"); foreach (var sessionPool in _pools.Values) { - sessionPool.ClearSessions(); + sessionPool.ClearSessions(); } _pools.Clear(); } - + public void SetMaxPoolSize(int maxPoolSize) { throw s_operationNotAvailable; @@ -71,9 +71,15 @@ public int GetMaxPoolSize() { s_logger.Debug("ConnectionPoolManager::GetMaxPoolSize"); var values = _pools.Values.Select(it => it.GetMaxPoolSize()).Distinct().ToList(); - return values.Count == 1 - ? values.First() - : throw new SnowflakeDbException(SFError.INCONSISTENT_RESULT_ERROR, "Multiple pools have different Max Pool Size values"); + switch (values.Count) + { + case 0: + return SFSessionHttpClientProperties.DefaultMaxPoolSize; + case 1: + return values.First(); + default: + throw new SnowflakeDbException(SFError.INCONSISTENT_RESULT_ERROR, "Multiple pools have different Max Pool Size values"); + } } public void SetTimeout(long connectionTimeout) @@ -85,18 +91,21 @@ public long GetTimeout() { s_logger.Debug("ConnectionPoolManager::GetTimeout"); var values = _pools.Values.Select(it => it.GetTimeout()).Distinct().ToList(); - return values.Count == 1 - ? values.First() - : throw new SnowflakeDbException(SFError.INCONSISTENT_RESULT_ERROR, "Multiple pools have different Timeout values"); + switch (values.Count) + { + case 0: + return (long) SFSessionHttpClientProperties.DefaultExpirationTimeout.TotalSeconds; + case 1: + return values.First(); + default: + throw new SnowflakeDbException(SFError.INCONSISTENT_RESULT_ERROR, "Multiple pools have different Timeout values"); + } } public int GetCurrentPoolSize() { s_logger.Debug("ConnectionPoolManager::GetCurrentPoolSize"); - var values = _pools.Values.Select(it => it.GetCurrentPoolSize()).Distinct().ToList(); - return values.Count == 1 - ? values.First() - : throw new SnowflakeDbException(SFError.INCONSISTENT_RESULT_ERROR, "Multiple pools have different Current Pool Size values"); + return _pools.Values.Select(it => it.GetCurrentPoolSize()).Sum(); } public bool SetPooling(bool poolingEnabled) @@ -104,20 +113,17 @@ public bool SetPooling(bool poolingEnabled) throw s_operationNotAvailable; } - public bool GetPooling() + public bool GetPooling() { s_logger.Debug("ConnectionPoolManager::GetPooling"); - var values = _pools.Values.Select(it => it.GetPooling()).Distinct().ToList(); - return values.Count == 1 - ? values.First() - : throw new SnowflakeDbException(SFError.INCONSISTENT_RESULT_ERROR, "Multiple pools have different Pooling values"); + return true; // in new pool pooling is always enabled by default, disabling only by connection string parameter } internal SessionPool GetPool(string connectionString, SecureString password) { s_logger.Debug($"ConnectionPoolManager::GetPool"); var poolKey = GetPoolKey(connectionString); - + if (_pools.TryGetValue(poolKey, out var item)) return item; lock (s_poolsLock)