Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SNOW-937190 Wait for idle sessions available #840

Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
ee2bff8
SNOW-937190 Wait for idle sessions available
sfc-gh-knozderko Nov 23, 2023
69d2545
add assert to check if the pool is empty at the beginning
sfc-gh-knozderko Dec 19, 2023
939f9f7
test with unique connection string per test
sfc-gh-knozderko Dec 20, 2023
b071399
fix
sfc-gh-knozderko Dec 20, 2023
2bb889f
some improvements
sfc-gh-knozderko Dec 20, 2023
1b763f5
debug info
sfc-gh-knozderko Jan 2, 2024
8eb34fd
for GH testing
sfc-gh-knozderko Jan 2, 2024
99799bc
fix waiting loop
sfc-gh-knozderko Jan 2, 2024
f5c1c23
fix timeout units
sfc-gh-knozderko Jan 2, 2024
1c3536d
fix TestDecreaseResources
sfc-gh-knozderko Jan 2, 2024
867bb25
more debug
sfc-gh-knozderko Jan 2, 2024
74a0f0c
time based stopwatch
sfc-gh-knozderko Jan 2, 2024
a949e35
use events for better tests
sfc-gh-knozderko Jan 3, 2024
9593408
tiny changes
sfc-gh-knozderko Jan 3, 2024
a567465
tiny change
sfc-gh-knozderko Jan 3, 2024
8e82261
fifo waiting queue
sfc-gh-knozderko Jan 3, 2024
a01a485
thread D delayed to C
sfc-gh-knozderko Jan 4, 2024
ee2f57c
clean debug changes
sfc-gh-knozderko Jan 4, 2024
826bbe7
more cleaning
sfc-gh-knozderko Jan 4, 2024
6c430c5
read write lock
sfc-gh-knozderko Jan 4, 2024
f8934ec
changes after review/improvements
sfc-gh-knozderko Jan 8, 2024
31b3899
rename classes, little refactors
sfc-gh-knozderko Jan 8, 2024
e583906
fix flaky tests
sfc-gh-knozderko Jan 9, 2024
a0b141d
little renaming
sfc-gh-knozderko Jan 9, 2024
f3b9a3e
fix tests
sfc-gh-knozderko Jan 9, 2024
ecf56fc
fix tests
sfc-gh-knozderko Jan 9, 2024
24a6947
tune tests
sfc-gh-knozderko Jan 10, 2024
68d1c68
fix test, more visibility in tests
sfc-gh-knozderko Jan 10, 2024
7bbdb17
rewrite test
sfc-gh-knozderko Jan 10, 2024
c425b2c
fix tests
sfc-gh-knozderko Jan 11, 2024
3ac7b88
fix comments
sfc-gh-knozderko Jan 11, 2024
144de1a
litle rename
sfc-gh-knozderko Jan 11, 2024
9f585a2
changes after review
sfc-gh-knozderko Jan 25, 2024
284b36a
fix cancellation case
sfc-gh-knozderko Jan 26, 2024
d42f4d5
fix cancellation case
sfc-gh-knozderko Jan 29, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 88 additions & 33 deletions Snowflake.Data.Tests/IntegrationTests/ConnectionMultiplePoolsIT.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
using System;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.Threading.Tasks;
using NUnit.Framework;
using Snowflake.Data.Client;
using Snowflake.Data.Core.Session;
Expand Down Expand Up @@ -102,24 +102,56 @@ public void TestReuseSessionInConnectionPoolReachingMaxConnections() // old name
public void TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimit()
{
// arrange
var connectionString = ConnectionString + "application=TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimit";
var connectionString = ConnectionString + "application=TestWaitForMaxSize1";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "expecting pool to be empty");
pool.SetMaxPoolSize(2);
pool.SetWaitingTimeout(1000);
pool.SetWaitingForSessionToReuseTimeout(1000);
var conn1 = OpenedConnection(connectionString);
var conn2 = OpenedConnection(connectionString);
var watch = new Stopwatch();
var watch = new StopWatch();

// act
watch.Start();
var thrown = Assert.Throws<SnowflakeDbException>(() => OpenedConnection());
var start = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
var thrown = Assert.Throws<SnowflakeDbException>(() => OpenedConnection(connectionString));
var stop = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
watch.Stop();

// assert
Assert.That(thrown.Message, Does.Contain("Unable to connect. Could not obtain a connection from the pool within a given timeout"));
Assert.GreaterOrEqual(watch.ElapsedMilliseconds, 1000);
Assert.LessOrEqual(watch.ElapsedMilliseconds, 1500);
Assert.That(watch.ElapsedMilliseconds, Is.InRange(1000, 1500));
Assert.AreEqual(pool.GetCurrentPoolSize(), 2);

