Skip to content

Commit

Permalink
Pool/snow-902610 min pool size (#880)
Browse files Browse the repository at this point in the history
### Description
Min pool size

### Checklist
- [x] Code compiles correctly
- [x] Code is formatted according to [Coding
Conventions](../CodingConventions.md)
- [x] Created tests which fail without the change (if possible)
- [x] All tests passing (`dotnet test`)
- [x] Extended the README / documentation, if necessary
- [x] Provide JIRA issue id (if possible) or GitHub issue id in PR name
  • Loading branch information
sfc-gh-knozderko authored Apr 3, 2024
1 parent 052d895 commit 0983d94
Show file tree
Hide file tree
Showing 17 changed files with 431 additions and 97 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Snowflake.Data.Client;
using Snowflake.Data.Core.Session;
using Snowflake.Data.Tests.Util;

namespace Snowflake.Data.Tests.IntegrationTests
{
[TestFixture]
[NonParallelizable]
public class ConnectionMultiplePoolsAsyncIT: SFBaseTestAsync
{
private readonly PoolConfig _previousPoolConfig = new PoolConfig();

[SetUp]
public new void BeforeTest()
{
SnowflakeDbConnectionPool.SetConnectionPoolVersion(ConnectionPoolType.MultipleConnectionPool);
SnowflakeDbConnectionPool.ClearAllPools();
}

[TearDown]
public new void AfterTest()
{
_previousPoolConfig.Reset();
}

[Test]
public async Task TestMinPoolSizeAsync()
{
// arrange
var connection = new SnowflakeDbConnection();
connection.ConnectionString = ConnectionString + "application=TestMinPoolSizeAsync;minPoolSize=3";

// act
await connection.OpenAsync().ConfigureAwait(false);
Thread.Sleep(3000);

// assert
var pool = SnowflakeDbConnectionPool.GetPool(connection.ConnectionString);
Assert.AreEqual(3, pool.GetCurrentPoolSize());

// cleanup
await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false);
}
}
}
127 changes: 87 additions & 40 deletions Snowflake.Data.Tests/IntegrationTests/ConnectionMultiplePoolsIT.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Data;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Snowflake.Data.Client;
Expand Down Expand Up @@ -51,37 +52,38 @@ public void TestBasicConnectionPool()
[Test]
public void TestReuseSessionInConnectionPool() // old name: TestConnectionPool
{
var conn1 = new SnowflakeDbConnection(ConnectionString);
var connectionString = ConnectionString + "minPoolSize=1";
var conn1 = new SnowflakeDbConnection(connectionString);
conn1.Open();
Assert.AreEqual(ConnectionState.Open, conn1.State);
conn1.Close();
Assert.AreEqual(1, SnowflakeDbConnectionPool.GetPool(ConnectionString).GetCurrentPoolSize());
Assert.AreEqual(1, SnowflakeDbConnectionPool.GetPool(connectionString).GetCurrentPoolSize());

var conn2 = new SnowflakeDbConnection();
conn2.ConnectionString = ConnectionString;
conn2.ConnectionString = connectionString;
conn2.Open();
Assert.AreEqual(ConnectionState.Open, conn2.State);
Assert.AreEqual(1, SnowflakeDbConnectionPool.GetPool(ConnectionString).GetCurrentPoolSize());
Assert.AreEqual(1, SnowflakeDbConnectionPool.GetPool(connectionString).GetCurrentPoolSize());

conn2.Close();
Assert.AreEqual(1, SnowflakeDbConnectionPool.GetPool(ConnectionString).GetCurrentPoolSize());
Assert.AreEqual(1, SnowflakeDbConnectionPool.GetPool(connectionString).GetCurrentPoolSize());
Assert.AreEqual(ConnectionState.Closed, conn1.State);
Assert.AreEqual(ConnectionState.Closed, conn2.State);
}

