Skip to content

Commit

Permalink
SNOW-860872 connection pool (#955)
Browse files Browse the repository at this point in the history
### Description
New connection pool.

### Checklist
- [x] Code compiles correctly
- [x] Code is formatted according to [Coding
Conventions](../blob/master/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

---------

Co-authored-by: Michał Hofman <[email protected]>
Co-authored-by: Dariusz Stempniak <[email protected]>
  • Loading branch information
3 people authored Jun 12, 2024
1 parent d158fd4 commit 1465bda
Show file tree
Hide file tree
Showing 102 changed files with 8,370 additions and 2,454 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ jobs:
run: |
cd Snowflake.Data.Tests
dotnet restore
dotnet build -f ${{ matrix.dotnet }}
dotnet build -f ${{ matrix.dotnet }} '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT'
- name: Run Tests
run: |
cd Snowflake.Data.Tests
Expand Down Expand Up @@ -119,7 +119,7 @@ jobs:
- name: Build Driver
run: |
dotnet restore
dotnet build
dotnet build '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT'
- name: Run Tests
run: |
cd Snowflake.Data.Tests
Expand Down Expand Up @@ -178,7 +178,7 @@ jobs:
- name: Build Driver
run: |
dotnet restore
dotnet build
dotnet build '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT'
- name: Run Tests
run: |
cd Snowflake.Data.Tests
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
repos:
- repo: [email protected]:snowflakedb/casec_precommit.git
rev: v1.20
rev: v1.35.4
hooks:
- id: secret-scanner
12 changes: 12 additions & 0 deletions CodingConventions.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,18 @@ public class ExampleClass
}
```

#### Property

Use PascalCase, eg. `SomeProperty`.

```csharp
public ExampleProperty
{
get;
set;
}
```

### Local variables

Use camelCase, eg. `someVariable`.
Expand Down
936 changes: 29 additions & 907 deletions README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,197 @@
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.Log;
using Snowflake.Data.Tests.Mock;
using Snowflake.Data.Tests.Util;

namespace Snowflake.Data.Tests.IntegrationTests
{
[TestFixture]
[NonParallelizable]
public class ConnectionMultiplePoolsAsyncIT: SFBaseTestAsync
{
private readonly PoolConfig _previousPoolConfig = new PoolConfig();
private readonly SFLogger logger = SFLoggerFactory.GetLogger<SFConnectionIT>();

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

[TearDown]
public new void AfterTest()
{
_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 TestFailForInvalidConnectionAsync()
{
// 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 {}
var thrown = Assert.Throws<SnowflakeDbException>(() => SnowflakeDbConnectionPool.GetPool(connection.ConnectionString));

// assert
Assert.That(thrown.Message, Does.Contain("Required property ACCOUNT is not provided"));
}

[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<object>(i);
}
}
}
}
catch (SnowflakeDbException)
{
// fail the test case if anything wrong.
Assert.Fail();
}
}
}

[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);
}

[Test]
public async Task TestPreventConnectionFromReturningToPool()
{
// arrange
var connectionString = ConnectionString + "minPoolSize=0";
var connection = new SnowflakeDbConnection(connectionString);
await connection.OpenAsync().ConfigureAwait(false);
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
Assert.AreEqual(1, pool.GetCurrentPoolSize());

// act
connection.PreventPooling();
await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false);

// assert
Assert.AreEqual(0, pool.GetCurrentPoolSize());
}

[Test]
public async Task TestReleaseConnectionWhenRollbackFailsAsync()
{
// arrange
var connectionString = ConnectionString + "minPoolSize=0";
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
var commandThrowingExceptionOnlyForRollback = MockHelper.CommandThrowingExceptionOnlyForRollback();
var mockDbProviderFactory = new Mock<DbProviderFactory>();
mockDbProviderFactory.Setup(p => p.CreateCommand()).Returns(commandThrowingExceptionOnlyForRollback.Object);
Assert.AreEqual(0, pool.GetCurrentPoolSize());
var connection = new TestSnowflakeDbConnection(mockDbProviderFactory.Object);
connection.ConnectionString = connectionString;
await connection.OpenAsync().ConfigureAwait(false);
connection.BeginTransaction(); // not using async version because it is not available on .net framework
Assert.AreEqual(true, connection.HasActiveExplicitTransaction());

// act
await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false);

// 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);
}
}
}
Loading

0 comments on commit 1465bda

Please sign in to comment.