Skip to content

Commit 7fb18b1

Browse files
authored
Merge pull request #135 from madelson/release-2.3.1
Release 2.3.1
2 parents f3a819d + f4a3401 commit 7fb18b1

File tree

20 files changed

+233
-28
lines changed

20 files changed

+233
-28
lines changed

DistributedLock.Core/DistributedLock.Core.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.4</Version>
13+
<Version>1.0.5</Version>
1414
<AssemblyVersion>1.0.0.0</AssemblyVersion>
1515
<Authors>Michael Adelson</Authors>
1616
<Description>Core interfaces and utilities that support the DistributedLock.* family of packages</Description>
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

DistributedLock.Core/Internal/Data/ConnectionMonitor.cs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -253,9 +253,10 @@ private async ValueTask StopOrDisposeAsync(bool isDispose)
253253
// the state to disposed above which the monitoring loop will check if it
254254
// takes over the Cancel() thread.
255255
this._monitorStateChangedTokenSource?.Cancel();
256-
257-
// unsubscribe from state change tracking
258-
if (this._stateChangedHandler != null
256+
257+
// If disposing, unsubscribe from state change tracking.
258+
if (isDispose
259+
&& this._stateChangedHandler != null
259260
&& this._weakConnection.TryGetTarget(out var connection))
260261
{
261262
((DbConnection)connection.InnerConnection).StateChange -= this._stateChangedHandler;
@@ -424,7 +425,7 @@ public MonitoringHandle(ConnectionMonitor keepaliveHelper, CancellationToken can
424425

425426
private sealed class AlreadyCanceledHandle : IDatabaseConnectionMonitoringHandle
426427
{
427-
private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
428+
private readonly CancellationTokenSource _cancellationTokenSource = new();
428429

429430
public AlreadyCanceledHandle()
430431
{
@@ -438,7 +439,7 @@ public AlreadyCanceledHandle()
438439

439440
private sealed class NullHandle : IDatabaseConnectionMonitoringHandle
440441
{
441-
public static readonly NullHandle Instance = new NullHandle();
442+
public static readonly NullHandle Instance = new();
442443

443444
private NullHandle() { }
444445

DistributedLock.FileSystem/DistributedLock.FileSystem.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

DistributedLock.MySql/DistributedLock.MySql.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.0</Version>
13+
<Version>1.0.1</Version>
1414
<AssemblyVersion>1.0.0.0</AssemblyVersion>
1515
<Authors>Michael Adelson</Authors>
1616
<Description>Provides a distributed lock implementation based on MySql</Description>
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

DistributedLock.Oracle/DistributedLock.Oracle.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.0</Version>
13+
<Version>1.0.1</Version>
1414
<AssemblyVersion>1.0.0.0</AssemblyVersion>
1515
<Authors>Michael Adelson</Authors>
1616
<Description>Provides a distributed lock implementation based on Oracle Database</Description>
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

DistributedLock.Postgres/DistributedLock.Postgres.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.2</Version>
13+
<Version>1.0.3</Version>
1414
<AssemblyVersion>1.0.0.0</AssemblyVersion>
1515
<Authors>Michael Adelson</Authors>
1616
<Description>Provides a distributed lock implementation based on Postgresql</Description>
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

DistributedLock.Redis/DistributedLock.Redis.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.1</Version>
13+
<Version>1.0.2</Version>
1414
<AssemblyVersion>1.0.0.0</AssemblyVersion>
1515
<Authors>Michael Adelson</Authors>
1616
<Description>Provides distributed locking primitives based on Redis</Description>
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

DistributedLock.Redis/RedisDistributedSynchronizationOptionsBuilder.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,15 @@ internal static RedisDistributedLockOptions GetOptions(Action<RedisDistributedSy
145145
TimeoutValue extensionCadence;
146146
if (options?._extensionCadence is { } specifiedExtensionCadence)
147147
{
148+
// Note: we do not allow for disabling auto-extension here because it leads to traps
149+
// where people might abandon the handle and then have it be closed due to GC.
150+
// See discussion here: https://github.com/madelson/DistributedLock/issues/130.
148151
if (specifiedExtensionCadence.CompareTo(minValidityTime) >= 0)
149152
{
150153
throw new ArgumentOutOfRangeException(
151154
nameof(extensionCadence),
152155
specifiedExtensionCadence.TimeSpan,
153-
$"{nameof(extensionCadence)} must be less than {nameof(expiry)} ({expiry.TimeSpan}). To disable auto-extension, specify {nameof(Timeout)}.{nameof(Timeout.InfiniteTimeSpan)}"
156+
$"{nameof(extensionCadence)} must be less than {nameof(expiry)} ({expiry.TimeSpan})"
154157
);
155158
}
156159
extensionCadence = specifiedExtensionCadence;

DistributedLock.SqlServer/DistributedLock.SqlServer.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.1</Version>
13+
<Version>1.0.2</Version>
1414
<AssemblyVersion>1.0.0.0</AssemblyVersion>
1515
<Authors>Michael Adelson</Authors>
1616
<Description>Provides a distributed lock implementation based on SQL Server</Description>
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
using Medallion.Threading.Internal.Data;
2+
using Medallion.Threading.SqlServer;
3+
using Medallion.Threading.Tests.SqlServer;
4+
using NUnit.Framework;
5+
using System;
6+
using System.Collections.Generic;
7+
using System.Data.SqlClient;
8+
using System.Linq;
9+
using System.Text;
10+
using System.Threading;
11+
using System.Threading.Tasks;
12+
13+
namespace Medallion.Threading.Tests.Core.Data
14+
{
15+
public class DatabaseConnectionTest
16+
{
17+
/// <summary>
18+
/// Reproduces the root cause of https://github.com/madelson/DistributedLock/issues/133
19+
/// </summary>
20+
[Test]
21+
public async Task TestConnectionMonitorStaysSubscribedAfterClose()
22+
{
23+
var db = new TestingSqlServerDb { ApplicationName = nameof(TestConnectionMonitorStaysSubscribedAfterClose) };
24+
25+
await using var connection = new SqlDatabaseConnection(db.ConnectionString);
26+
27+
await connection.OpenAsync(CancellationToken.None);
28+
connection.ConnectionMonitor.GetMonitoringHandle().Dispose(); // initialize monitoring
29+
await connection.CloseAsync();
30+
31+
await connection.OpenAsync(CancellationToken.None);
32+
using var handle = connection.ConnectionMonitor.GetMonitoringHandle();
33+
Assert.IsFalse(handle.ConnectionLostToken.IsCancellationRequested);
34+
await db.KillSessionsAsync(db.ApplicationName, idleSince: null);
35+
Assert.IsTrue(await TestHelper.WaitForAsync(() => new(handle.ConnectionLostToken.IsCancellationRequested), timeout: TimeSpan.FromSeconds(5)));
36+
}
37+
}
38+
}

DistributedLock.Tests/Tests/MySql/MySqlConnectionOptionsBuilderTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Medallion.Threading.Tests.MySql
88
{
9+
[Category("CI")]
910
public class MySqlConnectionOptionsBuilderTest
1011
{
1112
[Test]

DistributedLock.Tests/Tests/Oracle/OracleConnectionOptionsBuilderTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
namespace Medallion.Threading.Tests.Oracle
99
{
10+
[Category("CI")]
1011
public class OracleConnectionOptionsBuilderTest
1112
{
1213
[Test]

DistributedLock.Tests/Tests/Postgres/PostgresConnectionOptionsBuilderTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Medallion.Threading.Tests.Postgres
88
{
9+
[Category("CI")]
910
public class PostgresConnectionOptionsBuilderTest
1011
{
1112
[Test]

DistributedLock.Tests/Tests/SqlServer/SqlConnectionOptionsBuilderTest.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
namespace Medallion.Threading.Tests.SqlServer
88
{
9+
[Category("CI")]
910
public class SqlConnectionOptionsBuilderTest
1011
{
1112
[Test]

DistributedLock.Tests/Tests/WaitHandles/WaitHandleDistributedSemaphoreTest.cs

Lines changed: 65 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System;
55
using System.Collections.Generic;
66
using System.Text;
7+
using System.Threading;
78
using System.Threading.Tasks;
89

910
namespace Medallion.Threading.Tests.WaitHandles
@@ -47,7 +48,7 @@ public void TestMaxLengthNames()
4748
public async Task TestGarbageCollection()
4849
{
4950
var @lock = CreateAsLock("gc_test", NameStyle.AddPrefix);
50-
WeakReference AbandonLock() => new WeakReference(@lock.Acquire());
51+
WeakReference AbandonLock() => new(@lock.Acquire());
5152

5253
var weakHandle = AbandonLock();
5354
GC.Collect();
@@ -78,8 +79,70 @@ public void TestGetSafeLockNameCompat()
7879
.ShouldEqual(@"Global\xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxsrCnXZ1XHiT//dOSBfAU0iC4Gtnlr0dQACBUK8Ev2OdRYJ9jcvbiqVCv/rjyPemTW9AvOonkdr0B2bG04gmeYA==");
7980
}
8081

82+
/// <summary>
83+
/// Attempts to reproduce https://github.com/madelson/DistributedLock/issues/120.
84+
///
85+
/// NOTE: in practice this race condition is so slim that to reproduce with any reliability requires
86+
/// adding a call to Thread.Sleep(1) at the start of WaitHandleExtensions.Resignal().
87+
/// </summary>
88+
[Test]
89+
public async Task TestCancellationDoesNotLeadToLostSignal([Values] bool async)
90+
{
91+
var semaphore = new WaitHandleDistributedSemaphore(nameof(this.TestCancellationDoesNotLeadToLostSignal), 2);
92+
await using var _ = await semaphore.AcquireAsync(TimeSpan.FromSeconds(1));
93+
94+
Random random = new();
95+
for (var i = 0; i < 50; ++i)
96+
{
97+
using var blockingHandle = semaphore.TryAcquire(TimeSpan.Zero); // claim the last slot on the semaphore
98+
Assert.IsNotNull(blockingHandle);
99+
100+
using CancellationTokenSource source = new();
101+
102+
using SemaphoreSlim acquiringEvent = new(initialCount: 0, maxCount: 1);
103+
var acquireTask = Task.Run(async () =>
104+
{
105+
try
106+
{
107+
if (async)
108+
{
109+
var acquireHandleTask = semaphore.AcquireAsync(TimeSpan.FromSeconds(30), source.Token);
110+
acquiringEvent.Release();
111+
(await acquireHandleTask).Dispose();
112+
}
113+
else
114+
{
115+
acquiringEvent.Release();
116+
semaphore.Acquire(TimeSpan.FromSeconds(30), source.Token).Dispose();
117+
}
118+
}
119+
catch (OperationCanceledException) { }
120+
});
121+
await acquiringEvent.WaitAsync();
122+
Assert.IsFalse(acquireTask.IsCompleted);
123+
124+
using Barrier barrier = new(participantCount: 2);
125+
var releaseTask = Task.Run(() =>
126+
{
127+
barrier.SignalAndWait();
128+
blockingHandle!.Dispose();
129+
});
130+
var cancelTask = Task.Run(() =>
131+
{
132+
barrier.SignalAndWait();
133+
var yieldCount = random.Next(5, 25);
134+
for (var i = 0; i < yieldCount; ++i) { Thread.Yield(); }
135+
source.Cancel();
136+
});
137+
await Task.WhenAll(acquireTask, releaseTask, cancelTask);
138+
}
139+
140+
await using var handle = await semaphore.TryAcquireAsync();
141+
Assert.IsNotNull(handle); // if we lost even a single signal due to cancellation in the loop above, this will fail
142+
}
143+
81144
private static WaitHandleDistributedSemaphore CreateAsLock(string name, NameStyle nameStyle) =>
82-
new WaitHandleDistributedSemaphore(
145+
new(
83146
nameStyle == NameStyle.AddPrefix ? DistributedWaitHandleHelpers.GlobalPrefix + name : name,
84147
maxCount: 1,
85148
abandonmentCheckCadence: TimeSpan.FromSeconds(.3),

DistributedLock.WaitHandles/DistributedLock.WaitHandles.csproj

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010
</PropertyGroup>
1111

1212
<PropertyGroup>
13-
<Version>1.0.0</Version>
13+
<Version>1.0.1</Version>
1414
<AssemblyVersion>1.0.0.0</AssemblyVersion>
1515
<Authors>Michael Adelson</Authors>
1616
<Description>Provides a distributed lock implementation based on global WaitHandle objects in Windows</Description>
@@ -32,6 +32,9 @@
3232
<TreatSpecificWarningsAsErrors />
3333
<!-- see https://github.com/dotnet/sdk/issues/2679 -->
3434
<DebugType>embedded</DebugType>
35+
<!-- see https://mitchelsellers.com/blog/article/net-5-deterministic-builds-source-linking -->
36+
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
37+
<EmbedUntrackedSources>true</EmbedUntrackedSources>
3538
</PropertyGroup>
3639

3740
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">

0 commit comments

Comments
 (0)