// cleanup
conn1.Close();
conn2.Close();
}

[Test]
public void TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimitAsync()
{
// arrange
var connectionString = ConnectionString + "application=TestWaitForMaxSize2";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "expecting pool to be empty");
pool.SetMaxPoolSize(2);
pool.SetWaitingForSessionToReuseTimeout(1000);
var conn1 = OpenedConnection(connectionString);
var conn2 = OpenedConnection(connectionString);
var watch = new StopWatch();

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

// assert
Assert.That(thrown.Message, Does.Contain("Unable to connect"));
Assert.IsTrue(thrown.InnerException is AggregateException);
var nextedException = ((AggregateException)thrown.InnerException).InnerException;
Assert.That(nextedException.Message, Does.Contain("Could not obtain a connection from the pool within a given timeout"));
Assert.That(watch.ElapsedMilliseconds, Is.InRange(1000, 1500));
Assert.AreEqual(pool.GetCurrentPoolSize(), 2);

// cleanup
Expand All @@ -131,41 +163,56 @@ public void TestWaitForTheIdleConnectionWhenExceedingMaxConnectionsLimit()
public void TestWaitInAQueueForAnIdleSession()
{
// arrange
var connectionString = ConnectionString + "application=TestWaitInAQueueForAnIdleSession";
var connectionString = ConnectionString + "application=TestWaitForMaxSize3";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "the pool is expected to be empty");
pool.SetMaxPoolSize(2);
pool.SetWaitingTimeout(3000);
pool.SetWaitingForSessionToReuseTimeout(3000);
const long ADelay = 0;
const long BDelay = 400;
const long CDelay = 2 * BDelay;
const long DDelay = 3 * BDelay;
const long ABDelayAfterConnect = 2000;
const long ConnectPessimisticEstimate = 1300;
const long StartDelayPessimisticEstimate = 350;
const long AMinConnectionReleaseTime = ADelay + ABDelayAfterConnect; // 2000
const long AMaxConnectionReleaseTime = ADelay + StartDelayPessimisticEstimate + ConnectPessimisticEstimate + ABDelayAfterConnect; // 3650
const long BMinConnectionReleaseTime = BDelay + ABDelayAfterConnect; // 2400
const long BMaxConnectionReleaseTime = BDelay + StartDelayPessimisticEstimate + ConnectPessimisticEstimate + ABDelayAfterConnect; // 4050
const long CMinConnectDuration = AMinConnectionReleaseTime - CDelay - StartDelayPessimisticEstimate; // 2000 - 800 - 350 = 850
const long CMaxConnectDuration = AMaxConnectionReleaseTime - CDelay; // 3650 - 800 = 2850
const long DMinConnectDuration = BMinConnectionReleaseTime - DDelay - StartDelayPessimisticEstimate; // 2400 - 1200 - 350 = 850
const long DMaxConnectDuration = BMaxConnectionReleaseTime - DDelay; // 3650 - 800 = 2850

var threads = new ConnectingThreads(connectionString)
.NewThread("A", 0, 2000, true)
.NewThread("B", 50, 2000, true)
.NewThread("C", 100, 0, true)
.NewThread("D", 150, 0, true);
var watch = new Stopwatch();
.NewThread("A", ADelay, ABDelayAfterConnect, true)
.NewThread("B", BDelay, ABDelayAfterConnect, true)
.NewThread("C", CDelay, 0, true)
.NewThread("D", DDelay, 0, true);
pool.SetSessionPoolEventHandler(new SessionPoolThreadEventHandler(threads));

// act
watch.Start();
threads.StartAll().JoinAll();
watch.Stop();