[Test]
public void TestReuseSessionInConnectionPoolReachingMaxConnections() // old name: TestConnectionPoolFull
{
var pool = SnowflakeDbConnectionPool.GetPool(ConnectionString);
pool.SetMaxPoolSize(2);
var connectionString = ConnectionString + "maxPoolSize=2;minPoolSize=1";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);

var conn1 = new SnowflakeDbConnection();
conn1.ConnectionString = ConnectionString;
conn1.ConnectionString = connectionString;
conn1.Open();
Assert.AreEqual(ConnectionState.Open, conn1.State);

var conn2 = new SnowflakeDbConnection();
conn2.ConnectionString = ConnectionString;
conn2.ConnectionString = connectionString;
conn2.Open();
Assert.AreEqual(ConnectionState.Open, conn2.State);

Expand All @@ -91,12 +93,12 @@ public void TestReuseSessionInConnectionPoolReachingMaxConnections() // old name
Assert.AreEqual(2, pool.GetCurrentPoolSize());

var conn3 = new SnowflakeDbConnection();
conn3.ConnectionString = ConnectionString;
conn3.ConnectionString = connectionString;
conn3.Open();
Assert.AreEqual(ConnectionState.Open, conn3.State);

var conn4 = new SnowflakeDbConnection();
conn4.ConnectionString = ConnectionString;
conn4.ConnectionString = connectionString;
conn4.Open();
Assert.AreEqual(ConnectionState.Open, conn4.State);

Expand All @@ -115,16 +117,16 @@ public void TestReuseSessionInConnectionPoolReachingMaxConnections() // old name
public void TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimit()
{
// arrange
var connectionString = ConnectionString + "application=TestWaitForMaxSize1;waitingForIdleSessionTimeout=1s;maxPoolSize=2";
var connectionString = ConnectionString + "application=TestWaitForMaxSize1;waitingForIdleSessionTimeout=1s;maxPoolSize=2;minPoolSize=1";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "expecting pool to be empty");
var conn1 = OpenedConnection(connectionString);
var conn2 = OpenedConnection(connectionString);
var conn1 = OpenConnection(connectionString);
var conn2 = OpenConnection(connectionString);
var watch = new StopWatch();

// act
watch.Start();
var thrown = Assert.Throws<SnowflakeDbException>(() => OpenedConnection(connectionString));
var thrown = Assert.Throws<SnowflakeDbException>(() => OpenConnection(connectionString));
watch.Stop();

// assert
Expand All @@ -141,16 +143,16 @@ public void TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimit()
public void TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimitAsync()
{
// arrange
var connectionString = ConnectionString + "application=TestWaitForMaxSize2;waitingForIdleSessionTimeout=1s;maxPoolSize=2";
var connectionString = ConnectionString + "application=TestWaitForMaxSize2;waitingForIdleSessionTimeout=1s;maxPoolSize=2;minPoolSize=1";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "expecting pool to be empty");
var conn1 = OpenedConnection(connectionString);
var conn2 = OpenedConnection(connectionString);
var conn1 = OpenConnection(connectionString);
var conn2 = OpenConnection(connectionString);
var watch = new StopWatch();

// act
watch.Start();
var thrown = Assert.ThrowsAsync<SnowflakeDbException>(() => OpenedConnectionAsync(connectionString));
var thrown = Assert.ThrowsAsync<SnowflakeDbException>(() => OpenConnectionAsync(connectionString));
watch.Stop();

// assert
Expand All @@ -170,7 +172,7 @@ public void TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimitAsync()
public void TestWaitInAQueueForAnIdleSession()
{
// arrange
var connectionString = ConnectionString + "application=TestWaitForMaxSize3;waitingForIdleSessionTimeout=3s;maxPoolSize=2";
var connectionString = ConnectionString + "application=TestWaitForMaxSize3;waitingForIdleSessionTimeout=3s;maxPoolSize=2;minPoolSize=0";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "the pool is expected to be empty");
const long ADelay = 0;
Expand Down Expand Up @@ -201,7 +203,7 @@ public void TestWaitInAQueueForAnIdleSession()

