Skip to content

Commit 1465bda

Browse files
sfc-gh-knozderkosfc-gh-mhofmansfc-gh-dstempniak
authored
SNOW-860872 connection pool (#955)
### 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]>
1 parent d158fd4 commit 1465bda

File tree

102 files changed

+8370
-2454
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

102 files changed

+8370
-2454
lines changed

.github/workflows/main.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,7 @@ jobs:
6060
run: |
6161
cd Snowflake.Data.Tests
6262
dotnet restore
63-
dotnet build -f ${{ matrix.dotnet }}
63+
dotnet build -f ${{ matrix.dotnet }} '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT'
6464
- name: Run Tests
6565
run: |
6666
cd Snowflake.Data.Tests
@@ -119,7 +119,7 @@ jobs:
119119
- name: Build Driver
120120
run: |
121121
dotnet restore
122-
dotnet build
122+
dotnet build '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT'
123123
- name: Run Tests
124124
run: |
125125
cd Snowflake.Data.Tests
@@ -178,7 +178,7 @@ jobs:
178178
- name: Build Driver
179179
run: |
180180
dotnet restore
181-
dotnet build
181+
dotnet build '-p:DefineAdditionalConstants=SF_PUBLIC_ENVIRONMENT'
182182
- name: Run Tests
183183
run: |
184184
cd Snowflake.Data.Tests

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
repos:
22
- repo: [email protected]:snowflakedb/casec_precommit.git
3-
rev: v1.20
3+
rev: v1.35.4
44
hooks:
55
- id: secret-scanner

CodingConventions.md

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,18 @@ public class ExampleClass
8585
}
8686
```
8787

88+
#### Property
89+
90+
Use PascalCase, eg. `SomeProperty`.
91+
92+
```csharp
93+
public ExampleProperty
94+
{
95+
get;
96+
set;
97+
}
98+
```
99+
88100
### Local variables
89101

90102
Use camelCase, eg. `someVariable`.

README.md

Lines changed: 29 additions & 907 deletions
Large diffs are not rendered by default.
Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
using System.Data.Common;
2+
using System.Threading;
3+
using System.Threading.Tasks;
4+
using Moq;
5+
using NUnit.Framework;
6+
using Snowflake.Data.Client;
7+
using Snowflake.Data.Core;
8+
using Snowflake.Data.Core.Session;
9+
using Snowflake.Data.Log;
10+
using Snowflake.Data.Tests.Mock;
11+
using Snowflake.Data.Tests.Util;
12+
13+
namespace Snowflake.Data.Tests.IntegrationTests
14+
{
15+
[TestFixture]
16+
[NonParallelizable]
17+
public class ConnectionMultiplePoolsAsyncIT: SFBaseTestAsync
18+
{
19+
private readonly PoolConfig _previousPoolConfig = new PoolConfig();
20+
private readonly SFLogger logger = SFLoggerFactory.GetLogger<SFConnectionIT>();
21+
22+
[SetUp]
23+
public new void BeforeTest()
24+
{
25+
SnowflakeDbConnectionPool.SetConnectionPoolVersion(ConnectionPoolType.MultipleConnectionPool);
26+
SnowflakeDbConnectionPool.ClearAllPools();
27+
}
28+
29+
[TearDown]
30+
public new void AfterTest()
31+
{
32+
_previousPoolConfig.Reset();
33+
}
34+
35+
[Test]
36+
public async Task TestAddToPoolOnOpenAsync()
37+
{
38+
// arrange
39+
var connection = new SnowflakeDbConnection(ConnectionString + "minPoolSize=1");
40+
41+
// act
42+
await connection.OpenAsync().ConfigureAwait(false);
43+
44+
// assert
45+
var pool = SnowflakeDbConnectionPool.GetPool(connection.ConnectionString);
46+
Assert.AreEqual(1, pool.GetCurrentPoolSize());
47+
48+
// cleanup
49+
await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false);
50+
}
51+
52+
[Test]
53+
public async Task TestFailForInvalidConnectionAsync()
54+
{
55+
// arrange
56+
var invalidConnectionString = ";connection_timeout=123";
57+
var connection = new SnowflakeDbConnection(invalidConnectionString);
58+
59+
// act
60+
try
61+
{
62+
await connection.OpenAsync().ConfigureAwait(false);
63+
Assert.Fail("OpenAsync should fail for invalid connection string");
64+
}
65+
catch {}
66+
var thrown = Assert.Throws<SnowflakeDbException>(() => SnowflakeDbConnectionPool.GetPool(connection.ConnectionString));
67+
68+
// assert
69+
Assert.That(thrown.Message, Does.Contain("Required property ACCOUNT is not provided"));
70+
}
71+
72+
[Test]
73+
public void TestConnectionPoolWithInvalidOpenAsync()
74+
{
75+
// make the connection string unique so it won't pick up connection
76+
// pooled by other test cases.
77+
string connStr = ConnectionString + "minPoolSize=0;maxPoolSize=10;application=conn_pool_test_invalid_openasync2";
78+
using (var connection = new SnowflakeDbConnection())
79+
{
80+
connection.ConnectionString = connStr;
81+
// call openAsync but do not wait and destroy it direct
82+
// so the session is initialized with empty token
83+
connection.OpenAsync();
84+
}
85+
86+
// use the same connection string to make a new connection
87+
// to ensure the invalid connection made previously is not pooled
88+
using (var connection1 = new SnowflakeDbConnection())
89+
{
90+
connection1.ConnectionString = connStr;
91+
// this will not open a new session but get the invalid connection from pool
92+
connection1.Open();
93+
// Now run query with connection1
94+
var command = connection1.CreateCommand();
95+
command.CommandText = "select 1, 2, 3";
96+
97+
try
98+
{
99+
using (var reader = command.ExecuteReader())
100+
{
101+
while (reader.Read())
102+
{
103+
for (int i = 0; i < reader.FieldCount; i++)
104+
{
105+
// Process each column as appropriate
106+
reader.GetFieldValue<object>(i);
107+
}
108+
}
109+
}
110+
}
111+
catch (SnowflakeDbException)
112+
{
113+
// fail the test case if anything wrong.
114+
Assert.Fail();
115+
}
116+
}
117+
}
118+
119+
[Test]
120+
public async Task TestMinPoolSizeAsync()
121+
{
122+
// arrange
123+
var connection = new SnowflakeDbConnection();
124+
connection.ConnectionString = ConnectionString + "application=TestMinPoolSizeAsync;minPoolSize=3";
125+
126+
// act
127+
await connection.OpenAsync().ConfigureAwait(false);
128+
Thread.Sleep(3000);
129+
130+
// assert
131+
var pool = SnowflakeDbConnectionPool.GetPool(connection.ConnectionString);
132+
Assert.AreEqual(3, pool.GetCurrentPoolSize());
133+
134+
// cleanup
135+
await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false);
136+
}
137+
138+
[Test]
139+
public async Task TestPreventConnectionFromReturningToPool()
140+
{
141+
// arrange
142+
var connectionString = ConnectionString + "minPoolSize=0";
143+
var connection = new SnowflakeDbConnection(connectionString);
144+
await connection.OpenAsync().ConfigureAwait(false);
145+
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
146+
Assert.AreEqual(1, pool.GetCurrentPoolSize());
147+
148+
// act
149+
connection.PreventPooling();
150+
await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false);
151+
152+
// assert
153+
Assert.AreEqual(0, pool.GetCurrentPoolSize());
154+
}
155+
156+
[Test]
157+
public async Task TestReleaseConnectionWhenRollbackFailsAsync()
158+
{
159+
// arrange
160+
var connectionString = ConnectionString + "minPoolSize=0";
161+
var pool = SnowflakeDbConnectionPool.GetPool(connectionString);
162+
var commandThrowingExceptionOnlyForRollback = MockHelper.CommandThrowingExceptionOnlyForRollback();
163+
var mockDbProviderFactory = new Mock<DbProviderFactory>();
164+
mockDbProviderFactory.Setup(p => p.CreateCommand()).Returns(commandThrowingExceptionOnlyForRollback.Object);
165+
Assert.AreEqual(0, pool.GetCurrentPoolSize());
166+
var connection = new TestSnowflakeDbConnection(mockDbProviderFactory.Object);
167+
connection.ConnectionString = connectionString;
168+
await connection.OpenAsync().ConfigureAwait(false);
169+
connection.BeginTransaction(); // not using async version because it is not available on .net framework
170+
Assert.AreEqual(true, connection.HasActiveExplicitTransaction());
171+
172+
// act
173+
await connection.CloseAsync(CancellationToken.None).ConfigureAwait(false);
174+
175+
// assert
176+
Assert.AreEqual(0, pool.GetCurrentPoolSize(), "Should not return connection to the pool");
177+
}
178+
179+
[Test(Description = "test connection pooling with concurrent connection using async calls")]
180+
public void TestConcurrentConnectionPoolingAsync()
181+
{
182+
// add test case name in connection string to make in unique for each test case
183+
// set short expiration timeout to cover the case that connection expired
184+
string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingAsync2;ExpirationTimeout=3";
185+
ConnectionSinglePoolCacheAsyncIT.ConcurrentPoolingAsyncHelper(connStr, true, 7, 100, 2);
186+
}
187+
188+
[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.")]
189+
public void TestConcurrentConnectionPoolingDisposeAsync()
190+
{
191+
// add test case name in connection string to make in unique for each test case
192+
// set short expiration timeout to cover the case that connection expired
193+
string connStr = ConnectionString + ";application=TestConcurrentConnectionPoolingDisposeAsync2;ExpirationTimeout=3";
194+
ConnectionSinglePoolCacheAsyncIT.ConcurrentPoolingAsyncHelper(connStr, false, 7, 100, 2);
195+
}
196+
}
197+
}

0 commit comments

Comments
 (0)