// assert
var events = threads.Events().ToList();
Assert.AreEqual(4, events.Count);
CollectionAssert.AreEqual(
new[]
{
Tuple.Create("A", "CONNECTED"),
Tuple.Create("B", "CONNECTED"),
Tuple.Create("C", "CONNECTED"),
Tuple.Create("D", "CONNECTED")
},
events.Select(e => Tuple.Create(e.ThreadName, e.EventName)));
Assert.LessOrEqual(events[0].Duration, 1000);
Assert.LessOrEqual(events[1].Duration, 1000);
Assert.GreaterOrEqual(events[2].Duration, 2000);
Assert.LessOrEqual(events[2].Duration, 3100);
Assert.GreaterOrEqual(events[3].Duration, 2000);
Assert.LessOrEqual(events[3].Duration, 3100);
Assert.AreEqual(6, events.Count);
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
var connectedEvents = events.Where(e => e.IsConnectedEvent()).ToList();
Assert.AreEqual(4, connectedEvents.Count);
var firstConnectedEventsGroup = connectedEvents.GetRange(0, 2);
CollectionAssert.AreEquivalent(new[] { "A", "B" }, firstConnectedEventsGroup.Select(e => e.ThreadName));
var lastConnectingEventsGroup = connectedEvents.GetRange(2, 2);
CollectionAssert.AreEquivalent(new[] { "C", "D" }, lastConnectingEventsGroup.Select(e => e.ThreadName));
Assert.LessOrEqual(firstConnectedEventsGroup[0].Duration, ConnectPessimisticEstimate);
Assert.LessOrEqual(firstConnectedEventsGroup[1].Duration, ConnectPessimisticEstimate);
// first to wait from C and D should first to connect, because we won't create a new session, we just reuse sessions returned by A and B threads
Assert.AreEqual(waitingEvents[0].ThreadName, lastConnectingEventsGroup[0].ThreadName);
Assert.AreEqual(waitingEvents[1].ThreadName, lastConnectingEventsGroup[1].ThreadName);
Assert.That(lastConnectingEventsGroup[0].Duration, Is.InRange(CMinConnectDuration, CMaxConnectDuration));
Assert.That(lastConnectingEventsGroup[1].Duration, Is.InRange(DMinConnectDuration, DMaxConnectDuration));
}

[Test]
Expand Down Expand Up @@ -266,5 +313,13 @@ private SnowflakeDbConnection OpenedConnection(string connectionString)
connection.Open();
return connection;
}

