-
Notifications
You must be signed in to change notification settings - Fork 247
Expand file tree
/
Copy pathConnectionStringStrategyTestCases.cs
More file actions
125 lines (108 loc) · 4.92 KB
/
ConnectionStringStrategyTestCases.cs
File metadata and controls
125 lines (108 loc) · 4.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
using NUnit.Framework;
using System.Diagnostics;
namespace Medallion.Threading.Tests.Data;
public abstract class ConnectionStringStrategyTestCases<TLockProvider, TStrategy, TDb>
where TLockProvider : TestingLockProvider<TStrategy>, new()
where TStrategy : TestingConnectionStringSynchronizationStrategy<TDb>, new()
where TDb : TestingPrimaryClientDb, new()
{
private TLockProvider _lockProvider = default!;
[SetUp]
public async Task SetUp()
{
this._lockProvider = new TLockProvider();
await this._lockProvider.SetupAsync();
}
[TearDown]
public async Task TearDown() => await this._lockProvider.DisposeAsync();
/// <summary>
/// Tests that internally-owned connections are properly cleaned up by disposing the lock handle
/// </summary>
[Test]
public void TestConnectionDoesNotLeak()
{
// If the lock is based on a multi-ticket semaphore, then the first creation will claim N-1 connections. To avoid this messing with
// our count, we create a throwaway lock instance here to hold those connections using the default application name
this._lockProvider.CreateLock(nameof(TestConnectionDoesNotLeak));
// set a distinctive application name so that we can count how many connections are used
var applicationName = this._lockProvider.Strategy.Db.SetUniqueApplicationName();
var @lock = this._lockProvider.CreateLock(nameof(TestConnectionDoesNotLeak));
for (var i = 0; i < 30; ++i)
{
using (@lock.Acquire())
{
this._lockProvider.Strategy.Db.CountActiveSessions(applicationName).ShouldEqual(1, this.GetType().Name);
}
// still alive due to pooling, except in Oracle where the application name (client info) is not part of the pool key
Assert.That(this._lockProvider.Strategy.Db.CountActiveSessions(applicationName), Is.LessThanOrEqualTo(1), this.GetType().Name);
}
using (var connection = this._lockProvider.Strategy.Db.CreateConnection())
{
this._lockProvider.Strategy.Db.ClearPool(connection);
}
// checking immediately seems flaky; likely clear pool finishing
// doesn't guarantee that SQL will immediately reflect the clear
var maxWaitForPoolsToClear = TimeSpan.FromSeconds(5);
var stopwatch = Stopwatch.StartNew();
do
{
var activeCount = this._lockProvider.Strategy.Db.CountActiveSessions(applicationName);
if (activeCount == 0) { return; }
Thread.Sleep(10);
}
while (stopwatch.Elapsed < maxWaitForPoolsToClear);
Assert.Fail("Connection was not released");
}
[Test]
[NonParallelizable, Retry(5)] // timing-sensitive
public void TestKeepaliveProtectsFromIdleSessionKiller()
{
var applicationName = this._lockProvider.Strategy.Db.SetUniqueApplicationName();
this._lockProvider.Strategy.KeepaliveCadence = TimeSpan.FromSeconds(.05);
var @lock = this._lockProvider.CreateLock(Guid.NewGuid().ToString()); // use unique name due to retry
var handle = @lock.Acquire();
using var idleSessionKiller = new IdleSessionKiller(this._lockProvider.Strategy.Db, applicationName, idleTimeout: TimeSpan.FromSeconds(.5));
Thread.Sleep(TimeSpan.FromSeconds(2));
Assert.DoesNotThrow(handle.Dispose);
}
/// <summary>
/// Demonstrates that we don't multi-thread the connection despite running keepalive queries
/// </summary>
[Test]
public void TestKeepaliveDoesNotCreateRaceCondition()
{
this._lockProvider.Strategy.KeepaliveCadence = TimeSpan.FromMilliseconds(1);
Assert.DoesNotThrow(() =>
{
var @lock = this._lockProvider.CreateLock(nameof(TestKeepaliveDoesNotCreateRaceCondition));
for (var i = 0; i < 25; ++i)
{
using (@lock.Acquire())
{
Thread.Sleep(1);
}
}
});
}
// replicates issue from https://github.com/madelson/DistributedLock/issues/85
[Test]
public async Task TestAccessingHandleLostTokenWhileKeepaliveActiveDoesNotBlock()
{
this._lockProvider.Strategy.KeepaliveCadence = TimeSpan.FromMinutes(5);
var @lock = this._lockProvider.CreateLock(string.Empty);
var handle = await @lock.TryAcquireAsync();
if (handle != null)
{
var accessHandleLostTokenTask = Task.Run(() =>
{
if (handle.HandleLostToken.CanBeCanceled)
{
handle.HandleLostToken.Register(() => { });
}
});
Assert.That(await accessHandleLostTokenTask.TryWaitAsync(TimeSpan.FromSeconds(5)), Is.True);
// do this only on success; on failure we're likely deadlocked and dispose will hang
await handle.DisposeAsync();
}
}
}