// assert
var events = threads.Events().ToList();
Assert.AreEqual(6, events.Count);
Assert.AreEqual(6, events.Count); // A,B - connected; C,D - waiting, connected
var waitingEvents = events.Where(e => e.IsWaitingEvent()).ToList();
Assert.AreEqual(2, waitingEvents.Count);
CollectionAssert.AreEquivalent(new[] { "C", "D" }, waitingEvents.Select(e => e.ThreadName)); // equivalent = in any order
Expand All @@ -224,10 +226,10 @@ public void TestWaitInAQueueForAnIdleSession()
public void TestBusyAndIdleConnectionsCountedInPoolSize()
{
// arrange
var pool = SnowflakeDbConnectionPool.GetPool(ConnectionString);
pool.SetMaxPoolSize(2);
var connectionString = ConnectionString + "maxPoolSize=2;minPoolSize=1";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
var connection = new SnowflakeDbConnection();
connection.ConnectionString = ConnectionString;
connection.ConnectionString = connectionString;

// act
connection.Open();
Expand Down Expand Up @@ -279,7 +281,7 @@ public void TestConnectionPoolDisable()
[Test]
public void TestNewConnectionPoolClean()
{
var connectionString = ConnectionString + "maxPoolSize=2;";
var connectionString = ConnectionString + "maxPoolSize=2;minPoolSize=1;";
var conn1 = new SnowflakeDbConnection();
conn1.ConnectionString = connectionString;
conn1.Open();
Expand Down Expand Up @@ -313,35 +315,80 @@ public void TestNewConnectionPoolClean()
[Test]
public void TestConnectionPoolExpirationWorks()
{
var connectionString = ConnectionString + "expirationTimeout=0;maxPoolSize=2";
var conn1 = new SnowflakeDbConnection();
conn1.ConnectionString = connectionString;
conn1.Open();
conn1.Close();
// arrange
const int ExpirationTimeoutInSeconds = 10;
var connectionString = ConnectionString + $"expirationTimeout={ExpirationTimeoutInSeconds};maxPoolSize=4;minPoolSize=2";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize());

var conn2 = new SnowflakeDbConnection();
conn2.ConnectionString = connectionString;
conn2.Open();
// act
var conn1 = OpenConnection(connectionString);
var conn2 = OpenConnection(connectionString);
var conn3 = OpenConnection(connectionString);
var conn4 = OpenConnection(connectionString);

// assert
Assert.AreEqual(4, pool.GetCurrentPoolSize());

// act
WaitUntilAllSessionsCreatedOrTimeout(pool);
var beforeSleepMillis = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds();
Thread.Sleep(TimeSpan.FromSeconds(ExpirationTimeoutInSeconds));
conn1.Close();
conn2.Close();

var conn3 = new SnowflakeDbConnection();
conn3.ConnectionString = connectionString;
conn3.Open();
conn3.Close();
conn4.Close();

// assert
Assert.AreEqual(2, pool.GetCurrentPoolSize()); // 2 idle sessions, but expired because close doesn't remove expired sessions

// act
WaitUntilAllSessionsCreatedOrTimeout(pool);
var conn5 = OpenConnection(connectionString);
WaitUntilAllSessionsCreatedOrTimeout(pool);

// assert
Assert.AreEqual(2, pool.GetCurrentPoolSize()); // 1 idle session and 1 busy
var sessionStartTimes = pool.GetIdleSessionsStartTimes();
Assert.AreEqual(1, sessionStartTimes.Count);
Assert.That(sessionStartTimes.First(), Is.GreaterThan(beforeSleepMillis));
Assert.That(conn5.SfSession.GetStartTime(), Is.GreaterThan(beforeSleepMillis));
}

[Test]
public void TestMinPoolSize()
{
// arrange
var connection = new SnowflakeDbConnection();
connection.ConnectionString = ConnectionString + "application=TestMinPoolSize;minPoolSize=3";

// act
connection.Open();
Thread.Sleep(3000);

// assert
Assert.AreEqual(0, SnowflakeDbConnectionPool.GetPool(connectionString).GetCurrentPoolSize());
var pool = SnowflakeDbConnectionPool.GetPool(connection.ConnectionString);
Assert.AreEqual(3, pool.GetCurrentPoolSize());

// cleanup
connection.Close();
}

private void WaitUntilAllSessionsCreatedOrTimeout(SessionPool pool)
{
var expectingToWaitAtMostForSessionCreations = TimeSpan.FromSeconds(15);
Awaiter.WaitUntilConditionOrTimeout(() => pool.OngoingSessionCreationsCount() == 0, expectingToWaitAtMostForSessionCreations);
}

private SnowflakeDbConnection OpenedConnection(string connectionString)
private SnowflakeDbConnection OpenConnection(string connectionString)
{
var connection = new SnowflakeDbConnection();
connection.ConnectionString = connectionString;
connection.Open();
return connection;
}

private async Task<SnowflakeDbConnection> OpenedConnectionAsync(string connectionString)
private async Task<SnowflakeDbConnection> OpenConnectionAsync(string connectionString)
{
var connection = new SnowflakeDbConnection();
connection.ConnectionString = connectionString;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ public void TestConnectionPoolWithDispose()
conn1.ConnectionString = "bad connection string";
Assert.Throws<SnowflakeDbException>(() => conn1.Open());
conn1.Close();
Thread.Sleep(3000); // minPoolSize = 2 causes that another thread has been started. We sleep to make that thread finish.

Assert.AreEqual(ConnectionState.Closed, conn1.State);
Assert.AreEqual(0, SnowflakeDbConnectionPool.GetPool(conn1.ConnectionString).GetCurrentPoolSize());
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
using System;
using System.Data;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using Snowflake.Data.Client;
Expand Down
4 changes: 2 additions & 2 deletions Snowflake.Data.Tests/UnitTests/ConnectionPoolManagerTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,8 @@ namespace Snowflake.Data.Tests.UnitTests
class ConnectionPoolManagerTest
{
private readonly ConnectionPoolManager _connectionPoolManager = new ConnectionPoolManager();
private const string ConnectionString1 = "database=D1;warehouse=W1;account=A1;user=U1;password=P1;role=R1;";
private const string ConnectionString2 = "database=D2;warehouse=W2;account=A2;user=U2;password=P2;role=R2;";
private const string ConnectionString1 = "database=D1;warehouse=W1;account=A1;user=U1;password=P1;role=R1;minPoolSize=1;";
private const string ConnectionString2 = "database=D2;warehouse=W2;account=A2;user=U2;password=P2;role=R2;minPoolSize=1;";
private readonly SecureString _password = new SecureString();
private static PoolConfig s_poolConfig;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -207,7 +207,6 @@ public void TestExtractConnectionTimeoutFailsForWrongValue(string propertyValue)
[TestCase("TRUE", true)]
[TestCase("false", false)]
[TestCase("FALSE", false)]
// [TestCase("0", false)]
public void TestExtractPoolingEnabled(string propertyValue, bool poolingEnabled)
{
// arrange
Expand Down Expand Up @@ -256,7 +255,7 @@ public TimeoutTestCase(string propertyValue, TimeSpan expectedTimeout)

public static IEnumerable<TimeoutTestCase> CorrectTimeoutsWithZeroUnchanged() =>
CorrectTimeoutsWithoutZero().Concat(ZeroUnchangedTimeouts());

public static IEnumerable<TimeoutTestCase> CorrectTimeoutsWithZeroAsInfinite() =>
CorrectTimeoutsWithoutZero().Concat(ZeroAsInfiniteTimeouts());

Expand Down
Loading

0 comments on commit 0983d94

Please sign in to comment.