private async Task<SnowflakeDbConnection> OpenedConnectionAsync(string connectionString)
{
var connection = new SnowflakeDbConnection();
connection.ConnectionString = connectionString;
await connection.OpenAsync().ConfigureAwait(false);
return connection;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@
namespace Snowflake.Data.Tests.UnitTests.Session
{
[TestFixture]
public class NotCountingCreateSessionTokensTest
public class NonCountingSessionCreationTokenCounterTest
{
[Test]
public void TestGrantSessionCreation()
{
// arrange
var tokens = new NotCountingCreateSessionTokens();
var tokens = new NonCountingSessionCreationTokenCounter();

// act
tokens.BeginCreate();
tokens.NewToken();

// assert
Assert.AreEqual(0, tokens.Count());
Expand All @@ -23,11 +23,11 @@ public void TestGrantSessionCreation()
public void TestCompleteSessionCreation()
{
// arrange
var tokens = new NotCountingCreateSessionTokens();
var token = tokens.BeginCreate();
var tokens = new NonCountingSessionCreationTokenCounter();
var token = tokens.NewToken();

// act
tokens.EndCreate(token);
tokens.RemoveToken(token);

// assert
Assert.AreEqual(0, tokens.Count());
Expand All @@ -37,12 +37,12 @@ public void TestCompleteSessionCreation()
public void TestCompleteUnknownTokenDoesNotThrowExceptions()
{
// arrange
var tokens = new NotCountingCreateSessionTokens();
tokens.BeginCreate();
var unknownToken = new CreateSessionToken(0);
var tokens = new NonCountingSessionCreationTokenCounter();
tokens.NewToken();
var unknownToken = new SessionCreationToken(0);

// act
tokens.EndCreate(unknownToken);
tokens.RemoveToken(unknownToken);

// assert
Assert.AreEqual(0, tokens.Count());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,18 @@
namespace Snowflake.Data.Tests.UnitTests.Session
{
[TestFixture]
public class NoOneWaitingTest
public class NonWaitingQueueTest
{
[Test]
public void TestWaitDoesNotHangAndReturnsFalse()
{
// arrange
var noOneWaiting = new NoOneWaiting();
var nonWaitingQueue = new NonWaitingQueue();
var watch = new Stopwatch();

// act
watch.Start();
var result = noOneWaiting.Wait(10000, CancellationToken.None);
var result = nonWaitingQueue.Wait(10000, CancellationToken.None);
watch.Stop();

// assert
Expand All @@ -29,11 +29,11 @@ public void TestWaitDoesNotHangAndReturnsFalse()
public void TestNoOneIsWaiting()
{
// arrange
var noOneWaiting = new NoOneWaiting();
noOneWaiting.Wait(10000, CancellationToken.None);
var nonWaitingQueue = new NonWaitingQueue();
nonWaitingQueue.Wait(10000, CancellationToken.None);

// act
var isAnyoneWaiting = noOneWaiting.IsAnyoneWaiting();
var isAnyoneWaiting = nonWaitingQueue.IsAnyoneWaiting();

// assert
Assert.IsFalse(isAnyoneWaiting);
Expand All @@ -43,10 +43,10 @@ public void TestNoOneIsWaiting()
public void TestWaitingDisabled()
{
// arrange
var noOneWaiting = new NoOneWaiting();
var nonWaitingQueue = new NonWaitingQueue();

// act
var isWaitingEnabled = noOneWaiting.IsWaitingEnabled();
var isWaitingEnabled = nonWaitingQueue.IsWaitingEnabled();

// assert
Assert.IsFalse(isWaitingEnabled);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,25 @@
namespace Snowflake.Data.Tests.UnitTests.Session
{
[TestFixture]
public class CreateSessionTokensTest
public class SessionCreationTokenCounterTest
{
private const long LongTimeAsMillis = 30000;
private const long ShortTimeAsMillis = 50;

[Test]
public void TestGrantSessionCreation()
{
// arrange
var tokens = new CreateSessionTokens();
var tokens = new SessionCreationTokenCounter(LongTimeAsMillis);

// act
tokens.BeginCreate();
tokens.NewToken();

// assert
Assert.AreEqual(1, tokens.Count());

// act
tokens.BeginCreate();
tokens.NewToken();

// assert
Assert.AreEqual(2, tokens.Count());
Expand All @@ -30,18 +33,18 @@ public void TestGrantSessionCreation()
public void TestCompleteSessionCreation()
{
// arrange
var tokens = new CreateSessionTokens();
var token1 = tokens.BeginCreate();
var token2 = tokens.BeginCreate();
var tokens = new SessionCreationTokenCounter(LongTimeAsMillis);
var token1 = tokens.NewToken();
var token2 = tokens.NewToken();

// act
tokens.EndCreate(token1);
tokens.RemoveToken(token1);

// assert
Assert.AreEqual(1, tokens.Count());

// act
tokens.EndCreate(token2);
tokens.RemoveToken(token2);

// assert
Assert.AreEqual(0, tokens.Count());
Expand All @@ -51,12 +54,12 @@ public void TestCompleteSessionCreation()
public void TestCompleteUnknownTokenDoesNotThrowExceptions()
{
// arrange
var tokens = new CreateSessionTokens();
tokens.BeginCreate();
var unknownToken = new CreateSessionToken(0);
var tokens = new SessionCreationTokenCounter(LongTimeAsMillis);
tokens.NewToken();
var unknownToken = new SessionCreationToken(0);

// act
tokens.EndCreate(unknownToken);
tokens.RemoveToken(unknownToken);

// assert
Assert.AreEqual(1, tokens.Count());
Expand All @@ -66,15 +69,14 @@ public void TestCompleteUnknownTokenDoesNotThrowExceptions()
public void TestCompleteCleansExpiredTokens()
{
// arrange
var tokens = new CreateSessionTokens();
tokens._timeout = 50;
var token = tokens.BeginCreate();
tokens.BeginCreate(); // this token will be cleaned because of expiration
var tokens = new SessionCreationTokenCounter(ShortTimeAsMillis);
var token = tokens.NewToken();
tokens.NewToken(); // this token will be cleaned because of expiration
Assert.AreEqual(2, tokens.Count());
Thread.Sleep((int) tokens._timeout);
Thread.Sleep((int) ShortTimeAsMillis + 5);

// act
tokens.EndCreate(token);
tokens.RemoveToken(token);

// assert
Assert.AreEqual(0, tokens.Count());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,15 @@
namespace Snowflake.Data.Tests.UnitTests.Session
{
[TestFixture]
public class CreateSessionTokenTest
public class SessionCreationTokenTest
{
private readonly long _timeout = 30000; // 30 seconds in millis
private const long Timeout30SecondsAsMillis = 30000;

[Test]
public void TestTokenIsNotExpired()
{
// arrange
var token = new CreateSessionToken(_timeout);
var token = new SessionCreationToken(Timeout30SecondsAsMillis);

// act
var isExpired = token.IsExpired(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds());
Expand All @@ -26,10 +26,10 @@ public void TestTokenIsNotExpired()
public void TestTokenIsExpired()
{
// arrange
var token = new CreateSessionToken(_timeout);
var token = new SessionCreationToken(Timeout30SecondsAsMillis);

// act
var isExpired = token.IsExpired(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + _timeout + 1);
var isExpired = token.IsExpired(DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() + Timeout30SecondsAsMillis + 1);

// assert
Assert.IsTrue(isExpired);
Expand Down
Loading
Loading