From bf67ed38a9da3df987b0d1245bc8774f80de81cb Mon Sep 17 00:00:00 2001 From: Joes Date: Tue, 21 Oct 2025 17:34:39 +0800 Subject: [PATCH 01/13] feat: Added support for MongoDB distributed locks Added the DistributedLock.MongoDB project, providing a MongoDB-based distributed lock implementation that supports both synchronous and asynchronous lock operations. Added core classes `MongoDistributedLock`, `MongoDistributedLockHandle`, `MongoDistributedSynchronizationOptionsBuilder`, and `MongoDistributedSynchronizationProvider` to implement lock acquisition, release, automatic renewal, and configuration functions. Updated project configuration, added dependency on `MongoDB.Driver`, and supported multiple target frameworks (.NET 8, .NET Standard 2.1, .NET Framework 4.7.2). Added usage instructions and code examples in `README.md`. Added comprehensive test coverage, including basic functionality, competition scenarios, lock loss handling, and support for custom collection names, ensuring the reliability of the functionality. Updated dependency versions and optimized internal access configuration for the test project. --- src/Directory.Packages.props | 1 + src/DistributedLock.Core/packages.lock.json | 30 +- .../AssemblyAttributes.cs | 5 + .../DistributedLock.MongoDB.csproj | 62 ++ .../MongoDistributedLock.IDistributedLock.cs | 98 +++ .../MongoDistributedLock.cs | 116 +++ .../MongoDistributedLockHandle.cs | 136 ++++ ...istributedSynchronizationOptionsBuilder.cs | 124 ++++ ...MongoDistributedSynchronizationProvider.cs | 44 ++ .../MongoLockDocument.cs | 39 + .../PublicAPI.Shipped.txt | 22 + .../PublicAPI.Unshipped.txt | 1 + src/DistributedLock.MongoDB/README.md | 106 +++ .../packages.lock.json | 518 ++++++++++++++ .../packages.lock.json | 6 +- .../DistributedLock.Tests.csproj | 1 + .../MongoDB/TestingMongoDbProviders.cs | 45 ++ .../TestingMongoDbSynchronizationStrategy.cs | 11 + .../Shared/MongoDbCredentials.cs | 31 + .../Tests/MongoDB/MongoDistributedLockTest.cs | 118 +++ ...ibutedSynchronizationOptionsBuilderTest.cs | 64 ++ ...oDistributedSynchronizationProviderTest.cs | 71 ++ src/DistributedLock.Tests/packages.lock.json | 674 ++---------------- src/DistributedLock.sln | 10 +- src/DistributedLock/DistributedLock.csproj | 2 +- src/DistributedLock/packages.lock.json | 76 ++ src/DistributedLockTaker/packages.lock.json | 18 +- 27 files changed, 1783 insertions(+), 646 deletions(-) create mode 100644 src/DistributedLock.MongoDB/AssemblyAttributes.cs create mode 100644 src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj create mode 100644 src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs create mode 100644 src/DistributedLock.MongoDB/MongoDistributedLock.cs create mode 100644 src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs create mode 100644 src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs create mode 100644 src/DistributedLock.MongoDB/MongoDistributedSynchronizationProvider.cs create mode 100644 src/DistributedLock.MongoDB/MongoLockDocument.cs create mode 100644 src/DistributedLock.MongoDB/PublicAPI.Shipped.txt create mode 100644 src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt create mode 100644 src/DistributedLock.MongoDB/README.md create mode 100644 src/DistributedLock.MongoDB/packages.lock.json create mode 100644 src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbProviders.cs create mode 100644 src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbSynchronizationStrategy.cs create mode 100644 src/DistributedLock.Tests/Infrastructure/Shared/MongoDbCredentials.cs create mode 100644 src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs create mode 100644 src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs create mode 100644 src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index f0fe5922..2894c36a 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -7,6 +7,7 @@ + diff --git a/src/DistributedLock.Core/packages.lock.json b/src/DistributedLock.Core/packages.lock.json index 2f96fece..0e0884af 100644 --- a/src/DistributedLock.Core/packages.lock.json +++ b/src/DistributedLock.Core/packages.lock.json @@ -11,12 +11,6 @@ "System.Threading.Tasks.Extensions": "4.5.4" } }, - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.NETFramework.ReferenceAssemblies": { "type": "Direct", "requested": "[1.0.3, )", @@ -81,12 +75,6 @@ "System.Threading.Tasks.Extensions": "4.5.4" } }, - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -136,12 +124,6 @@ } }, ".NETStandard,Version=v2.1": { - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.SourceLink.GitHub": { "type": "Direct", "requested": "[8.0.0, )", @@ -164,17 +146,11 @@ } }, "net8.0": { - "Microsoft.CodeAnalysis.PublicApiAnalyzers": { - "type": "Direct", - "requested": "[3.3.4, )", - "resolved": "3.3.4", - "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" - }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.4, )", - "resolved": "8.0.4", - "contentHash": "PZb5nfQ+U19nhnmnR9T1jw+LTmozhuG2eeuzuW5A7DqxD/UXW2ucjmNJqnqOuh8rdPzM3MQXoF8AfFCedJdCUw==" + "requested": "[8.0.21, )", + "resolved": "8.0.21", + "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/src/DistributedLock.MongoDB/AssemblyAttributes.cs b/src/DistributedLock.MongoDB/AssemblyAttributes.cs new file mode 100644 index 00000000..f3102190 --- /dev/null +++ b/src/DistributedLock.MongoDB/AssemblyAttributes.cs @@ -0,0 +1,5 @@ +using System.Runtime.CompilerServices; + +[assembly: + InternalsVisibleTo( + "DistributedLock.Tests, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fd3af56ccc8ed94fffe25bfd651e6a5674f8f20a76d37de800dd0f7380e04f0fde2da6fa200380b14fe398605b6f470c87e5e0a0bf39ae871f07536a4994aa7a0057c4d3bcedc8fef3eecb0c88c2024a1b3289305c2393acd9fb9f9a42d0bd7826738ce864d507575ea3a1fe1746ab19823303269f79379d767949807f494be8")] \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj b/src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj new file mode 100644 index 00000000..828c3eaa --- /dev/null +++ b/src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj @@ -0,0 +1,62 @@ + + + + netstandard2.1;net8.0;net472; + Medallion.Threading.MongoDB + True + 4 + Latest + enable + enable + + + + 1.3.0 + 1.0.0.0 + Michael Adelson + Provides a distributed lock implementation based on MongoDB + Copyright © 2020 Michael Adelson + MIT + distributed lock async mongodb + https://github.com/madelson/DistributedLock + https://github.com/madelson/DistributedLock + 1.0.0.0 + See https://github.com/madelson/DistributedLock#release-notes + true + ..\DistributedLock.snk + + + + True + True + True + + + embedded + + true + true + + + + False + 1591 + TRACE;DEBUG + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs b/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs new file mode 100644 index 00000000..89936ac7 --- /dev/null +++ b/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs @@ -0,0 +1,98 @@ +using Medallion.Threading.Internal; + +namespace Medallion.Threading.MongoDB; + +public partial class MongoDistributedLock +{ + IDistributedSynchronizationHandle? IDistributedLock.TryAcquire(TimeSpan timeout, CancellationToken cancellationToken) + { + return TryAcquire(timeout, cancellationToken); + } + + IDistributedSynchronizationHandle IDistributedLock.Acquire(TimeSpan? timeout, CancellationToken cancellationToken) + { + return Acquire(timeout, cancellationToken); + } + + ValueTask IDistributedLock.TryAcquireAsync(TimeSpan timeout, CancellationToken cancellationToken) + { + return TryAcquireAsync(timeout, cancellationToken).Convert(To.ValueTask); + } + + ValueTask IDistributedLock.AcquireAsync(TimeSpan? timeout, CancellationToken cancellationToken) + { + return AcquireAsync(timeout, cancellationToken).Convert(To.ValueTask); + } + + /// + /// Attempts to acquire the lock synchronously. Usage: + /// + /// using (var handle = myLock.TryAcquire(...)) + /// { + /// if (handle != null) { /* we have the lock! */ } + /// } + /// // dispose releases the lock if we took it + /// + /// + /// How long to wait before giving up on the acquisition attempt. Defaults to 0 + /// Specifies a token by which the wait can be canceled + /// A which can be used to release the lock or null on failure + public MongoDistributedLockHandle? TryAcquire(TimeSpan timeout = default, CancellationToken cancellationToken = default) + { + return DistributedLockHelpers.TryAcquire(this, timeout, cancellationToken); + } + + /// + /// Acquires the lock synchronously, failing with if the attempt times out. Usage: + /// + /// using (myLock.Acquire(...)) + /// { + /// /* we have the lock! */ + /// } + /// // dispose releases the lock + /// + /// + /// How long to wait before giving up on the acquisition attempt. Defaults to + /// Specifies a token by which the wait can be canceled + /// A which can be used to release the lock + public MongoDistributedLockHandle Acquire(TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + return DistributedLockHelpers.Acquire(this, timeout, cancellationToken); + } + + /// + /// Attempts to acquire the lock asynchronously. Usage: + /// + /// await using (var handle = await myLock.TryAcquireAsync(...)) + /// { + /// if (handle != null) { /* we have the lock! */ } + /// } + /// // dispose releases the lock if we took it + /// + /// + /// How long to wait before giving up on the acquisition attempt. Defaults to 0 + /// Specifies a token by which the wait can be canceled + /// A which can be used to release the lock or null on failure + public ValueTask TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) + { + return this.As>().InternalTryAcquireAsync(timeout, cancellationToken); + } + + /// + /// Acquires the lock asynchronously, failing with if the attempt times out. Usage: + /// + /// await using (await myLock.AcquireAsync(...)) + /// { + /// /* we have the lock! */ + /// } + /// // dispose releases the lock + /// + /// + /// How long to wait before giving up on the acquisition attempt. Defaults to + /// Specifies a token by which the wait can be canceled + /// A which can be used to release the lock + public ValueTask AcquireAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) + { + return DistributedLockHelpers.AcquireAsync(this, timeout, cancellationToken); + } +} \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/MongoDistributedLock.cs b/src/DistributedLock.MongoDB/MongoDistributedLock.cs new file mode 100644 index 00000000..1fb4ec8d --- /dev/null +++ b/src/DistributedLock.MongoDB/MongoDistributedLock.cs @@ -0,0 +1,116 @@ +using Medallion.Threading.Internal; +using MongoDB.Driver; + +namespace Medallion.Threading.MongoDB; + +/// +/// Implements a using MongoDB. +/// +public sealed partial class MongoDistributedLock : IInternalDistributedLock +{ + private readonly string _collectionName; + private readonly IMongoDatabase _database; + private readonly MongoDistributedLockOptions _options; + + /// + /// The MongoDB key used to implement the lock + /// + public string Key { get; } + + /// + /// Implements + /// + public string Name => Key; + + /// + /// Constructs a lock named using the provided and . + /// The locks will be stored in a collection named "DistributedLocks" by default. + /// + public MongoDistributedLock(string key, IMongoDatabase database, Action? options = null) + : this(key, database, "DistributedLocks", options) { } + + /// + /// Constructs a lock named using the provided , , and + /// . + /// + public MongoDistributedLock(string key, IMongoDatabase database, string collectionName, Action? options = null) + { + if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); } + _database = database ?? throw new ArgumentNullException(nameof(database)); + _collectionName = collectionName ?? throw new ArgumentNullException(nameof(collectionName)); + Key = key; + _options = MongoDistributedSynchronizationOptionsBuilder.GetOptions(options); + } + + ValueTask IInternalDistributedLock.InternalTryAcquireAsync(TimeoutValue timeout, CancellationToken cancellationToken) + { + return BusyWaitHelper.WaitAsync(this, + (@this, ct) => @this.TryAcquireAsync(ct), + timeout, + _options.MinBusyWaitSleepTime, + _options.MaxBusyWaitSleepTime, + cancellationToken); + } + + private async ValueTask TryAcquireAsync(CancellationToken cancellationToken) + { + var collection = _database.GetCollection(_collectionName); + + // Ensure index exists for efficient queries + await EnsureIndexAsync(collection, cancellationToken).ConfigureAwait(false); + var lockId = Guid.NewGuid().ToString(); + var now = DateTime.UtcNow; + var expiresAt = now.Add(_options.Expiry.TimeSpan); + + // Filter: lock with this key doesn't exist OR has expired + var filter = Builders.Filter.Or(Builders.Filter.Eq(d => d.Id, Key) & Builders.Filter.Lt(d => d.ExpiresAt, now), + Builders.Filter.Eq(d => d.Id, Key) & Builders.Filter.Exists(d => d.ExpiresAt, false)); + var update = Builders.Update + .Set(d => d.Id, Key) + .Set(d => d.LockId, lockId) + .Set(d => d.ExpiresAt, expiresAt) + .Set(d => d.AcquiredAt, now); + var options = new FindOneAndUpdateOptions { IsUpsert = true, ReturnDocument = ReturnDocument.After }; + try + { + var result = await collection.FindOneAndUpdateAsync(filter, update, options, cancellationToken).ConfigureAwait(false); + + // Verify we actually got the lock + if (result != null && result.LockId == lockId) + { + return new(_database, + _collectionName, + Key, + lockId, + _options.Expiry, + _options.ExtensionCadence); + } + } + catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) + { + // Lock is already held by someone else + return null; + } + catch (MongoCommandException) + { + // Lock is already held + return null; + } + return null; + } + + private static async Task EnsureIndexAsync(IMongoCollection collection, CancellationToken cancellationToken) + { + try + { + var indexKeys = Builders.IndexKeys.Ascending(d => d.ExpiresAt); + var indexOptions = new CreateIndexOptions { Background = true }; + var indexModel = new CreateIndexModel(indexKeys, indexOptions); + await collection.Indexes.CreateOneAsync(indexModel, cancellationToken: cancellationToken).ConfigureAwait(false); + } + catch + { + // Index might already exist, ignore errors + } + } +} \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs b/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs new file mode 100644 index 00000000..352c698c --- /dev/null +++ b/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs @@ -0,0 +1,136 @@ +using Medallion.Threading.Internal; +using MongoDB.Driver; + +namespace Medallion.Threading.MongoDB; + +/// +/// Implements for +/// +public sealed class MongoDistributedLockHandle : IDistributedSynchronizationHandle +{ + private readonly string _collectionName; + private readonly CancellationTokenSource _cts; + private readonly IMongoDatabase _database; + private readonly Task _extensionTask; + private readonly string _key; + private readonly string _lockId; + private int _disposed; + + /// + /// Implements + /// + public CancellationToken HandleLostToken => _cts.Token; + + internal MongoDistributedLockHandle( + IMongoDatabase database, + string collectionName, + string key, + string lockId, + TimeoutValue expiry, + TimeoutValue extensionCadence) + { + _database = database; + _collectionName = collectionName; + _key = key; + _lockId = lockId; + _cts = new(); + + // Start background task to extend the lock + _extensionTask = ExtendLockAsync(expiry, extensionCadence, _cts.Token); + } + + /// + /// Releases the lock + /// + public void Dispose() + { + if (Interlocked.Exchange(ref _disposed, 1) is not 0) + { + return; + } + _cts.Cancel(); + try + { + _extensionTask.Wait(HandleLostToken); + } + catch + { + // Ignore exceptions during cleanup + } + finally + { + _cts.Dispose(); + ReleaseLockAsync(CancellationToken.None).AsTask().Wait(HandleLostToken); + } + } + + /// + /// Releases the lock asynchronously + /// + public async ValueTask DisposeAsync() + { + if (Interlocked.Exchange(ref _disposed, 1) is 0) + { + _cts.Cancel(); + try + { + await _extensionTask.ConfigureAwait(false); + } + catch + { + // Ignore exceptions during cleanup + } + finally + { + _cts.Dispose(); + await ReleaseLockAsync(CancellationToken.None).ConfigureAwait(false); + } + } + } + + private async Task ExtendLockAsync(TimeoutValue expiry, TimeoutValue extensionCadence, CancellationToken cancellationToken) + { + try + { + while (!cancellationToken.IsCancellationRequested) + { + await Task.Delay(extensionCadence.TimeSpan, cancellationToken).ConfigureAwait(false); + var collection = _database.GetCollection(_collectionName); + var filter = Builders.Filter.Eq(d => d.Id, _key) & Builders.Filter.Eq(d => d.LockId, _lockId); + var update = Builders.Update.Set(d => d.ExpiresAt, DateTime.UtcNow.Add(expiry.TimeSpan)); + var result = await collection.UpdateOneAsync(filter, update, cancellationToken: cancellationToken).ConfigureAwait(false); + + // If we failed to extend, the lock was lost + if (result.MatchedCount is not 0) + { + continue; + } + _cts.Cancel(); + break; + } + } + catch (OperationCanceledException) + { + // Expected when disposing + } + catch + { + // Lock extension failed, signal that the lock is lost + _cts.Cancel(); + } + } + + private async ValueTask ReleaseLockAsync(CancellationToken cancellationToken) + { + try + { + var collection = _database.GetCollection(_collectionName); + var filter = Builders.Filter.Eq(d => d.Id, _key) & Builders.Filter.Eq(d => d.LockId, _lockId); + await collection.DeleteOneAsync(filter, cancellationToken).ConfigureAwait(false); + } + catch + { + // Ignore errors during release + } + } +} \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs b/src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs new file mode 100644 index 00000000..eae699e1 --- /dev/null +++ b/src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs @@ -0,0 +1,124 @@ +using Medallion.Threading.Internal; + +namespace Medallion.Threading.MongoDB; + +/// +/// Options for configuring a MongoDB-based distributed synchronization algorithm +/// +public sealed class MongoDistributedSynchronizationOptionsBuilder +{ + internal static readonly TimeoutValue DefaultExpiry = TimeSpan.FromSeconds(30); + + /// + /// We don't want to allow expiry to go too low, since then the lock doesn't even work + /// + internal static readonly TimeoutValue MinimumExpiry = TimeSpan.FromSeconds(.1); + + private TimeoutValue? _expiry, _extensionCadence, _minBusyWaitSleepTime, _maxBusyWaitSleepTime; + + internal MongoDistributedSynchronizationOptionsBuilder() { } + + /// + /// Specifies how long the lock will last, absent auto-extension. Because auto-extension exists, + /// this value generally will have little effect on program behavior. However, making the expiry longer means that + /// auto-extension requests can occur less frequently, saving resources. On the other hand, when a lock is abandoned + /// without explicit release (e. g. if the holding process crashes), the expiry determines how long other processes + /// would need to wait in order to acquire it. + /// Defaults to 30s. + /// + public MongoDistributedSynchronizationOptionsBuilder Expiry(TimeSpan expiry) + { + var expiryTimeoutValue = new TimeoutValue(expiry, nameof(expiry)); + if (expiryTimeoutValue.IsInfinite || expiryTimeoutValue.CompareTo(MinimumExpiry) < 0) + { + throw new ArgumentOutOfRangeException(nameof(expiry), expiry, $"Must be >= {MinimumExpiry.TimeSpan} and < ∞"); + } + _expiry = expiryTimeoutValue; + return this; + } + + /// + /// Determines how frequently the lock will be extended while held. More frequent extension means more unnecessary requests + /// but also a lower chance of losing the lock due to the process hanging or otherwise failing to get its extension request in + /// before the lock expiry elapses. + /// Defaults to 1/3 of the expiry time. + /// + public MongoDistributedSynchronizationOptionsBuilder ExtensionCadence(TimeSpan extensionCadence) + { + _extensionCadence = new TimeoutValue(extensionCadence, nameof(extensionCadence)); + return this; + } + + /// + /// Waiting to acquire a lock requires a busy wait that alternates acquire attempts and sleeps. + /// This determines how much time is spent sleeping between attempts. Lower values will raise the + /// volume of acquire requests under contention but will also raise the responsiveness (how long + /// it takes a waiter to notice that a contended the lock has become available). + /// Specifying a range of values allows the implementation to select an actual value in the range + /// at random for each sleep. This helps avoid the case where two clients become "synchronized" + /// in such a way that results in one client monopolizing the lock. + /// The default is [10ms, 800ms] + /// + public MongoDistributedSynchronizationOptionsBuilder BusyWaitSleepTime(TimeSpan min, TimeSpan max) + { + var minTimeoutValue = new TimeoutValue(min, nameof(min)); + var maxTimeoutValue = new TimeoutValue(max, nameof(max)); + if (minTimeoutValue.IsInfinite) { throw new ArgumentOutOfRangeException(nameof(min), "may not be infinite"); } + if (maxTimeoutValue.IsInfinite || maxTimeoutValue.CompareTo(min) < 0) + { + throw new ArgumentOutOfRangeException(nameof(max), max, "must be non-infinite and greater than " + nameof(min)); + } + _minBusyWaitSleepTime = minTimeoutValue; + _maxBusyWaitSleepTime = maxTimeoutValue; + return this; + } + + internal static MongoDistributedLockOptions GetOptions(Action? optionsBuilder) + { + MongoDistributedSynchronizationOptionsBuilder? options; + if (optionsBuilder != null) + { + options = new(); + optionsBuilder(options); + } + else + { + options = null; + } + var expiry = options?._expiry ?? DefaultExpiry; + TimeoutValue extensionCadence; + if (options?._extensionCadence is { } specifiedExtensionCadence) + { + if (specifiedExtensionCadence.CompareTo(expiry) >= 0) + { + throw new ArgumentOutOfRangeException(nameof(extensionCadence), + specifiedExtensionCadence.TimeSpan, + $"{nameof(extensionCadence)} must be less than {nameof(expiry)} ({expiry.TimeSpan})"); + } + extensionCadence = specifiedExtensionCadence; + } + else + { + extensionCadence = TimeSpan.FromMilliseconds(expiry.InMilliseconds / 3.0); + } + return new(expiry, + extensionCadence, + options?._minBusyWaitSleepTime ?? TimeSpan.FromMilliseconds(10), + options?._maxBusyWaitSleepTime ?? TimeSpan.FromSeconds(0.8)); + } +} + +internal readonly struct MongoDistributedLockOptions( + TimeoutValue expiry, + TimeoutValue extensionCadence, + TimeoutValue minBusyWaitSleepTime, + TimeoutValue maxBusyWaitSleepTime) +{ + public TimeoutValue Expiry { get; } = expiry; + + public TimeoutValue ExtensionCadence { get; } = extensionCadence; + + public TimeoutValue MinBusyWaitSleepTime { get; } = minBusyWaitSleepTime; + + public TimeoutValue MaxBusyWaitSleepTime { get; } = maxBusyWaitSleepTime; +} \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/MongoDistributedSynchronizationProvider.cs b/src/DistributedLock.MongoDB/MongoDistributedSynchronizationProvider.cs new file mode 100644 index 00000000..c3a46ace --- /dev/null +++ b/src/DistributedLock.MongoDB/MongoDistributedSynchronizationProvider.cs @@ -0,0 +1,44 @@ +using MongoDB.Driver; + +namespace Medallion.Threading.MongoDB; + +/// +/// Implements for . +/// +public sealed class MongoDistributedSynchronizationProvider : IDistributedLockProvider +{ + private readonly string _collectionName; + private readonly IMongoDatabase _database; + private readonly Action? _options; + + /// + /// Constructs a that connects to the provided + /// and uses the provided . Locks will be stored in a collection named "DistributedLocks" by default. + /// + public MongoDistributedSynchronizationProvider(IMongoDatabase database, Action? options = null) + : this(database, "DistributedLocks", options) { } + + /// + /// Constructs a that connects to the provided , + /// stores locks in the specified , and uses the provided . + /// + public MongoDistributedSynchronizationProvider(IMongoDatabase database, string collectionName, Action? options = null) + { + _database = database ?? throw new ArgumentNullException(nameof(database)); + _collectionName = collectionName ?? throw new ArgumentNullException(nameof(collectionName)); + _options = options; + } + + /// + /// Creates a using the given . + /// + public MongoDistributedLock CreateLock(string name) + { + return new(name, _database, _collectionName, _options); + } + + IDistributedLock IDistributedLockProvider.CreateLock(string name) + { + return CreateLock(name); + } +} \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/MongoLockDocument.cs b/src/DistributedLock.MongoDB/MongoLockDocument.cs new file mode 100644 index 00000000..d6359a29 --- /dev/null +++ b/src/DistributedLock.MongoDB/MongoLockDocument.cs @@ -0,0 +1,39 @@ +using MongoDB.Bson; +using MongoDB.Bson.Serialization.Attributes; + +namespace Medallion.Threading.MongoDB; + +/// +/// Represents a lock document stored in MongoDB +/// +// ReSharper disable once ClassNeverInstantiated.Global +internal sealed class MongoLockDocument +{ + /// + /// When the lock was acquired (UTC) + /// + [BsonElement("acquiredAt")] + [BsonRepresentation(BsonType.DateTime)] + public DateTime AcquiredAt { get; set; } + + /// + /// When the lock expires (UTC) + /// + [BsonElement("expiresAt")] + [BsonRepresentation(BsonType.DateTime)] + public DateTime ExpiresAt { get; set; } + + /// + /// The lock name/key (MongoDB document ID) + /// + [BsonId] + [BsonRepresentation(BsonType.String)] + public string Id { get; set; } = string.Empty; + + /// + /// Unique identifier for this lock acquisition + /// + [BsonElement("lockId")] + [BsonRepresentation(BsonType.String)] + public string LockId { get; set; } = string.Empty; +} \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/PublicAPI.Shipped.txt b/src/DistributedLock.MongoDB/PublicAPI.Shipped.txt new file mode 100644 index 00000000..5e440dc2 --- /dev/null +++ b/src/DistributedLock.MongoDB/PublicAPI.Shipped.txt @@ -0,0 +1,22 @@ +#nullable enable +Medallion.Threading.MongoDB.MongoDistributedLock +Medallion.Threading.MongoDB.MongoDistributedLock.Acquire(System.TimeSpan? timeout = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Medallion.Threading.MongoDB.MongoDistributedLockHandle! +Medallion.Threading.MongoDB.MongoDistributedLock.AcquireAsync(System.TimeSpan? timeout = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Medallion.Threading.MongoDB.MongoDistributedLock.Key.get -> string! +Medallion.Threading.MongoDB.MongoDistributedLock.Name.get -> string! +Medallion.Threading.MongoDB.MongoDistributedLock.MongoDistributedLock(string! key, MongoDB.Driver.IMongoDatabase! database, System.Action? options = null) -> void +Medallion.Threading.MongoDB.MongoDistributedLock.MongoDistributedLock(string! key, MongoDB.Driver.IMongoDatabase! database, string! collectionName, System.Action? options = null) -> void +Medallion.Threading.MongoDB.MongoDistributedLock.TryAcquire(System.TimeSpan timeout = default(System.TimeSpan), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> Medallion.Threading.MongoDB.MongoDistributedLockHandle? +Medallion.Threading.MongoDB.MongoDistributedLock.TryAcquireAsync(System.TimeSpan timeout = default(System.TimeSpan), System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.ValueTask +Medallion.Threading.MongoDB.MongoDistributedLockHandle +Medallion.Threading.MongoDB.MongoDistributedLockHandle.Dispose() -> void +Medallion.Threading.MongoDB.MongoDistributedLockHandle.DisposeAsync() -> System.Threading.Tasks.ValueTask +Medallion.Threading.MongoDB.MongoDistributedLockHandle.HandleLostToken.get -> System.Threading.CancellationToken +Medallion.Threading.MongoDB.MongoDistributedSynchronizationOptionsBuilder +Medallion.Threading.MongoDB.MongoDistributedSynchronizationOptionsBuilder.BusyWaitSleepTime(System.TimeSpan min, System.TimeSpan max) -> Medallion.Threading.MongoDB.MongoDistributedSynchronizationOptionsBuilder! +Medallion.Threading.MongoDB.MongoDistributedSynchronizationOptionsBuilder.Expiry(System.TimeSpan expiry) -> Medallion.Threading.MongoDB.MongoDistributedSynchronizationOptionsBuilder! +Medallion.Threading.MongoDB.MongoDistributedSynchronizationOptionsBuilder.ExtensionCadence(System.TimeSpan extensionCadence) -> Medallion.Threading.MongoDB.MongoDistributedSynchronizationOptionsBuilder! +Medallion.Threading.MongoDB.MongoDistributedSynchronizationProvider +Medallion.Threading.MongoDB.MongoDistributedSynchronizationProvider.CreateLock(string! name) -> Medallion.Threading.MongoDB.MongoDistributedLock! +Medallion.Threading.MongoDB.MongoDistributedSynchronizationProvider.MongoDistributedSynchronizationProvider(MongoDB.Driver.IMongoDatabase! database, System.Action? options = null) -> void +Medallion.Threading.MongoDB.MongoDistributedSynchronizationProvider.MongoDistributedSynchronizationProvider(MongoDB.Driver.IMongoDatabase! database, string! collectionName, System.Action? options = null) -> void diff --git a/src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt b/src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt new file mode 100644 index 00000000..1883be4e --- /dev/null +++ b/src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt @@ -0,0 +1 @@ +Medallion.Threading.MongoDB.MongoDistributedLock \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/README.md b/src/DistributedLock.MongoDB/README.md new file mode 100644 index 00000000..2cac9f0d --- /dev/null +++ b/src/DistributedLock.MongoDB/README.md @@ -0,0 +1,106 @@ +# DistributedLock.MongoDB + +This library provides distributed lock implementation using MongoDB as the backing store. + +## Installation + +```bash +dotnet add package DistributedLock.MongoDB +``` + +## Usage + +### Basic Lock Usage + +```csharp +using Medallion.Threading.MongoDB; +using MongoDB.Driver; + +// Create MongoDB client and database +var client = new MongoClient("mongodb://localhost:27017"); +var database = client.GetDatabase("myDatabase"); + +// Create a lock +var @lock = new MongoDistributedLock("myLockName", database); + +// Acquire the lock +await using (var handle = await @lock.AcquireAsync()) +{ + // Critical section protected by the lock + Console.WriteLine("Lock acquired!"); +} +// Lock is automatically released when disposed +``` + +### Using the Provider + +```csharp +using Medallion.Threading.MongoDB; +using MongoDB.Driver; + +var client = new MongoClient("mongodb://localhost:27017"); +var database = client.GetDatabase("myDatabase"); + +// Create a provider +var provider = new MongoDistributedSynchronizationProvider(database); + +// Use the provider to create locks +var lock1 = provider.CreateLock("lock1"); +var lock2 = provider.CreateLock("lock2"); + +await using (var handle = await lock1.AcquireAsync()) +{ + // Do work... +} +``` + +### Configuration Options + +You can customize the lock behavior using the options builder: + +```csharp +var @lock = new MongoDistributedLock( + "myLockName", + database, + options => options + .Expiry(TimeSpan.FromSeconds(30)) // Lock expiry time + .ExtensionCadence(TimeSpan.FromSeconds(10)) // How often to extend the lock + .BusyWaitSleepTime( // Sleep time between acquire attempts + min: TimeSpan.FromMilliseconds(10), + max: TimeSpan.FromMilliseconds(800)) +); +``` + +### Custom Collection Name + +By default, locks are stored in a collection named "DistributedLocks". You can specify a custom collection name: + +```csharp +var @lock = new MongoDistributedLock("myLockName", database, "MyCustomLocks"); +``` + +## How It Works + +The MongoDB distributed lock uses MongoDB's document upsert and update operations to implement distributed locking: + +1. **Acquisition**: Attempts to insert or update a document with the lock key and a unique lock ID +2. **Extension**: Automatically extends the lock expiry while held to prevent timeout +3. **Release**: Deletes the lock document when disposed +4. **Expiry**: Locks automatically expire if not extended, allowing recovery from crashed processes + +## Features + +- ✅ Async/await support +- ✅ Automatic lock extension while held +- ✅ Configurable expiry and extension cadence +- ✅ Lock abandonment protection via expiry +- ✅ `CancellationToken` support +- ✅ Handle lost token notification +- ✅ Multi-target support: .NET 8, .NET Standard 2.1, .NET Framework 4.7.2 + +## Notes + +- The lock collection will have an index on the `expiresAt` field for efficient queries +- Lock extension happens automatically in the background +- If lock extension fails, the `HandleLostToken` will be signaled +- Stale locks (from crashed processes) will automatically expire based on the expiry setting diff --git a/src/DistributedLock.MongoDB/packages.lock.json b/src/DistributedLock.MongoDB/packages.lock.json new file mode 100644 index 00000000..11abd24a --- /dev/null +++ b/src/DistributedLock.MongoDB/packages.lock.json @@ -0,0 +1,518 @@ +{ + "version": 2, + "dependencies": { + ".NETFramework,Version=v4.7.2": { + "Microsoft.CodeAnalysis.PublicApiAnalyzers": { + "type": "Direct", + "requested": "[3.3.4, )", + "resolved": "3.3.4", + "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "MongoDB.Driver": { + "type": "Direct", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "System.Net.Http": "4.3.4", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "ZstdSharp.Port": "0.7.3" + } + }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0", + "System.Buffers": "4.5.1" + } + }, + "Microsoft.Bcl.AsyncInterfaces": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", + "dependencies": { + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Text.Encoding.CodePages": "5.0.0" + } + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "4.7.1", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.5.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" + }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA==" + }, + "System.Runtime.InteropServices.RuntimeInformation": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" + }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "System.Threading.Tasks.Extensions": { + "type": "Transitive", + "resolved": "4.5.4", + "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.ValueTuple": { + "type": "Transitive", + "resolved": "4.5.0", + "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" + }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "5.0.0", + "System.Memory": "4.5.5" + } + }, + "distributedlock.core": { + "type": "Project", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "[8.0.0, )", + "System.ValueTuple": "[4.5.0, )" + } + } + }, + ".NETStandard,Version=v2.1": { + "Microsoft.CodeAnalysis.PublicApiAnalyzers": { + "type": "Direct", + "requested": "[3.3.4, )", + "resolved": "3.3.4", + "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "MongoDB.Driver": { + "type": "Direct", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "ZstdSharp.Port": "0.7.3" + } + }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Memory": "4.5.4", + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==", + "dependencies": { + "System.Text.Encoding.CodePages": "5.0.0" + } + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.7.1" + } + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", + "dependencies": { + "System.Buffers": "4.5.1", + "System.Numerics.Vectors": "4.4.0", + "System.Runtime.CompilerServices.Unsafe": "4.5.3" + } + }, + "System.Numerics.Vectors": { + "type": "Transitive", + "resolved": "4.4.0", + "contentHash": "UiLzLW+Lw6HLed1Hcg+8jSRttrbuXv7DANVj0DkL9g6EnnzbL75EB7EWsw5uRbhxd/4YdG8li5XizGWepmG3PQ==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "6.0.0", + "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, + "distributedlock.core": { + "type": "Project" + } + }, + "net8.0": { + "Microsoft.CodeAnalysis.PublicApiAnalyzers": { + "type": "Direct", + "requested": "[3.3.4, )", + "resolved": "3.3.4", + "contentHash": "kNLTfXtXUWDHVt5iaPkkiPuyHYlMgLI6SOFT4w88bfeI2vqSeGgHunFkdvlaCM8RDfcY0t2+jnesQtidRJJ/DA==" + }, + "Microsoft.NET.ILLink.Tasks": { + "type": "Direct", + "requested": "[8.0.21, )", + "resolved": "8.0.21", + "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" + }, + "Microsoft.SourceLink.GitHub": { + "type": "Direct", + "requested": "[8.0.0, )", + "resolved": "8.0.0", + "contentHash": "G5q7OqtwIyGTkeIOAc3u2ZuV/kicQaec5EaRnc0pIeSnh9LUjj+PYQrJYBURvDt7twGl2PKA7nSN0kz1Zw5bnQ==", + "dependencies": { + "Microsoft.Build.Tasks.Git": "8.0.0", + "Microsoft.SourceLink.Common": "8.0.0" + } + }, + "MongoDB.Driver": { + "type": "Direct", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "ZstdSharp.Port": "0.7.3" + } + }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, + "Microsoft.Build.Tasks.Git": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "bZKfSIKJRXLTuSzLudMFte/8CempWjVamNUR5eHJizsy+iuOuO/k2gnh7W0dHJmYY0tBf+gUErfluCv5mySAOQ==" + }, + "Microsoft.Extensions.Logging.Abstractions": { + "type": "Transitive", + "resolved": "2.0.0", + "contentHash": "6ZCllUYGFukkymSTx3Yr0G/ajRxoNJp7/FqSxSB4fGISST54ifBhgu4Nc0ItGi3i6DqwuNd8SUyObmiC++AO2Q==" + }, + "Microsoft.NETCore.Platforms": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" + }, + "Microsoft.SourceLink.Common": { + "type": "Transitive", + "resolved": "8.0.0", + "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" + }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==" + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, + "System.Memory": { + "type": "Transitive", + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" + }, + "System.Runtime.CompilerServices.Unsafe": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "ZD9TMpsmYJLrxbbmdvhwt9YEgG5WntEnZ/d1eH8JBX9LBp+Ju8BSBhUGbZMNVHHomWo2KVImJhTDl2hIgw/6MA==" + }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==" + }, + "distributedlock.core": { + "type": "Project" + } + } + } +} \ No newline at end of file diff --git a/src/DistributedLock.Postgres/packages.lock.json b/src/DistributedLock.Postgres/packages.lock.json index 797320b6..041eb72b 100644 --- a/src/DistributedLock.Postgres/packages.lock.json +++ b/src/DistributedLock.Postgres/packages.lock.json @@ -518,9 +518,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.4, )", - "resolved": "8.0.4", - "contentHash": "PZb5nfQ+U19nhnmnR9T1jw+LTmozhuG2eeuzuW5A7DqxD/UXW2ucjmNJqnqOuh8rdPzM3MQXoF8AfFCedJdCUw==" + "requested": "[8.0.21, )", + "resolved": "8.0.21", + "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/src/DistributedLock.Tests/DistributedLock.Tests.csproj b/src/DistributedLock.Tests/DistributedLock.Tests.csproj index be82219e..07020675 100644 --- a/src/DistributedLock.Tests/DistributedLock.Tests.csproj +++ b/src/DistributedLock.Tests/DistributedLock.Tests.csproj @@ -31,6 +31,7 @@ + \ No newline at end of file diff --git a/src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbProviders.cs b/src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbProviders.cs new file mode 100644 index 00000000..d873e300 --- /dev/null +++ b/src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbProviders.cs @@ -0,0 +1,45 @@ +using Medallion.Threading.MongoDB; +using MongoDB.Driver; + +namespace Medallion.Threading.Tests.MongoDB; + +public sealed class TestingMongoDistributedLockProvider : TestingLockProvider +{ + private readonly string _collectionName = "DistributedLocks_" + Guid.NewGuid().ToString("N"); + private readonly IMongoDatabase _database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + + public override IDistributedLock CreateLockWithExactName(string name) + { + var @lock = new MongoDistributedLock(name, _database, _collectionName); + Strategy.KillHandleAction = () => + { + var collection = _database.GetCollection(_collectionName); + collection.DeleteOne(Builders.Filter.Eq(d => d.Id, name)); + }; + return @lock; + } + + public override string GetSafeName(string name) + { + return new MongoDistributedLock(name, _database, _collectionName).Name; + } + + public override string GetCrossProcessLockType() + { + return nameof(MongoDistributedLock); + } + + public override void Dispose() + { + // Clean up test collection + try + { + _database.DropCollection(_collectionName); + } + catch + { + // Ignore cleanup errors + } + base.Dispose(); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbSynchronizationStrategy.cs b/src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbSynchronizationStrategy.cs new file mode 100644 index 00000000..4324c1d5 --- /dev/null +++ b/src/DistributedLock.Tests/Infrastructure/MongoDB/TestingMongoDbSynchronizationStrategy.cs @@ -0,0 +1,11 @@ +namespace Medallion.Threading.Tests.MongoDB; + +public sealed class TestingMongoDbSynchronizationStrategy : TestingSynchronizationStrategy +{ + public Action? KillHandleAction { get; set; } + + public override void PrepareForHandleAbandonment() + { + KillHandleAction?.Invoke(); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/Infrastructure/Shared/MongoDbCredentials.cs b/src/DistributedLock.Tests/Infrastructure/Shared/MongoDbCredentials.cs new file mode 100644 index 00000000..8c8ce172 --- /dev/null +++ b/src/DistributedLock.Tests/Infrastructure/Shared/MongoDbCredentials.cs @@ -0,0 +1,31 @@ +using MongoDB.Driver; +using System.IO; + +namespace Medallion.Threading.Tests.MongoDB; + +internal static class MongoDbCredentials +{ + private static string? _connectionString; + + public static string GetConnectionString(string baseDirectory) + { + if (_connectionString != null) { return _connectionString; } + var file = Path.GetFullPath(Path.Combine(baseDirectory, "..", "..", "..", "credentials", "mongodb.txt")); + if (File.Exists(file)) + { + _connectionString = File.ReadAllText(file).Trim(); + } + else + { + // Default local MongoDB connection + _connectionString = "mongodb://localhost:27017"; + } + return _connectionString; + } + + public static IMongoDatabase GetDefaultDatabase(string baseDirectory) + { + var client = new MongoClient(GetConnectionString(baseDirectory)); + return client.GetDatabase("DistributedLockTests"); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs new file mode 100644 index 00000000..a8f8ef1e --- /dev/null +++ b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs @@ -0,0 +1,118 @@ +using Medallion.Threading.MongoDB; +using MongoDB.Driver; +using Moq; +using NUnit.Framework; + +namespace Medallion.Threading.Tests.MongoDB; + +public class MongoDistributedLockTest +{ + [Test] + public async Task TestBasicLockFunctionality() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + var lockName = TestHelper.UniqueName; + var @lock = new MongoDistributedLock(lockName, database); + await using (var handle = await @lock.AcquireAsync()) + { + Assert.That(handle, Is.Not.Null); + // Use async TryAcquireAsync instead of synchronous IsHeld() + var handle2 = await @lock.TryAcquireAsync(TimeSpan.Zero); + Assert.That(handle2, Is.Null, "Lock should be held"); + if (handle2 != null) + { + await handle2.DisposeAsync(); + } + } + // Verify lock is released + await using (var handle = await @lock.TryAcquireAsync(TimeSpan.FromSeconds(1))) + { + Assert.That(handle, Is.Not.Null, "Lock should be released"); + } + } + + [Test] + public async Task TestCustomCollectionName() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + var lockName = TestHelper.UniqueName; + const string CustomCollectionName = "CustomLocks"; + var @lock = new MongoDistributedLock(lockName, database, CustomCollectionName); + await using (var handle = await @lock.AcquireAsync()) + { + Assert.That(handle, Is.Not.Null); + } + + // Verify the collection was created + var collectionExists = (await database.ListCollectionNamesAsync()).ToList().Contains(CustomCollectionName); + Assert.That(collectionExists, Is.True); + + // Cleanup + await database.DropCollectionAsync(CustomCollectionName); + } + + [Test] + public async Task TestHandleLostToken() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + var lockName = TestHelper.UniqueName; + // Configure a short extension cadence so the test doesn't have to wait too long + var @lock = new MongoDistributedLock(lockName, database, options: o => o.ExtensionCadence(TimeSpan.FromMilliseconds(500))); + await using var handle = await @lock.AcquireAsync(); + Assert.That(handle, Is.Not.Null); + Assert.Multiple(() => + { + Assert.That(handle.HandleLostToken.CanBeCanceled, Is.True); + Assert.That(handle.HandleLostToken.IsCancellationRequested, Is.False); + }); + + // Manually delete the lock document to simulate lock loss + var collection = database.GetCollection("DistributedLocks"); + await collection.DeleteOneAsync(Builders.Filter.Eq(d => d.Id, lockName)); + + // Wait a bit for the extension task to detect the loss + await Task.Delay(TimeSpan.FromSeconds(2)); + Assert.That(handle.HandleLostToken.IsCancellationRequested, Is.True, "HandleLostToken should be signaled when lock is lost"); + } + + [Test] + public async Task TestLockContentionAsync() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + var lockName = TestHelper.UniqueName; + var lock1 = new MongoDistributedLock(lockName, database); + var lock2 = new MongoDistributedLock(lockName, database); + await using (var handle1 = await lock1.AcquireAsync()) + { + Assert.That(handle1, Is.Not.Null); + var handle2 = await lock2.TryAcquireAsync(TimeSpan.FromMilliseconds(100)); + Assert.That(handle2, Is.Null, "Should not acquire lock while held by another instance"); + } + + // After release, lock2 should be able to acquire + await using (var handle2 = await lock2.AcquireAsync(TimeSpan.FromSeconds(5))) + { + Assert.That(handle2, Is.Not.Null); + } + } + + [Test] + public void TestName() + { + const string Name = "\0🐉汉字\b\r\n\\"; + var database = new Mock(MockBehavior.Strict).Object; + var @lock = new MongoDistributedLock(Name, database); + @lock.Name.ShouldEqual(Name); + @lock.Key.ShouldEqual(Name); + } + + [Test] + public void TestValidatesConstructorParameters() + { + var database = new Mock(MockBehavior.Strict).Object; + Assert.Throws(() => new MongoDistributedLock(null!, database)); + Assert.Throws(() => new MongoDistributedLock(string.Empty, database)); + Assert.Throws(() => new MongoDistributedLock("key", null!)); + Assert.Throws(() => new MongoDistributedLock("key", database, (string)null!)); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs new file mode 100644 index 00000000..c6d4a430 --- /dev/null +++ b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs @@ -0,0 +1,64 @@ +using Medallion.Threading.MongoDB; +using NUnit.Framework; + +namespace Medallion.Threading.Tests.MongoDB; + +[Category("CI")] +public class MongoDistributedSynchronizationOptionsBuilderTest +{ + [Test] + public void TestBusyWaitSleepTimeValidation() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + Assert.Throws(() => + new MongoDistributedLock("test", database, options => options + .BusyWaitSleepTime(TimeSpan.FromSeconds(1), TimeSpan.FromMilliseconds(500)))); + Assert.Throws(() => + new MongoDistributedLock("test", database, options => options + .BusyWaitSleepTime(Timeout.InfiniteTimeSpan, TimeSpan.FromSeconds(1)))); + Assert.DoesNotThrow(() => + new MongoDistributedLock("test", database, options => options + .BusyWaitSleepTime(TimeSpan.FromMilliseconds(10), TimeSpan.FromSeconds(1)))); + } + + [Test] + public void TestExpiryValidation() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + Assert.Throws(() => + new MongoDistributedLock("test", database, options => options.Expiry(TimeSpan.FromMilliseconds(50)))); + Assert.Throws(() => + new MongoDistributedLock("test", database, options => options.Expiry(Timeout.InfiniteTimeSpan))); + Assert.DoesNotThrow(() => + new MongoDistributedLock("test", database, options => options.Expiry(TimeSpan.FromSeconds(1)))); + } + + [Test] + public void TestExtensionCadenceValidation() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + Assert.Throws(() => + new MongoDistributedLock("test", database, options => options + .Expiry(TimeSpan.FromSeconds(5)) + .ExtensionCadence(TimeSpan.FromSeconds(10)))); + Assert.DoesNotThrow(() => + new MongoDistributedLock("test", database, options => options + .Expiry(TimeSpan.FromSeconds(10)) + .ExtensionCadence(TimeSpan.FromSeconds(3)))); + } + + [Test] + public async Task TestOptionsAreApplied() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + var lockName = TestHelper.UniqueName; + var @lock = new MongoDistributedLock(lockName, database, options => options + .Expiry(TimeSpan.FromSeconds(60)) + .ExtensionCadence(TimeSpan.FromSeconds(20)) + .BusyWaitSleepTime(TimeSpan.FromMilliseconds(5), TimeSpan.FromMilliseconds(100))); + await using (var handle = await @lock.AcquireAsync()) + { + Assert.That(handle, Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs new file mode 100644 index 00000000..54879238 --- /dev/null +++ b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs @@ -0,0 +1,71 @@ +using Medallion.Threading.MongoDB; +using MongoDB.Driver; +using NUnit.Framework; + +namespace Medallion.Threading.Tests.MongoDB; + +[Category("CI")] +public class MongoDistributedSynchronizationProviderTest +{ + [Test] + public void TestArgumentValidation() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + Assert.Throws(() => new MongoDistributedSynchronizationProvider(null!)); + Assert.Throws(() => new MongoDistributedSynchronizationProvider(database, (string)null!)); + Assert.DoesNotThrow(() => new MongoDistributedSynchronizationProvider(database)); + Assert.DoesNotThrow(() => new MongoDistributedSynchronizationProvider(database, "CustomCollection")); + } + + [Test] + public void TestIDistributedLockProviderInterface() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + IDistributedLockProvider provider = new MongoDistributedSynchronizationProvider(database); + var @lock = provider.CreateLock("interfaceTest"); + Assert.That(@lock, Is.Not.Null); + Assert.That(@lock, Is.InstanceOf()); + Assert.That(@lock.Name, Is.EqualTo("interfaceTest")); + } + + [Test] + public async Task TestProviderCreateLock() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + var provider = new MongoDistributedSynchronizationProvider(database); + var lock1 = provider.CreateLock("testLock1"); + var lock2 = provider.CreateLock("testLock2"); + Assert.That(lock1, Is.Not.Null); + Assert.That(lock2, Is.Not.Null); + Assert.That(lock1.Name, Is.EqualTo("testLock1")); + Assert.That(lock2.Name, Is.EqualTo("testLock2")); + + // Test that locks work + await using (var handle1 = await lock1.AcquireAsync()) + await using (var handle2 = await lock2.AcquireAsync()) + { + Assert.That(handle1, Is.Not.Null); + Assert.That(handle2, Is.Not.Null); + } + } + + [Test] + public async Task TestProviderWithCustomCollection() + { + var database = MongoDbCredentials.GetDefaultDatabase(Environment.CurrentDirectory); + const string CustomCollection = "TestProviderLocks"; + var provider = new MongoDistributedSynchronizationProvider(database, CustomCollection); + var @lock = provider.CreateLock("testLock"); + await using (var handle = await @lock.AcquireAsync()) + { + Assert.That(handle, Is.Not.Null); + } + + // Verify the custom collection was used + var collectionExists = (await database.ListCollectionNamesAsync()).ToList().Contains(CustomCollection); + Assert.That(collectionExists, Is.True); + + // Cleanup + await database.DropCollectionAsync(CustomCollection); + } +} \ No newline at end of file diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index 89913e0e..6cc0a2f3 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -1,593 +1,6 @@ { "version": 2, "dependencies": { - ".NETFramework,Version=v4.7.2": { - "MedallionShell.StrongName": { - "type": "Direct", - "requested": "[1.6.2, )", - "resolved": "1.6.2", - "contentHash": "x7kIh8HiLHQrm5tcLEwNXhYfIHjQoK8ZS9MPx/LcCgNubtfFVJZm8Kk5/FSOalHjlXizcLAm6733L691l8cr/Q==" - }, - "Microsoft.NET.Test.Sdk": { - "type": "Direct", - "requested": "[17.9.0, )", - "resolved": "17.9.0", - "contentHash": "7GUNAUbJYn644jzwLm5BD3a2p9C1dmP8Hr6fDPDxgItQk9hBs1Svdxzz07KQ/UphMSmgza9AbijBJGmw5D658A==", - "dependencies": { - "Microsoft.CodeCoverage": "17.9.0" - } - }, - "Moq": { - "type": "Direct", - "requested": "[4.20.70, )", - "resolved": "4.20.70", - "contentHash": "4rNnAwdpXJBuxqrOCzCyICXHSImOTRktCgCWXWykuF1qwoIsVvEnR7PjbMk/eLOxWvhmj5Kwt+kDV3RGUYcNwg==", - "dependencies": { - "Castle.Core": "5.1.1", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "NUnit": { - "type": "Direct", - "requested": "[3.14.0, )", - "resolved": "3.14.0", - "contentHash": "R7iPwD7kbOaP3o2zldWJbWeMQAvDKD0uld27QvA3PAALl1unl7x0v2J7eGiJOYjimV/BuGT4VJmr45RjS7z4LA==" - }, - "NUnit.Analyzers": { - "type": "Direct", - "requested": "[4.1.0, )", - "resolved": "4.1.0", - "contentHash": "Odd1RusSMnfswIiCPbokAqmlcCCXjQ20poaXWrw+CWDnBY1vQ/x6ZGqgyJXpebPq5Uf8uEBe5iOAySsCdSrWdQ==" - }, - "NUnit3TestAdapter": { - "type": "Direct", - "requested": "[4.5.0, )", - "resolved": "4.5.0", - "contentHash": "s8JpqTe9bI2f49Pfr3dFRfoVSuFQyraTj68c3XXjIS/MRGvvkLnrg6RLqnTjdShX+AdFUCCU/4Xex58AdUfs6A==" - }, - "System.Data.SqlClient": { - "type": "Direct", - "requested": "[4.8.6, )", - "resolved": "4.8.6", - "contentHash": "2Ij/LCaTQRyAi5lAv7UUTV9R2FobC8xN9mE0fXBZohum/xLl8IZVmE98Rq5ugQHjCgTBRKqpXRb4ORulRdA6Ig==" - }, - "Azure.Core": { - "type": "Transitive", - "resolved": "1.38.0", - "contentHash": "IuEgCoVA0ef7E4pQtpC3+TkPbzaoQfa77HlfJDmfuaJUCVJmn7fT0izamZiryW5sYUFKizsftIxMkXKbgIcPMQ==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "1.1.1", - "System.ClientModel": "1.0.0", - "System.Diagnostics.DiagnosticSource": "6.0.1", - "System.Memory.Data": "1.0.2", - "System.Numerics.Vectors": "4.5.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Azure.Identity": { - "type": "Transitive", - "resolved": "1.11.4", - "contentHash": "Sf4BoE6Q3jTgFkgBkx7qztYOFELBCo+wQgpYDwal/qJ1unBH73ywPztIJKXBXORRzAeNijsuxhk94h0TIMvfYg==", - "dependencies": { - "Azure.Core": "1.38.0", - "Microsoft.Identity.Client": "4.61.3", - "Microsoft.Identity.Client.Extensions.Msal": "4.61.3", - "System.Memory": "4.5.4", - "System.Security.Cryptography.ProtectedData": "4.7.0", - "System.Text.Json": "4.7.2", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Azure.Storage.Common": { - "type": "Transitive", - "resolved": "12.18.1", - "contentHash": "ohCslqP9yDKIn+DVjBEOBuieB1QwsUCz+BwHYNaJ3lcIsTSiI4Evnq81HcKe8CqM8qvdModbipVQKpnxpbdWqA==", - "dependencies": { - "Azure.Core": "1.36.0", - "System.IO.Hashing": "6.0.0" - } - }, - "Castle.Core": { - "type": "Transitive", - "resolved": "5.1.1", - "contentHash": "rpYtIczkzGpf+EkZgDr9CClTdemhsrwA/W5hMoPjLkRFnXzH44zDLoovXeKtmxb1ykXK9aJVODSpiJml8CTw2g==" - }, - "Microsoft.Bcl.AsyncInterfaces": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "3WA9q9yVqJp222P3x1wYIGDAkpjAku0TMUaaQV22g6L67AI0LdOIrVS7Ht2vJfLHGSPVuqN94vIr15qn+HEkHw==", - "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Microsoft.Bcl.HashCode": { - "type": "Transitive", - "resolved": "1.1.1", - "contentHash": "MalY0Y/uM/LjXtHfX/26l2VtN4LDNZ2OE3aumNOHDLsT4fNYy2hiHXI4CXCqKpNUNm7iJ2brrc4J89UdaL56FA==" - }, - "Microsoft.CodeCoverage": { - "type": "Transitive", - "resolved": "17.9.0", - "contentHash": "RGD37ZSrratfScYXm7M0HjvxMxZyWZL4jm+XgMZbkIY1UPgjUpbNA/t+WTGj/rC/0Hm9A3IrH3ywbKZkOCnoZA==" - }, - "Microsoft.Data.SqlClient.SNI": { - "type": "Transitive", - "resolved": "5.2.0", - "contentHash": "0p2KMVc8WSC5JWgO+OdhYJiRM41dp6w2Dsd9JfEiHLPc6nyxBQgSrx9TYlbC8fRT2RK+HyWzDlv9ofFtxMOwQg==" - }, - "Microsoft.Extensions.DependencyInjection.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "cjWrLkJXK0rs4zofsK4bSdg+jhDLTaxrkXu4gS6Y7MAlCvRyNNgwY/lJi5RDlQOnSZweHqoyvgvbdvQsRIW+hg==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Microsoft.Extensions.Logging.Abstractions": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "arDBqTgFCyS0EvRV7O3MZturChstm50OJ0y9bDJvAcmEPJm0FFpFyjU/JLYyStNGGey081DvnQYlncNX5SJJGA==", - "dependencies": { - "Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.0", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5" - } - }, - "Microsoft.Identity.Client": { - "type": "Transitive", - "resolved": "4.61.3", - "contentHash": "naJo/Qm35Caaoxp5utcw+R8eU8ZtLz2ALh8S+gkekOYQ1oazfCQMWVT4NJ/FnHzdIJlm8dMz0oMpMGCabx5odA==", - "dependencies": { - "Microsoft.IdentityModel.Abstractions": "6.35.0", - "System.Diagnostics.DiagnosticSource": "6.0.1" - } - }, - "Microsoft.Identity.Client.Extensions.Msal": { - "type": "Transitive", - "resolved": "4.61.3", - "contentHash": "PWnJcznrSGr25MN8ajlc2XIDW4zCFu0U6FkpaNLEWLgd1NgFCp5uDY3mqLDgM8zCN8hqj8yo5wHYfLB2HjcdGw==", - "dependencies": { - "Microsoft.Identity.Client": "4.61.3", - "System.IO.FileSystem.AccessControl": "5.0.0", - "System.Security.Cryptography.ProtectedData": "4.5.0" - } - }, - "Microsoft.IdentityModel.Abstractions": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "xuR8E4Rd96M41CnUSCiOJ2DBh+z+zQSmyrYHdYhD6K4fXBcQGVnRCFQ0efROUYpP+p0zC1BLKr0JRpVuujTZSg==" - }, - "Microsoft.IdentityModel.JsonWebTokens": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "9wxai3hKgZUb4/NjdRKfQd0QJvtXKDlvmGMYACbEC8DFaicMFCFhQFZq9ZET1kJLwZahf2lfY5Gtcpsx8zYzbg==", - "dependencies": { - "Microsoft.IdentityModel.Tokens": "6.35.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2" - } - }, - "Microsoft.IdentityModel.Logging": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "jePrSfGAmqT81JDCNSY+fxVWoGuJKt9e6eJ+vT7+quVS55nWl//jGjUQn4eFtVKt4rt5dXaleZdHRB9J9AJZ7Q==", - "dependencies": { - "Microsoft.IdentityModel.Abstractions": "6.35.0" - } - }, - "Microsoft.IdentityModel.Protocols": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "BPQhlDzdFvv1PzaUxNSk+VEPwezlDEVADIKmyxubw7IiELK18uJ06RQ9QKKkds30XI+gDu9n8j24XQ8w7fjWcg==", - "dependencies": { - "Microsoft.IdentityModel.Logging": "6.35.0", - "Microsoft.IdentityModel.Tokens": "6.35.0" - } - }, - "Microsoft.IdentityModel.Protocols.OpenIdConnect": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "LMtVqnECCCdSmyFoCOxIE5tXQqkOLrvGrL7OxHg41DIm1bpWtaCdGyVcTAfOQpJXvzND9zUKIN/lhngPkYR8vg==", - "dependencies": { - "Microsoft.IdentityModel.Protocols": "6.35.0", - "System.IdentityModel.Tokens.Jwt": "6.35.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2" - } - }, - "Microsoft.IdentityModel.Tokens": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "RN7lvp7s3Boucg1NaNAbqDbxtlLj5Qeb+4uSS1TeK5FSBVM40P4DKaTKChT43sHyKfh7V0zkrMph6DdHvyA4bg==", - "dependencies": { - "Microsoft.IdentityModel.Logging": "6.35.0", - "System.Text.Encoding": "4.3.0", - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.7.2" - } - }, - "Pipelines.Sockets.Unofficial": { - "type": "Transitive", - "resolved": "2.2.8", - "contentHash": "zG2FApP5zxSx6OcdJQLbZDk2AVlN2BNQD6MorwIfV6gVj0RRxWPEp2LXAxqDGZqeNV1Zp0BNPcNaey/GXmTdvQ==", - "dependencies": { - "System.IO.Pipelines": "5.0.1" - } - }, - "System.Buffers": { - "type": "Transitive", - "resolved": "4.5.1", - "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" - }, - "System.ClientModel": { - "type": "Transitive", - "resolved": "1.0.0", - "contentHash": "I3CVkvxeqFYjIVEP59DnjbeoGNfo/+SZrCLpRz2v/g0gpCHaEMPtWSY0s9k/7jR1rAsLNg2z2u1JRB76tPjnIw==", - "dependencies": { - "System.Memory.Data": "1.0.2", - "System.Text.Json": "4.7.2" - } - }, - "System.Collections.Immutable": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "AurL6Y5BA1WotzlEvVaIDpqzpIPvYnnldxru8oXJU2yFxFUy3+pNXjXd1ymO+RA0rq0+590Q8gaz2l3Sr7fmqg==", - "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Configuration.ConfigurationManager": { - "type": "Transitive", - "resolved": "6.0.1", - "contentHash": "jXw9MlUu/kRfEU0WyTptAVueupqIeE3/rl0EZDMlf8pcvJnitQ8HeVEp69rZdaStXwTV72boi/Bhw8lOeO+U2w==", - "dependencies": { - "System.Security.Permissions": "6.0.0" - } - }, - "System.Diagnostics.DiagnosticSource": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "c9xLpVz6PL9lp/djOWtk5KPDZq3cSYpmXoJQY524EOtuFl5z9ZtsotpsyrDW40U1DRnQSYvcPKEUV0X//u6gkQ==", - "dependencies": { - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Formats.Asn1": { - "type": "Transitive", - "resolved": "8.0.1", - "contentHash": "XqKba7Mm/koKSjKMfW82olQdmfbI5yqeoLV/tidRp7fbh5rmHAQ5raDI/7SU0swTzv+jgqtUGkzmFxuUg0it1A==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.ValueTuple": "4.5.0" - } - }, - "System.IdentityModel.Tokens.Jwt": { - "type": "Transitive", - "resolved": "6.35.0", - "contentHash": "yxGIQd3BFK7F6S62/7RdZk3C/mfwyVxvh6ngd1VYMBmbJ1YZZA9+Ku6suylVtso0FjI0wbElpJ0d27CdsyLpBQ==", - "dependencies": { - "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", - "Microsoft.IdentityModel.Tokens": "6.35.0" - } - }, - "System.IO.Compression": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "YHndyoiV90iu4iKG115ibkhrG+S3jBm8Ap9OwoUAzO5oPDAWcr0SFwQFm0HjM8WkEZWo0zvLTyLmbvTkW1bXgg==" - }, - "System.IO.FileSystem.AccessControl": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "SxHB3nuNrpptVk+vZ/F+7OHEpoHUIKKMl02bUmYHQr1r+glbZQxs7pRtsf4ENO29TVm2TH3AEeep2fJcy92oYw==", - "dependencies": { - "System.Security.AccessControl": "5.0.0", - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.IO.Hashing": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "Rfm2jYCaUeGysFEZjDe7j1R4x6Z6BzumS/vUT5a1AA/AWJuGX71PoGB0RmpyX3VmrGqVnAwtfMn39OHR8Y/5+g==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.4" - } - }, - "System.IO.Pipelines": { - "type": "Transitive", - "resolved": "5.0.1", - "contentHash": "qEePWsaq9LoEEIqhbGe6D5J8c9IqQOUuTzzV6wn1POlfdLkJliZY3OlB0j0f17uMWlqZYjH7txj+2YbyrIA8Yg==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.4", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "System.Memory": { - "type": "Transitive", - "resolved": "4.5.5", - "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Numerics.Vectors": "4.5.0", - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, - "System.Memory.Data": { - "type": "Transitive", - "resolved": "1.0.2", - "contentHash": "JGkzeqgBsiZwKJZ1IxPNsDFZDhUvuEdX8L8BDC8N3KOj+6zMcNU28CNN59TpZE/VJYy9cP+5M+sbxtWJx3/xtw==", - "dependencies": { - "System.Text.Encodings.Web": "4.7.2", - "System.Text.Json": "4.6.0" - } - }, - "System.Numerics.Vectors": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" - }, - "System.Runtime.CompilerServices.Unsafe": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" - }, - "System.Runtime.InteropServices.RuntimeInformation": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "cbz4YJMqRDR7oLeMRbdYv7mYzc++17lNhScCX0goO2XpGWdvAt60CGN+FHdePUEHCe/Jy9jUlvNAiNdM+7jsOw==" - }, - "System.Security.AccessControl": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "AUADIc0LIEQe7MzC+I0cl0rAT8RrTAKFHl53yHjEUzNVIaUlhFY11vc2ebiVJzVBuOzun6F7FBA+8KAbGTTedQ==", - "dependencies": { - "System.Security.Principal.Windows": "5.0.0" - } - }, - "System.Security.Cryptography.ProtectedData": { - "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" - }, - "System.Security.Permissions": { - "type": "Transitive", - "resolved": "6.0.0", - "contentHash": "T/uuc7AklkDoxmcJ7LGkyX1CcSviZuLCa4jg3PekfJ7SU0niF0IVTXwUiNVP9DSpzou2PpxJ+eNY2IfDM90ZCg==", - "dependencies": { - "System.Security.AccessControl": "6.0.0" - } - }, - "System.Security.Principal.Windows": { - "type": "Transitive", - "resolved": "5.0.0", - "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" - }, - "System.Text.Encoding": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==" - }, - "System.Text.Encodings.Web": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "yev/k9GHAEGx2Rg3/tU6MQh4HGBXJs70y7j1LaM1i/ER9po+6nnQ6RRqTJn1E7Xu0fbIFK80Nh5EoODxrbxwBQ==", - "dependencies": { - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0" - } - }, - "System.Text.Json": { - "type": "Transitive", - "resolved": "8.0.5", - "contentHash": "0f1B50Ss7rqxXiaBJyzUu9bWFOO2/zSlifZ/UNMdiIpDYe4cY4LQQicP4nirK1OS31I43rn062UIJ1Q9bpmHpg==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "8.0.0", - "System.Buffers": "4.5.1", - "System.Memory": "4.5.5", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Encodings.Web": "8.0.0", - "System.Threading.Tasks.Extensions": "4.5.4", - "System.ValueTuple": "4.5.0" - } - }, - "System.Threading.Channels": { - "type": "Transitive", - "resolved": "8.0.0", - "contentHash": "CMaFr7v+57RW7uZfZkPExsPB6ljwzhjACWW1gfU35Y56rk72B/Wu+sTqxVmGSk4SFUlPc3cjeKND0zktziyjBA==", - "dependencies": { - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "System.Threading.Tasks.Extensions": { - "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==", - "dependencies": { - "System.Runtime.CompilerServices.Unsafe": "4.5.3" - } - }, - "System.ValueTuple": { - "type": "Transitive", - "resolved": "4.5.0", - "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" - }, - "distributedlock": { - "type": "Project", - "dependencies": { - "DistributedLock.Azure": "[1.0.2, )", - "DistributedLock.FileSystem": "[1.0.3, )", - "DistributedLock.MySql": "[1.0.2, )", - "DistributedLock.Oracle": "[1.0.4, )", - "DistributedLock.Postgres": "[1.3.0, )", - "DistributedLock.Redis": "[1.1.0, )", - "DistributedLock.SqlServer": "[1.0.6, )", - "DistributedLock.WaitHandles": "[1.0.1, )", - "DistributedLock.ZooKeeper": "[1.0.0, )" - } - }, - "distributedlock.azure": { - "type": "Project", - "dependencies": { - "Azure.Storage.Blobs": "[12.19.1, )", - "DistributedLock.Core": "[1.0.8, )" - } - }, - "distributedlock.core": { - "type": "Project", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "[8.0.0, )", - "System.ValueTuple": "[4.5.0, )" - } - }, - "distributedlock.filesystem": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )" - } - }, - "distributedlock.mysql": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )", - "MySqlConnector": "[2.3.5, )" - } - }, - "distributedlock.oracle": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )", - "Oracle.ManagedDataAccess": "[23.6.1, )" - } - }, - "distributedlock.postgres": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )", - "Npgsql": "[8.0.6, )" - } - }, - "distributedlock.redis": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )", - "StackExchange.Redis": "[2.7.33, )" - } - }, - "distributedlock.sqlserver": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )", - "Microsoft.Data.SqlClient": "[5.2.2, )" - } - }, - "distributedlock.waithandles": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )" - } - }, - "distributedlock.zookeeper": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )", - "ZooKeeperNetEx": "[3.4.12.4, )" - } - }, - "Azure.Storage.Blobs": { - "type": "CentralTransitive", - "requested": "[12.19.1, )", - "resolved": "12.19.1", - "contentHash": "x43hWFJ4sPQ23TD4piCwT+KlQpZT8pNDAzqj6yUCqh+WJ2qcQa17e1gh6ZOeT2QNFQTTDSuR56fm2bIV7i11/w==", - "dependencies": { - "Azure.Storage.Common": "12.18.1", - "System.Text.Json": "4.7.2" - } - }, - "Microsoft.Data.SqlClient": { - "type": "CentralTransitive", - "requested": "[5.2.2, )", - "resolved": "5.2.2", - "contentHash": "mtoeRMh7F/OA536c/Cnh8L4H0uLSKB5kSmoi54oN7Fp0hNJDy22IqyMhaMH4PkDCqI7xL//Fvg9ldtuPHG0h5g==", - "dependencies": { - "Azure.Identity": "1.11.4", - "Microsoft.Data.SqlClient.SNI": "5.2.0", - "Microsoft.Identity.Client": "4.61.3", - "Microsoft.IdentityModel.JsonWebTokens": "6.35.0", - "Microsoft.IdentityModel.Protocols.OpenIdConnect": "6.35.0", - "System.Buffers": "4.5.1", - "System.Configuration.ConfigurationManager": "6.0.1", - "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", - "System.Text.Encodings.Web": "6.0.0" - } - }, - "MySqlConnector": { - "type": "CentralTransitive", - "requested": "[2.3.5, )", - "resolved": "2.3.5", - "contentHash": "AmEfUPkFl+Ev6jJ8Dhns3CYHBfD12RHzGYWuLt6DfG6/af6YvOMyPz74ZPPjBYQGRJkumD2Z48Kqm8s5DJuhLA==", - "dependencies": { - "Microsoft.Extensions.Logging.Abstractions": "7.0.1", - "System.Diagnostics.DiagnosticSource": "7.0.2", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "Npgsql": { - "type": "CentralTransitive", - "requested": "[8.0.6, )", - "resolved": "8.0.6", - "contentHash": "KaS6CY5kY2Sd0P00MSeFcOI3t2DiQ4UWG8AuRpVOUeDWITOKfoEEG91DP3cmT6aerixPkjwKgXxnpDxIkDpO6g==", - "dependencies": { - "Microsoft.Bcl.HashCode": "1.1.1", - "Microsoft.Extensions.Logging.Abstractions": "8.0.0", - "System.Collections.Immutable": "8.0.0", - "System.Diagnostics.DiagnosticSource": "8.0.0", - "System.Runtime.CompilerServices.Unsafe": "6.0.0", - "System.Text.Json": "8.0.5", - "System.Threading.Channels": "8.0.0" - } - }, - "Oracle.ManagedDataAccess": { - "type": "CentralTransitive", - "requested": "[23.6.1, )", - "resolved": "23.6.1", - "contentHash": "EZi+mahzUwQFWs9Is8ed94eTzWOlfCLMd+DDWukf/h/brTz1wB9Qk3fsxBrjw9+fEXrxDgx4uXNiPHNPRS3BeQ==", - "dependencies": { - "System.Diagnostics.DiagnosticSource": "6.0.1", - "System.Formats.Asn1": "8.0.1", - "System.Text.Json": "8.0.5", - "System.Threading.Tasks.Extensions": "4.5.4" - } - }, - "StackExchange.Redis": { - "type": "CentralTransitive", - "requested": "[2.7.33, )", - "resolved": "2.7.33", - "contentHash": "2kCX5fvhEE824a4Ab5Imyi8DRuGuTxyklXV01kegkRpsWJcPmO6+GAQ+HegKxvXAxlXZ8yaRspvWJ8t3mMClfQ==", - "dependencies": { - "Microsoft.Bcl.AsyncInterfaces": "5.0.0", - "Microsoft.Extensions.Logging.Abstractions": "6.0.0", - "Pipelines.Sockets.Unofficial": "2.2.8", - "System.IO.Compression": "4.3.0", - "System.Threading.Channels": "5.0.0" - } - }, - "ZooKeeperNetEx": { - "type": "CentralTransitive", - "requested": "[3.4.12.4, )", - "resolved": "3.4.12.4", - "contentHash": "YECtByVSH7TRjQKplwOWiKyanCqYE5eEkGk5YtHJgsnbZ6+p1o0Gvs5RIsZLotiAVa6Niez1BJyKY/RDY/L6zg==" - } - }, "net8.0": { "MedallionShell.StrongName": { "type": "Direct", @@ -692,6 +105,14 @@ "System.Diagnostics.EventLog": "6.0.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "1.1.1", @@ -797,8 +218,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "3.1.0", - "contentHash": "z7aeg8oHln2CuNulfhiLYxCVMPEwBl3rzicjvIX+4sUuCwvXw5oXQEtbiU2c0z4qYL5L3Kmx0mMA/+t/SbY67w==" + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -829,11 +250,20 @@ }, "Microsoft.Win32.Registry": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "KSrRMb5vNi0CWSGG1++id2ZOs/1QhRqROt+qgbEAdQuGjGrFcl4AOl4/exGPUYz2wUnU42nvJqon1T3U0kPXLA==", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", "dependencies": { - "System.Security.AccessControl": "4.7.0", - "System.Security.Principal.Windows": "4.7.0" + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" } }, "NETStandard.Library": { @@ -893,6 +323,21 @@ "resolved": "4.4.0", "contentHash": "YhEdSQUsTx+C8m8Bw7ar5/VesXvCFMItyZF7G1AUY+OM0VPZUOeAVpJ4Wl6fydBGUYZxojTDR3I6Bj/+BPkJNA==" }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==" + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, "System.ClientModel": { "type": "Transitive", "resolved": "1.0.0", @@ -963,8 +408,8 @@ }, "System.Memory": { "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==" + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" }, "System.Memory.Data": { "type": "Transitive", @@ -1009,11 +454,11 @@ }, "System.Security.AccessControl": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "JECvTt5aFF3WT3gHpfofL2MNNP6v84sxtXxpqhLBCcDRzqsPBmHhQ6shv4DwwN2tRlzsUxtb3G9M3763rbXKDg==", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", "dependencies": { - "Microsoft.NETCore.Platforms": "3.1.0", - "System.Security.Principal.Windows": "4.7.0" + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" } }, "System.Security.Cryptography.Cng": { @@ -1036,8 +481,8 @@ }, "System.Security.Principal.Windows": { "type": "Transitive", - "resolved": "4.7.0", - "contentHash": "ojD0PX0XhneCsUbAZVKdb7h/70vyYMDYs85lwEI+LngEONe/17A0cFaRFqZU+sOEidcVswYWikYOQ9PPfjlbtQ==" + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, "System.Text.Encoding": { "type": "Transitive", @@ -1064,6 +509,11 @@ "resolved": "4.5.4", "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==" + }, "distributedlock": { "type": "Project", "dependencies": { @@ -1094,6 +544,13 @@ "DistributedLock.Core": "[1.0.8, )" } }, + "distributedlock.mongodb": { + "type": "Project", + "dependencies": { + "DistributedLock.Core": "[1.0.8, )", + "MongoDB.Driver": "[3.5.0, )" + } + }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -1169,6 +626,21 @@ "System.Runtime.Caching": "8.0.0" } }, + "MongoDB.Driver": { + "type": "CentralTransitive", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "ZstdSharp.Port": "0.7.3" + } + }, "MySqlConnector": { "type": "CentralTransitive", "requested": "[2.3.5, )", diff --git a/src/DistributedLock.sln b/src/DistributedLock.sln index 61d17d38..30c8dd91 100644 --- a/src/DistributedLock.sln +++ b/src/DistributedLock.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.9.34616.47 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11116.177 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DistributedLock", "DistributedLock\DistributedLock.csproj", "{C1F56B68-C2EE-48E5-A99B-B40D397AE34F}" EndProject @@ -47,6 +47,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution package.readme.md = package.readme.md EndProjectSection EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "DistributedLock.MongoDB", "DistributedLock.MongoDB\DistributedLock.MongoDB.csproj", "{92074E6D-99D1-46B1-A0AE-442EA1FEA397}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -109,6 +111,10 @@ Global {1CAB9A1D-0C02-459C-A90E-47819832BD58}.Debug|Any CPU.Build.0 = Debug|Any CPU {1CAB9A1D-0C02-459C-A90E-47819832BD58}.Release|Any CPU.ActiveCfg = Release|Any CPU {1CAB9A1D-0C02-459C-A90E-47819832BD58}.Release|Any CPU.Build.0 = Release|Any CPU + {92074E6D-99D1-46B1-A0AE-442EA1FEA397}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92074E6D-99D1-46B1-A0AE-442EA1FEA397}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92074E6D-99D1-46B1-A0AE-442EA1FEA397}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92074E6D-99D1-46B1-A0AE-442EA1FEA397}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/DistributedLock/DistributedLock.csproj b/src/DistributedLock/DistributedLock.csproj index cdb3a088..48c5da8d 100644 --- a/src/DistributedLock/DistributedLock.csproj +++ b/src/DistributedLock/DistributedLock.csproj @@ -57,7 +57,7 @@ - + \ No newline at end of file diff --git a/src/DistributedLock/packages.lock.json b/src/DistributedLock/packages.lock.json index 44b4b439..5407bb0c 100644 --- a/src/DistributedLock/packages.lock.json +++ b/src/DistributedLock/packages.lock.json @@ -436,6 +436,13 @@ "DistributedLock.Core": "[1.0.8, )" } }, + "distributedlock.mongodb": { + "type": "Project", + "dependencies": { + "DistributedLock.Core": "[1.0.8, )", + "MySqlConnector": "[3.5.0, )" + } + }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -973,6 +980,9 @@ "DistributedLock.Core": "[1.0.8, )" } }, + "distributedlock.mongodb": { + "type": "Project" + }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -1616,6 +1626,9 @@ "DistributedLock.Core": "[1.0.8, )" } }, + "distributedlock.mongodb": { + "type": "Project" + }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -1812,6 +1825,14 @@ "System.IO.Hashing": "6.0.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "8.0.0", @@ -1955,6 +1976,15 @@ "System.Security.Principal.Windows": "5.0.0" } }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -1963,6 +1993,22 @@ "System.IO.Pipelines": "5.0.1" } }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==", + "dependencies": { + "System.Text.Encoding.CodePages": "5.0.0" + } + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "4.7.1" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", @@ -2263,6 +2309,14 @@ "System.Runtime.CompilerServices.Unsafe": "4.5.3" } }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "6.0.0" + } + }, "distributedlock.azure": { "type": "Project", "dependencies": { @@ -2279,6 +2333,13 @@ "DistributedLock.Core": "[1.0.8, )" } }, + "distributedlock.mongodb": { + "type": "Project", + "dependencies": { + "DistributedLock.Core": "[1.0.8, )", + "MongoDB.Driver": "[3.5.0, )" + } + }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -2361,6 +2422,21 @@ "System.Text.Encodings.Web": "6.0.0" } }, + "MongoDB.Driver": { + "type": "CentralTransitive", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "ZstdSharp.Port": "0.7.3" + } + }, "MySqlConnector": { "type": "CentralTransitive", "requested": "[2.3.5, )", diff --git a/src/DistributedLockTaker/packages.lock.json b/src/DistributedLockTaker/packages.lock.json index 1584b3e4..8476aab4 100644 --- a/src/DistributedLockTaker/packages.lock.json +++ b/src/DistributedLockTaker/packages.lock.json @@ -529,7 +529,7 @@ "contentHash": "YECtByVSH7TRjQKplwOWiKyanCqYE5eEkGk5YtHJgsnbZ6+p1o0Gvs5RIsZLotiAVa6Niez1BJyKY/RDY/L6zg==" } }, - ".NETFramework,Version=v4.7.2/win7-x86": { + ".NETFramework,Version=v4.7.2/win-x86": { "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.1", @@ -604,9 +604,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.4, )", - "resolved": "8.0.4", - "contentHash": "PZb5nfQ+U19nhnmnR9T1jw+LTmozhuG2eeuzuW5A7DqxD/UXW2ucjmNJqnqOuh8rdPzM3MQXoF8AfFCedJdCUw==" + "requested": "[8.0.21, )", + "resolved": "8.0.21", + "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" }, "Azure.Core": { "type": "Transitive", @@ -1076,7 +1076,7 @@ "contentHash": "YECtByVSH7TRjQKplwOWiKyanCqYE5eEkGk5YtHJgsnbZ6+p1o0Gvs5RIsZLotiAVa6Niez1BJyKY/RDY/L6zg==" } }, - "net8.0/win7-x86": { + "net8.0/win-x86": { "Microsoft.Data.SqlClient.SNI.runtime": { "type": "Transitive", "resolved": "5.2.0", @@ -1105,11 +1105,6 @@ "resolved": "4.3.0", "contentHash": "+ihI5VaXFCMVPJNstG4O4eo1CfbrByLxRrQQTqOTp1ttK0kUKDqOdBSTaCB2IBk/QtjDrs6+x4xuezyMXdm0HQ==" }, - "runtime.win7.System.Private.Uri": { - "type": "Transitive", - "resolved": "4.3.0", - "contentHash": "Q+IBgaPYicSQs2tBlmXqbS25c/JLIthWrgrpMwxKSOobW/OqIMVFruUGfuaz4QABVzV8iKdCAbN7APY7Tclbnw==" - }, "System.Diagnostics.EventLog": { "type": "Transitive", "resolved": "8.0.0", @@ -1193,8 +1188,7 @@ "contentHash": "o1+7RJnu3Ik3PazR7Z7tJhjPdE000Eq2KGLLWhqJJKXj04wrS8lwb1OFtDF9jzXXADhUuZNJZlPc98uwwqmpFA==", "dependencies": { "Microsoft.NETCore.Platforms": "1.1.1", - "Microsoft.NETCore.Targets": "1.1.3", - "runtime.win7.System.Private.Uri": "4.3.0" + "Microsoft.NETCore.Targets": "1.1.3" } }, "System.Threading.AccessControl": { From 80ff241f8dc081a8702d481e1c360158aa6c3e66 Mon Sep 17 00:00:00 2001 From: Joes Date: Tue, 21 Oct 2025 18:02:53 +0800 Subject: [PATCH 02/13] =?UTF-8?q?=E6=8F=90=E9=AB=98=E5=B0=81=E8=A3=85?= =?UTF-8?q?=E6=80=A7=E5=B9=B6=E4=BC=98=E5=8C=96=E4=BB=A3=E7=A0=81=E5=8F=AF?= =?UTF-8?q?=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将字段和构造函数的访问修饰符从 internal 修改为 private,以增强封装性。修复注释中的拼写错误并优化格式,提升代码的可读性和维护性。 --- .../MongoDistributedSynchronizationOptionsBuilder.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs b/src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs index eae699e1..b021030d 100644 --- a/src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs +++ b/src/DistributedLock.MongoDB/MongoDistributedSynchronizationOptionsBuilder.cs @@ -7,22 +7,22 @@ namespace Medallion.Threading.MongoDB; /// public sealed class MongoDistributedSynchronizationOptionsBuilder { - internal static readonly TimeoutValue DefaultExpiry = TimeSpan.FromSeconds(30); + private static readonly TimeoutValue DefaultExpiry = TimeSpan.FromSeconds(30); /// /// We don't want to allow expiry to go too low, since then the lock doesn't even work /// - internal static readonly TimeoutValue MinimumExpiry = TimeSpan.FromSeconds(.1); + private static readonly TimeoutValue MinimumExpiry = TimeSpan.FromSeconds(.1); private TimeoutValue? _expiry, _extensionCadence, _minBusyWaitSleepTime, _maxBusyWaitSleepTime; - internal MongoDistributedSynchronizationOptionsBuilder() { } + private MongoDistributedSynchronizationOptionsBuilder() { } /// /// Specifies how long the lock will last, absent auto-extension. Because auto-extension exists, /// this value generally will have little effect on program behavior. However, making the expiry longer means that /// auto-extension requests can occur less frequently, saving resources. On the other hand, when a lock is abandoned - /// without explicit release (e. g. if the holding process crashes), the expiry determines how long other processes + /// without explicit release (e.g. if the holding process crashes), the expiry determines how long other processes /// would need to wait in order to acquire it. /// Defaults to 30s. /// From e06c413145391bd2db6c08c917ad6fc2085ec887 Mon Sep 17 00:00:00 2001 From: Joes Date: Tue, 21 Oct 2025 18:12:21 +0800 Subject: [PATCH 03/13] =?UTF-8?q?fix:=20=E4=B8=BA=20DistributedLock.MongoD?= =?UTF-8?q?B=20=E6=B7=BB=E5=8A=A0=20InternalsVisibleTo?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `AssemblyAttributes.cs` 文件中,新增了对 `DistributedLock.MongoDB` 的 `InternalsVisibleTo` 特性声明。 此更改允许 `DistributedLock.MongoDB` 程序集访问当前程序集的内部成员。 其他现有的 `InternalsVisibleTo` 特性未受影响。 --- src/DistributedLock.Core/AssemblyAttributes.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/DistributedLock.Core/AssemblyAttributes.cs b/src/DistributedLock.Core/AssemblyAttributes.cs index d2e9bff3..888ed9ef 100644 --- a/src/DistributedLock.Core/AssemblyAttributes.cs +++ b/src/DistributedLock.Core/AssemblyAttributes.cs @@ -15,4 +15,5 @@ [assembly: InternalsVisibleTo("DistributedLock.ZooKeeper, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fd3af56ccc8ed94fffe25bfd651e6a5674f8f20a76d37de800dd0f7380e04f0fde2da6fa200380b14fe398605b6f470c87e5e0a0bf39ae871f07536a4994aa7a0057c4d3bcedc8fef3eecb0c88c2024a1b3289305c2393acd9fb9f9a42d0bd7826738ce864d507575ea3a1fe1746ab19823303269f79379d767949807f494be8")] [assembly: InternalsVisibleTo("DistributedLock.MySql, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fd3af56ccc8ed94fffe25bfd651e6a5674f8f20a76d37de800dd0f7380e04f0fde2da6fa200380b14fe398605b6f470c87e5e0a0bf39ae871f07536a4994aa7a0057c4d3bcedc8fef3eecb0c88c2024a1b3289305c2393acd9fb9f9a42d0bd7826738ce864d507575ea3a1fe1746ab19823303269f79379d767949807f494be8")] [assembly: InternalsVisibleTo("DistributedLock.Oracle, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fd3af56ccc8ed94fffe25bfd651e6a5674f8f20a76d37de800dd0f7380e04f0fde2da6fa200380b14fe398605b6f470c87e5e0a0bf39ae871f07536a4994aa7a0057c4d3bcedc8fef3eecb0c88c2024a1b3289305c2393acd9fb9f9a42d0bd7826738ce864d507575ea3a1fe1746ab19823303269f79379d767949807f494be8")] +[assembly: InternalsVisibleTo("DistributedLock.MongoDB, PublicKey=0024000004800000940000000602000000240000525341310004000001000100fd3af56ccc8ed94fffe25bfd651e6a5674f8f20a76d37de800dd0f7380e04f0fde2da6fa200380b14fe398605b6f470c87e5e0a0bf39ae871f07536a4994aa7a0057c4d3bcedc8fef3eecb0c88c2024a1b3289305c2393acd9fb9f9a42d0bd7826738ce864d507575ea3a1fe1746ab19823303269f79379d767949807f494be8")] #endif From 003ce7b9f262c8c5583b931d60f9c5208f33f83b Mon Sep 17 00:00:00 2001 From: Joes Date: Wed, 22 Oct 2025 09:18:07 +0800 Subject: [PATCH 04/13] fix: ci error --- src/DistributedLock.Tests/packages.lock.json | 1 + src/DistributedLock/DistributedLock.csproj | 1 + src/DistributedLock/packages.lock.json | 144 ++++++++- src/DistributedLockTaker/packages.lock.json | 305 ++++++++++++++++++- 4 files changed, 431 insertions(+), 20 deletions(-) diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index 6cc0a2f3..8fcb7525 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -519,6 +519,7 @@ "dependencies": { "DistributedLock.Azure": "[1.0.2, )", "DistributedLock.FileSystem": "[1.0.3, )", + "DistributedLock.MongoDB": "[1.3.0, )", "DistributedLock.MySql": "[1.0.2, )", "DistributedLock.Oracle": "[1.0.4, )", "DistributedLock.Postgres": "[1.3.0, )", diff --git a/src/DistributedLock/DistributedLock.csproj b/src/DistributedLock/DistributedLock.csproj index 48c5da8d..2cbbc0e1 100644 --- a/src/DistributedLock/DistributedLock.csproj +++ b/src/DistributedLock/DistributedLock.csproj @@ -54,6 +54,7 @@ + diff --git a/src/DistributedLock/packages.lock.json b/src/DistributedLock/packages.lock.json index 5407bb0c..8d115faf 100644 --- a/src/DistributedLock/packages.lock.json +++ b/src/DistributedLock/packages.lock.json @@ -436,13 +436,6 @@ "DistributedLock.Core": "[1.0.8, )" } }, - "distributedlock.mongodb": { - "type": "Project", - "dependencies": { - "DistributedLock.Core": "[1.0.8, )", - "MySqlConnector": "[3.5.0, )" - } - }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -628,6 +621,15 @@ "System.IO.Hashing": "6.0.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0", + "System.Buffers": "4.5.1" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "8.0.0", @@ -750,6 +752,24 @@ "resolved": "8.0.0", "contentHash": "dk9JPxTCIevS75HyEQ0E4OVAFhB2N+V9ShCXf8Q6FkUQZDkgLI12y679Nym1YqsiSysuQskT7Z+6nUf3yab6Vw==" }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -758,6 +778,25 @@ "System.IO.Pipelines": "5.0.1" } }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Text.Encoding.CodePages": "5.0.0" + } + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "4.7.1", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", @@ -817,6 +856,11 @@ "Microsoft.IdentityModel.Tokens": "6.35.0" } }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==" + }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", @@ -869,11 +913,24 @@ "System.Text.Json": "4.6.0" } }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==" + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", @@ -892,11 +949,41 @@ "System.Security.Principal.Windows": "5.0.0" } }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" + }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", @@ -915,6 +1002,14 @@ "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==" }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -960,6 +1055,15 @@ "resolved": "4.5.0", "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "5.0.0", + "System.Memory": "4.5.5" + } + }, "distributedlock.azure": { "type": "Project", "dependencies": { @@ -981,7 +1085,11 @@ } }, "distributedlock.mongodb": { - "type": "Project" + "type": "Project", + "dependencies": { + "DistributedLock.Core": "[1.0.8, )", + "MongoDB.Driver": "[3.5.0, )" + } }, "distributedlock.mysql": { "type": "Project", @@ -1058,6 +1166,23 @@ "System.Text.Encodings.Web": "6.0.0" } }, + "MongoDB.Driver": { + "type": "CentralTransitive", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "System.Net.Http": "4.3.4", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "ZstdSharp.Port": "0.7.3" + } + }, "MySqlConnector": { "type": "CentralTransitive", "requested": "[2.3.5, )", @@ -1626,9 +1751,6 @@ "DistributedLock.Core": "[1.0.8, )" } }, - "distributedlock.mongodb": { - "type": "Project" - }, "distributedlock.mysql": { "type": "Project", "dependencies": { diff --git a/src/DistributedLockTaker/packages.lock.json b/src/DistributedLockTaker/packages.lock.json index 8476aab4..0feb1eeb 100644 --- a/src/DistributedLockTaker/packages.lock.json +++ b/src/DistributedLockTaker/packages.lock.json @@ -40,6 +40,15 @@ "System.IO.Hashing": "6.0.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0", + "System.Buffers": "4.5.1" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "8.0.0", @@ -152,6 +161,24 @@ "System.Text.Json": "4.7.2" } }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "Pipelines.Sockets.Unofficial": { "type": "Transitive", "resolved": "2.2.8", @@ -160,6 +187,25 @@ "System.IO.Pipelines": "5.0.1" } }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Text.Encoding.CodePages": "5.0.0" + } + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==", + "dependencies": { + "System.Memory": "4.5.4", + "System.Runtime.CompilerServices.Unsafe": "4.7.1", + "System.Threading.Tasks.Extensions": "4.5.4" + } + }, "System.Buffers": { "type": "Transitive", "resolved": "4.5.1", @@ -219,6 +265,11 @@ "Microsoft.IdentityModel.Tokens": "6.35.0" } }, + "System.IO": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "3qjaHvxQPDpSOYICjUoTsmoq5u6QJAFRUITgeT/4gqkF1bajbSmb1kwSxEA8AHlofqgcKJcM8udgieRNhaJ5Cg==" + }, "System.IO.Compression": { "type": "Transitive", "resolved": "4.3.0", @@ -271,11 +322,24 @@ "System.Text.Json": "4.6.0" } }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, "System.Numerics.Vectors": { "type": "Transitive", "resolved": "4.5.0", "contentHash": "QQTlPTl06J/iiDbJCiepZ4H//BVraReU4O4EoRw1U02H5TLUIT7xn3GnDp9AXPSlJUDyFs4uWjWafNX6WrAojQ==" }, + "System.Runtime": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "JufQi0vPQ0xGnAczR13AUFglDyVYt4Kqnz1AZaiKZ5+GICq0/1MH/mO/eAJHt/mHW1zjKBJd7kV26SrxddAhiw==" + }, "System.Runtime.CompilerServices.Unsafe": { "type": "Transitive", "resolved": "6.0.0", @@ -294,11 +358,41 @@ "System.Security.Principal.Windows": "5.0.0" } }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, + "System.Security.Cryptography.Primitives": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "7bDIyVFNL/xKeFHjhobUAQqSpJq9YTOpbEs6mR233Et01STBMXNAc/V+BM6dwYGc95gVh/Zf+iVXWzj3mE8DWg==" + }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", @@ -317,6 +411,14 @@ "resolved": "4.3.0", "contentHash": "BiIg+KWaSDOITze6jGQynxg64naAPtqGHBwDrLaCtixsa5bKiR8dpPOHA7ge3C0JJQizJE+sfkz1wV+BAKAYZw==" }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "System.Text.Encodings.Web": { "type": "Transitive", "resolved": "8.0.0", @@ -362,11 +464,21 @@ "resolved": "4.5.0", "contentHash": "okurQJO6NRE/apDIP23ajJ0hpiNmJ+f0BwOlB/cSqTLQlw5upkf+5+96+iG2Jw40G1fCVCyPz/FhIABUjMR+RQ==" }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==", + "dependencies": { + "Microsoft.Bcl.AsyncInterfaces": "5.0.0", + "System.Memory": "4.5.5" + } + }, "distributedlock": { "type": "Project", "dependencies": { "DistributedLock.Azure": "[1.0.2, )", "DistributedLock.FileSystem": "[1.0.3, )", + "DistributedLock.MongoDB": "[1.3.0, )", "DistributedLock.MySql": "[1.0.2, )", "DistributedLock.Oracle": "[1.0.4, )", "DistributedLock.Postgres": "[1.3.0, )", @@ -396,6 +508,13 @@ "DistributedLock.Core": "[1.0.8, )" } }, + "distributedlock.mongodb": { + "type": "Project", + "dependencies": { + "DistributedLock.Core": "[1.0.8, )", + "MongoDB.Driver": "[3.5.0, )" + } + }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -471,6 +590,23 @@ "System.Text.Encodings.Web": "6.0.0" } }, + "MongoDB.Driver": { + "type": "CentralTransitive", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "System.Net.Http": "4.3.4", + "System.Runtime.InteropServices.RuntimeInformation": "4.3.0", + "ZstdSharp.Port": "0.7.3" + } + }, "MySqlConnector": { "type": "CentralTransitive", "requested": "[2.3.5, )", @@ -530,6 +666,15 @@ } }, ".NETFramework,Version=v4.7.2/win-x86": { + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.Configuration.ConfigurationManager": { "type": "Transitive", "resolved": "6.0.1", @@ -552,6 +697,14 @@ "System.Security.Principal.Windows": "5.0.0" } }, + "System.Net.Http": { + "type": "Transitive", + "resolved": "4.3.4", + "contentHash": "aOa2d51SEbmM+H+Csw7yJOuNZoHkrP2XnAurye5HWYgGVVU54YZDvsLUYRv6h18X3sPnjNCANmN7ZhIPiqMcjA==", + "dependencies": { + "System.Security.Cryptography.X509Certificates": "4.3.0" + } + }, "System.Runtime.InteropServices.RuntimeInformation": { "type": "Transitive", "resolved": "4.3.0", @@ -565,11 +718,36 @@ "System.Security.Principal.Windows": "5.0.0" } }, + "System.Security.Cryptography.Algorithms": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "W1kd2Y8mYSCgc3ULTAZ0hOP2dSdG5YauTb1089T0/kRcN2MpSAW1izOFROrJgxSlMn3ArsgHXagigyi+ibhevg==", + "dependencies": { + "System.IO": "4.3.0", + "System.Runtime": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0", + "System.Security.Cryptography.Primitives": "4.3.0" + } + }, + "System.Security.Cryptography.Encoding": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "1DEWjZZly9ae9C79vFwqaO5kaOlI5q+3/55ohmq/7dpDyDfc8lYe7YVxJUZ5MF/NtbkRjwFRo14yM4OEo9EmDw==" + }, "System.Security.Cryptography.ProtectedData": { "type": "Transitive", "resolved": "4.7.0", "contentHash": "ehYW0m9ptxpGWvE4zgqongBVWpSDU/JCFD4K7krxkQwSz/sFQjEXCUqpvencjy6DYDbn7Ig09R8GFffu8TtneQ==" }, + "System.Security.Cryptography.X509Certificates": { + "type": "Transitive", + "resolved": "4.3.0", + "contentHash": "t2Tmu6Y2NtJ2um0RtcuhP7ZdNNxXEgUm2JeoA/0NvlMjAhKCnM1NX07TDl3244mVp3QU6LPEhT3HTtH1uF7IYw==", + "dependencies": { + "System.Security.Cryptography.Algorithms": "4.3.0", + "System.Security.Cryptography.Encoding": "4.3.0" + } + }, "System.Security.Permissions": { "type": "Transitive", "resolved": "6.0.0", @@ -583,6 +761,14 @@ "resolved": "5.0.0", "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" }, + "System.Text.Encoding.CodePages": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "NyscU59xX6Uo91qvhOs2Ccho3AR2TnZPomo1Z0K6YpyztBPM/A5VbkzOO19sy3A3i1TtEnTxA7bCe3Us+r5MWg==", + "dependencies": { + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "Microsoft.Data.SqlClient": { "type": "CentralTransitive", "requested": "[5.2.2, )", @@ -646,6 +832,14 @@ "System.IO.Hashing": "6.0.0" } }, + "DnsClient": { + "type": "Transitive", + "resolved": "1.6.1", + "contentHash": "4H/f2uYJOZ+YObZjpY9ABrKZI+JNw3uizp6oMzTXwDw6F+2qIPhpRl/1t68O/6e98+vqNiYGu+lswmwdYUy3gg==", + "dependencies": { + "Microsoft.Win32.Registry": "5.0.0" + } + }, "Microsoft.Bcl.AsyncInterfaces": { "type": "Transitive", "resolved": "1.1.1", @@ -746,8 +940,8 @@ }, "Microsoft.NETCore.Platforms": { "type": "Transitive", - "resolved": "1.1.0", - "contentHash": "kz0PEW2lhqygehI/d6XsPCQzD7ff7gUJaVGPVETX611eadGsA3A877GdSlU0LRVMCTH/+P3o2iDTak+S08V2+A==" + "resolved": "5.0.0", + "contentHash": "VyPlqzH2wavqquTcYpkIIAQ6WdenuKoFN0BdYBbCWsclXacSOHNQn66Gt4z5NBqEYW0FAPm5rlvki9ZiCij5xQ==" }, "Microsoft.NETCore.Targets": { "type": "Transitive", @@ -759,6 +953,24 @@ "resolved": "1.0.0", "contentHash": "N4KeF3cpcm1PUHym1RmakkzfkEv3GRMyofVv40uXsQhCQeglr2OHNcUk2WOG51AKpGO8ynGpo9M/kFXSzghwug==" }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, + "MongoDB.Bson": { + "type": "Transitive", + "resolved": "3.5.0", + "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "dependencies": { + "System.Memory": "4.5.5", + "System.Runtime.CompilerServices.Unsafe": "5.0.0" + } + }, "Oracle.ManagedDataAccess.Core": { "type": "Transitive", "resolved": "23.6.1", @@ -778,6 +990,21 @@ "System.IO.Pipelines": "5.0.1" } }, + "SharpCompress": { + "type": "Transitive", + "resolved": "0.30.1", + "contentHash": "XqD4TpfyYGa7QTPzaGlMVbcecKnXy4YmYLDWrU+JIj7IuRNl7DH2END+Ll7ekWIY8o3dAMWLFDE1xdhfIWD1nw==" + }, + "Snappier": { + "type": "Transitive", + "resolved": "1.0.0", + "contentHash": "rFtK2KEI9hIe8gtx3a0YDXdHOpedIf9wYCEYtBEmtlyiWVX3XlCNV03JrmmAi/Cdfn7dxK+k0sjjcLv4fpHnqA==" + }, + "System.Buffers": { + "type": "Transitive", + "resolved": "4.5.1", + "contentHash": "Rw7ijyl1qqRS0YQD/WycNst8hUUMgrMH4FCn1nNm27M4VxchZ1js3fVjQaANHO5f3sN4isvP4a+Met9Y4YomAg==" + }, "System.ClientModel": { "type": "Transitive", "resolved": "1.0.0", @@ -848,8 +1075,8 @@ }, "System.Memory": { "type": "Transitive", - "resolved": "4.5.4", - "contentHash": "1MbJTHS1lZ4bS4FmsJjnuGJOu88ZzTT2rLvrhW7Ygic+pC0NWA+3hgAen0HRdsocuQXCkUTdFn9yHJJhsijDXw==" + "resolved": "4.5.5", + "contentHash": "XIWiDvKPXaTveaB7HVganDlOCRoj03l+jrwNvcge/t8vhGYKvqV+dMv6G4SAX2NoNmN0wZfVPTAlFwZcZvVOUw==" }, "System.Memory.Data": { "type": "Transitive", @@ -887,6 +1114,15 @@ "resolved": "6.0.0", "contentHash": "/iUeP3tq1S0XdNNoMz5C9twLSrM/TH+qElHkXWaPvuNOt+99G75NrV0OS2EqHx5wMN7popYjpc8oTjC1y16DLg==" }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.5.0", @@ -905,6 +1141,11 @@ "resolved": "8.0.0", "contentHash": "+TUFINV2q2ifyXauQXRwy4CiBhqvDEDZeVJU7qfxya4aRYOKzVBpN+4acx25VcPB9ywUN6C0n8drWl110PhZEg==" }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", @@ -930,11 +1171,17 @@ "resolved": "4.5.4", "contentHash": "zteT+G8xuGu6mS+mzDzYXbzS7rd3K6Fjb9RiZlYlJPam2/hU7JCBZBVEcywNuR+oZ1ncTvc/cq0faRr3P01OVg==" }, + "ZstdSharp.Port": { + "type": "Transitive", + "resolved": "0.7.3", + "contentHash": "U9Ix4l4cl58Kzz1rJzj5hoVTjmbx1qGMwzAcbv1j/d3NzrFaESIurQyg+ow4mivCgkE3S413y+U9k4WdnEIkRA==" + }, "distributedlock": { "type": "Project", "dependencies": { "DistributedLock.Azure": "[1.0.2, )", "DistributedLock.FileSystem": "[1.0.3, )", + "DistributedLock.MongoDB": "[1.3.0, )", "DistributedLock.MySql": "[1.0.2, )", "DistributedLock.Oracle": "[1.0.4, )", "DistributedLock.Postgres": "[1.3.0, )", @@ -960,6 +1207,13 @@ "DistributedLock.Core": "[1.0.8, )" } }, + "distributedlock.mongodb": { + "type": "Project", + "dependencies": { + "DistributedLock.Core": "[1.0.8, )", + "MongoDB.Driver": "[3.5.0, )" + } + }, "distributedlock.mysql": { "type": "Project", "dependencies": { @@ -1035,6 +1289,21 @@ "System.Runtime.Caching": "8.0.0" } }, + "MongoDB.Driver": { + "type": "CentralTransitive", + "requested": "[3.5.0, )", + "resolved": "3.5.0", + "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "dependencies": { + "DnsClient": "1.6.1", + "Microsoft.Extensions.Logging.Abstractions": "2.0.0", + "MongoDB.Bson": "3.5.0", + "SharpCompress": "0.30.1", + "Snappier": "1.0.0", + "System.Buffers": "4.5.1", + "ZstdSharp.Port": "0.7.3" + } + }, "MySqlConnector": { "type": "CentralTransitive", "requested": "[2.3.5, )", @@ -1082,16 +1351,20 @@ "resolved": "5.2.0", "contentHash": "po1jhvFd+8pbfvJR/puh+fkHi0GRanAdvayh/0e47yaM6CXWZ6opUjCMFuYlAnD2LcbyvQE7fPJKvogmaUcN+w==" }, - "Microsoft.NETCore.Platforms": { - "type": "Transitive", - "resolved": "1.1.1", - "contentHash": "TMBuzAHpTenGbGgk0SMTwyEkyijY/Eae4ZGsFNYJvAr/LDn1ku3Etp3FPxChmDp5HHF3kzJuoaa08N0xjqAJfQ==" - }, "Microsoft.NETCore.Targets": { "type": "Transitive", "resolved": "1.1.3", "contentHash": "3Wrmi0kJDzClwAC+iBdUBpEKmEle8FQNsCs77fkiOIw/9oYA07bL1EZNX0kQ2OMN3xpwvl0vAtOCYY3ndDNlhQ==" }, + "Microsoft.Win32.Registry": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dDoKi0PnDz31yAyETfRntsLArTlVAVzUzCIvvEDsDsucrl33Dl8pIJG06ePTJTI3tGpeyHS9Cq7Foc/s4EeKcg==", + "dependencies": { + "System.Security.AccessControl": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "runtime.any.System.Runtime": { "type": "Transitive", "resolved": "4.3.0", @@ -1141,6 +1414,15 @@ "System.Configuration.ConfigurationManager": "8.0.0" } }, + "System.Security.AccessControl": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "dagJ1mHZO3Ani8GH0PHpPEe/oYO+rVdbQjvjJkBRNQkX4t0r1iaeGn8+/ybkSLEan3/slM0t59SVdHzuHf2jmw==", + "dependencies": { + "Microsoft.NETCore.Platforms": "5.0.0", + "System.Security.Principal.Windows": "5.0.0" + } + }, "System.Security.Cryptography.Cng": { "type": "Transitive", "resolved": "4.5.0", @@ -1154,6 +1436,11 @@ "System.Formats.Asn1": "8.0.0" } }, + "System.Security.Principal.Windows": { + "type": "Transitive", + "resolved": "5.0.0", + "contentHash": "t0MGLukB5WAVU9bO3MGzvlGnyJPgUlcwerXn1kzBRjwLKixT96XV0Uza41W49gVd8zEMFu9vQEFlv0IOrytICA==" + }, "System.Text.Encoding": { "type": "Transitive", "resolved": "4.3.0", From e26e3b7013b2737e5df932a927ad91b9300b3c17 Mon Sep 17 00:00:00 2001 From: Joes Date: Wed, 22 Oct 2025 09:19:31 +0800 Subject: [PATCH 05/13] add authors --- src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj b/src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj index 828c3eaa..7259e5a4 100644 --- a/src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj +++ b/src/DistributedLock.MongoDB/DistributedLock.MongoDB.csproj @@ -13,7 +13,7 @@ 1.3.0 1.0.0.0 - Michael Adelson + Michael Adelson, joesdu Provides a distributed lock implementation based on MongoDB Copyright © 2020 Michael Adelson MIT From b2cf5aa8425ad781951a122ad9719d82e4e49013 Mon Sep 17 00:00:00 2001 From: Joes Date: Wed, 22 Oct 2025 09:23:52 +0800 Subject: [PATCH 06/13] =?UTF-8?q?fix:=20error=20RS0025,=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E5=8F=AF=E7=A9=BA=E5=BC=95=E7=94=A8=E7=B1=BB=E5=9E=8B=E6=8C=87?= =?UTF-8?q?=E4=BB=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `PublicAPI.Unshipped.txt` 文件中,删除了 `Medallion.Threading.MongoDB.MongoDistributedLock+#nullable enable` 行。这意味着不再启用该文件中的可空引用类型功能,可能会影响到代码的可空性检查和相关的编译器警告。 --- src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt b/src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt index 1883be4e..815c9200 100644 --- a/src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt +++ b/src/DistributedLock.MongoDB/PublicAPI.Unshipped.txt @@ -1 +1 @@ -Medallion.Threading.MongoDB.MongoDistributedLock \ No newline at end of file +#nullable enable \ No newline at end of file From b0fe2d0b5561e955b6cdb1e70e4410db6b744e61 Mon Sep 17 00:00:00 2001 From: Joes Date: Wed, 22 Oct 2025 09:38:15 +0800 Subject: [PATCH 07/13] =?UTF-8?q?docs:=20=E6=B7=BB=E5=8A=A0=20DistributedL?= =?UTF-8?q?ock.MongoDB=20=E5=AE=8C=E6=95=B4=E6=96=87=E6=A1=A3=E5=B9=B6?= =?UTF-8?q?=E4=BF=AE=E6=AD=A3=20README=20=E6=8E=92=E7=89=88=E4=B8=8E?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E7=BC=A9=E8=BF=9B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 149 ++++++++++++++++---------------- docs/DistributedLock.MongoDB.md | 82 ++++++++++++++++++ 2 files changed, 158 insertions(+), 73 deletions(-) create mode 100644 docs/DistributedLock.MongoDB.md diff --git a/README.md b/README.md index 403f776b..b7e1de00 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ DistributedLock is a .NET library that provides robust and easy-to-use distributed mutexes, reader-writer locks, and semaphores based on a variety of underlying technologies. With DistributedLock, synchronizing access to a region of code across multiple applications/machines is as simple as: + ```C# await using (await myDistributedLock.AcquireAsync()) { @@ -12,12 +13,13 @@ await using (await myDistributedLock.AcquireAsync()) ## Implementations -DistributedLock contains implementations based on various technologies; you can install implementation packages individually or just install the [DistributedLock NuGet package](https://www.nuget.org/packages/DistributedLock) [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.svg?style=flat)](https://www.nuget.org/packages/DistributedLock/), a ["meta" package](https://endjin.com/blog/2020/09/streamline-dependency-management-with-nuget-meta-packages) which includes all implementations as dependencies. *Note that each package is versioned independently according to SemVer*. +DistributedLock contains implementations based on various technologies; you can install implementation packages individually or just install the [DistributedLock NuGet package](https://www.nuget.org/packages/DistributedLock) [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.svg?style=flat)](https://www.nuget.org/packages/DistributedLock/), a ["meta" package](https://endjin.com/blog/2020/09/streamline-dependency-management-with-nuget-meta-packages) which includes all implementations as dependencies. _Note that each package is versioned independently according to SemVer_. - **[DistributedLock.SqlServer](docs/DistributedLock.SqlServer.md)** [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.SqlServer.svg?style=flat)](https://www.nuget.org/packages/DistributedLock.SqlServer/) [![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-190088?logo=readme&logoColor=white)](https://dndocs.com/d/distributedlock/api/Medallion.Threading.SqlServer.html) -: uses Microsoft SQL Server + : uses Microsoft SQL Server - **[DistributedLock.Postgres](docs/DistributedLock.Postgres.md)** [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.Postgres.svg?style=flat)](https://www.nuget.org/packages/DistributedLock.Postgres/) [![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-190088?logo=readme&logoColor=white)](https://dndocs.com/d/distributedlock/api/Medallion.Threading.Postgres.html) -: uses Postgresql + : uses Postgresql +- **[DistributedLock.MongoDB](docs/DistributedLock.MongoDB.md)** [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.MongoDB.svg?style=flat)](https://www.nuget.org/packages/DistributedLock.MongoDB/) [![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-190088?logo=readme&logoColor=white)](https://dndocs.com/d/distributedlock/api/Medallion.Threading.MongoDB.html): uses MongoDB - **[DistributedLock.MySql](docs/DistributedLock.MySql.md)** [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.MySql.svg?style=flat)](https://www.nuget.org/packages/DistributedLock.MySql/) [![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-190088?logo=readme&logoColor=white)](https://dndocs.com/d/distributedlock/api/Medallion.Threading.MySql.html): uses MySQL or MariaDB - **[DistributedLock.Oracle](docs/DistributedLock.Oracle.md)** [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.Oracle.svg?style=flat)](https://www.nuget.org/packages/DistributedLock.Oracle/) [![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-190088?logo=readme&logoColor=white)](https://dndocs.com/d/distributedlock/api/Medallion.Threading.Oracle.html): uses Oracle - **[DistributedLock.Redis](docs/DistributedLock.Redis.md)** [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.Redis.svg?style=flat)](https://www.nuget.org/packages/DistributedLock.Redis/) [![Static Badge](https://img.shields.io/badge/API%20Docs-DNDocs-190088?logo=readme&logoColor=white)](https://dndocs.com/d/distributedlock/api/Medallion.Threading.Redis.html): uses Redis @@ -108,7 +110,7 @@ public class SomeService { this._synchronizationProvider = synchronizationProvider; } - + public void InitializeUserAccount(int id) { // use the provider to construct a lock @@ -117,7 +119,7 @@ public class SomeService { // do stuff } - + // ALTERNATIVELY, for common use-cases extension methods allow this to be done with a single call using (this._synchronizationProvider.AcquireLock($"UserAccount{id}")) { @@ -141,94 +143,95 @@ Contributions are welcome! If you are interested in contributing towards a new o Setup steps for working with the repository locally are documented [here](docs/Developing%20DistributedLock.md). ## Release notes + - 2.7 - - Add support for fetching a Redis-based semaphore's current available count. Thanks [@teesoftech](https://github.com/teesofttech) for implementing! ([#234](https://github.com/madelson/DistributedLock/issues/234), DistributedLock.Redis 1.1) + - Add support for fetching a Redis-based semaphore's current available count. Thanks [@teesoftech](https://github.com/teesofttech) for implementing! ([#234](https://github.com/madelson/DistributedLock/issues/234), DistributedLock.Redis 1.1) - 2.6 - - Add support for acquiring transaction-scoped Postgres locks using externally-owned transactions. Thanks [@Tzachi009](https://github.com/Tzachi009) for implementing! ([#213](https://github.com/madelson/DistributedLock/issues/213), DistributedLock.Postgres 1.3) + - Add support for acquiring transaction-scoped Postgres locks using externally-owned transactions. Thanks [@Tzachi009](https://github.com/Tzachi009) for implementing! ([#213](https://github.com/madelson/DistributedLock/issues/213), DistributedLock.Postgres 1.3) - 2.5.1 - - Increase efficiency of Azure blob locks when the blob does not exist. Thanks [@richardkooiman](https://github.com/richardkooiman) for implementing! ([#227](https://github.com/madelson/DistributedLock/pull/227), DistributedLock.Azure 1.0.2) - - Improve error handling in race condition scenarios for Azure blobs. Thanks [@MartinDembergerR9](https://github.com/MartinDembergerR9) for implementing! ([#228](https://github.com/madelson/DistributedLock/pull/228), DistributedLock.Azure 1.0.2) - - Bump Microsoft.Data.SqlClient to 5.2.2 to avoid vulnerability. Thanks [@steve85](https://github.com/steve85) for implementing! ([#229](https://github.com/madelson/DistributedLock/pull/229), DistributedLock.SqlServer 1.0.6) - - Bump Oracle.ManagedDataAccess to latest to avoid bringing in vulnerable packages (DistributedLock.Core 1.0.8, DistributedLock.Oracle 1.0.4) - - Bump Npgsql to latest patch to avoid bringing in vulnerable packages (DistributedLock.Postgres 1.2.1) - - Improve directory creation concurrency handling for `FileDistributedLock` (DistributedLock.FileSystem 1.0.3) + - Increase efficiency of Azure blob locks when the blob does not exist. Thanks [@richardkooiman](https://github.com/richardkooiman) for implementing! ([#227](https://github.com/madelson/DistributedLock/pull/227), DistributedLock.Azure 1.0.2) + - Improve error handling in race condition scenarios for Azure blobs. Thanks [@MartinDembergerR9](https://github.com/MartinDembergerR9) for implementing! ([#228](https://github.com/madelson/DistributedLock/pull/228), DistributedLock.Azure 1.0.2) + - Bump Microsoft.Data.SqlClient to 5.2.2 to avoid vulnerability. Thanks [@steve85](https://github.com/steve85) for implementing! ([#229](https://github.com/madelson/DistributedLock/pull/229), DistributedLock.SqlServer 1.0.6) + - Bump Oracle.ManagedDataAccess to latest to avoid bringing in vulnerable packages (DistributedLock.Core 1.0.8, DistributedLock.Oracle 1.0.4) + - Bump Npgsql to latest patch to avoid bringing in vulnerable packages (DistributedLock.Postgres 1.2.1) + - Improve directory creation concurrency handling for `FileDistributedLock` (DistributedLock.FileSystem 1.0.3) - 2.5 - - Add support for creating Postgres locks off `DbDataSource` which is helpful for apps using `NpgsqlMultiHostDataSource`. Thanks [davidngjy](https://github.com/davidngjy) for implementing! ([#153](https://github.com/madelson/DistributedLock/issues/153), DistributedLock.Postgres 1.2.0) - - Upgrade Npgsql to 8.0.3 to avoid vulnerability. Thanks [@Meir017](https://github.com/Meir017)/[@davidngjy](https://github.com/davidngjy) for implementing! ([#218](https://github.com/madelson/DistributedLock/issues/218), DistributedLock.Postgres 1.2.0) - - Fix Postgres race condition with connection keepalive enabled ([#216](https://github.com/madelson/DistributedLock/issues/216), DistributedLock.Core 1.0.7) - - Upgrade Microsoft.Data.SqlClient to 5.2.1 to avoid vulnerability ([#210](https://github.com/madelson/DistributedLock/issues/210), DistributedLock.SqlServer 1.0.5) + - Add support for creating Postgres locks off `DbDataSource` which is helpful for apps using `NpgsqlMultiHostDataSource`. Thanks [davidngjy](https://github.com/davidngjy) for implementing! ([#153](https://github.com/madelson/DistributedLock/issues/153), DistributedLock.Postgres 1.2.0) + - Upgrade Npgsql to 8.0.3 to avoid vulnerability. Thanks [@Meir017](https://github.com/Meir017)/[@davidngjy](https://github.com/davidngjy) for implementing! ([#218](https://github.com/madelson/DistributedLock/issues/218), DistributedLock.Postgres 1.2.0) + - Fix Postgres race condition with connection keepalive enabled ([#216](https://github.com/madelson/DistributedLock/issues/216), DistributedLock.Core 1.0.7) + - Upgrade Microsoft.Data.SqlClient to 5.2.1 to avoid vulnerability ([#210](https://github.com/madelson/DistributedLock/issues/210), DistributedLock.SqlServer 1.0.5) - 2.4 - - Add support for transaction-scoped locking in Postgres using `pg_advisory_xact_lock` which is helpful when using PgBouncer ([#168](https://github.com/madelson/DistributedLock/issues/168), DistributedLock.Postgres 1.1.0) - - Improve support for newer versions of StackExchange.Redis, especially when using the default backlog policy ([#162](https://github.com/madelson/DistributedLock/issues/162), DistributedLock.Redis 1.0.3). Thanks [@Bartleby2718](https://github.com/Bartleby2718) for helping with this! - - Drop `net461` support (`net462` remains supported). Thanks [@Bartleby2718](https://github.com/Bartleby2718) for implementing! - - Reduce occurrence of `UnobservedTaskException`s thrown by the library ([#192](https://github.com/madelson/DistributedLock/issues/192), DistributedLock.Core 1.0.6) - - Update dependencies to modern versions without known issues/vulnerabilities ([#111](https://github.com/madelson/DistributedLock/issues/111)/[#177](https://github.com/madelson/DistributedLock/issues/177)/[#184](https://github.com/madelson/DistributedLock/issues/184)/[#185](https://github.com/madelson/DistributedLock/issues/185), all packages). Thanks [@Bartleby2718](https://github.com/Bartleby2718) for helping with this! - - Improve directory creation concurrency handling for `FileDistributedLock` on Linux/.NET 8 ([#195](https://github.com/madelson/DistributedLock/issues/195), DistributedLock.FileSystem 1.0.2) - - Allow using transaction-scoped locks in SQL Server without explicitly disabling multiplexing ([#189](https://github.com/madelson/DistributedLock/issues/189), DistributedLock.SqlServer 1.0.4) - - New API documentation on [dndocs](https://dndocs.com/). Thanks [@NeuroXiq](https://github.com/NeuroXiq)! - - New documentation for contributors to get the project running locally (see [Contributing](#contributing)) + - Add support for transaction-scoped locking in Postgres using `pg_advisory_xact_lock` which is helpful when using PgBouncer ([#168](https://github.com/madelson/DistributedLock/issues/168), DistributedLock.Postgres 1.1.0) + - Improve support for newer versions of StackExchange.Redis, especially when using the default backlog policy ([#162](https://github.com/madelson/DistributedLock/issues/162), DistributedLock.Redis 1.0.3). Thanks [@Bartleby2718](https://github.com/Bartleby2718) for helping with this! + - Drop `net461` support (`net462` remains supported). Thanks [@Bartleby2718](https://github.com/Bartleby2718) for implementing! + - Reduce occurrence of `UnobservedTaskException`s thrown by the library ([#192](https://github.com/madelson/DistributedLock/issues/192), DistributedLock.Core 1.0.6) + - Update dependencies to modern versions without known issues/vulnerabilities ([#111](https://github.com/madelson/DistributedLock/issues/111)/[#177](https://github.com/madelson/DistributedLock/issues/177)/[#184](https://github.com/madelson/DistributedLock/issues/184)/[#185](https://github.com/madelson/DistributedLock/issues/185), all packages). Thanks [@Bartleby2718](https://github.com/Bartleby2718) for helping with this! + - Improve directory creation concurrency handling for `FileDistributedLock` on Linux/.NET 8 ([#195](https://github.com/madelson/DistributedLock/issues/195), DistributedLock.FileSystem 1.0.2) + - Allow using transaction-scoped locks in SQL Server without explicitly disabling multiplexing ([#189](https://github.com/madelson/DistributedLock/issues/189), DistributedLock.SqlServer 1.0.4) + - New API documentation on [dndocs](https://dndocs.com/). Thanks [@NeuroXiq](https://github.com/NeuroXiq)! + - New documentation for contributors to get the project running locally (see [Contributing](#contributing)) - 2.3.4 - - Support Npgsql 8.0's [ExecuteScalar breaking change](https://github.com/npgsql/npgsql/issues/5143) ([#174](https://github.com/madelson/DistributedLock/issues/174), DistributedLock.Postgres 1.0.5). Thanks [@Kaffeetasse](https://github.com/Kaffeetasse) for diagnosing and fixing! + - Support Npgsql 8.0's [ExecuteScalar breaking change](https://github.com/npgsql/npgsql/issues/5143) ([#174](https://github.com/madelson/DistributedLock/issues/174), DistributedLock.Postgres 1.0.5). Thanks [@Kaffeetasse](https://github.com/Kaffeetasse) for diagnosing and fixing! - 2.3.3 - - Update Microsoft.Data.SqlClient due to vulnerabilities ([#149](https://github.com/madelson/DistributedLock/issues/149), DistributedLock.SqlServer 1.0.3) - - Update versions of Oracle.ManagedDataAccess and Oracle.ManagedDataAccess.Core due to vulnerabilities (DistributedLock.Oracle 1.0.2) + - Update Microsoft.Data.SqlClient due to vulnerabilities ([#149](https://github.com/madelson/DistributedLock/issues/149), DistributedLock.SqlServer 1.0.3) + - Update versions of Oracle.ManagedDataAccess and Oracle.ManagedDataAccess.Core due to vulnerabilities (DistributedLock.Oracle 1.0.2) - 2.3.2 - - Work around underlying Postgres race condition when waiting on advisory locks with a short non-zero timeout ([#147](https://github.com/madelson/DistributedLock/issues/147), DistributedLock.Postgres 1.0.4). Thanks [@Tzachi009](https://github.com/Tzachi009) for reporting and isolating the issue! + - Work around underlying Postgres race condition when waiting on advisory locks with a short non-zero timeout ([#147](https://github.com/madelson/DistributedLock/issues/147), DistributedLock.Postgres 1.0.4). Thanks [@Tzachi009](https://github.com/Tzachi009) for reporting and isolating the issue! - 2.3.1 - - Fixed concurrency issue with `HandleLostToken` for relational database locks ([#133](https://github.com/madelson/DistributedLock/issues/133), DistributedLock.Core 1.0.5, DistributedLock.MySql 1.0.1, DistributedLock.Oracle 1.0.1, DistributedLock.Postgres 1.0.3, DistributedLock.SqlServer 1.0.2). Thanks [@OskarKlintrot](https://github.com/OskarKlintrot) for testing! - - Fixed misleading error message why trying to disable auto-extension in Redis ([#130](https://github.com/madelson/DistributedLock/issues/130), DistributedLock.Redis 1.0.2) - - Fixed concurrency issue with canceling async waits on `WaitHandle`s ([#120](https://github.com/madelson/DistributedLock/issues/120), DistributedLock.WaitHandles 1.0.1) + - Fixed concurrency issue with `HandleLostToken` for relational database locks ([#133](https://github.com/madelson/DistributedLock/issues/133), DistributedLock.Core 1.0.5, DistributedLock.MySql 1.0.1, DistributedLock.Oracle 1.0.1, DistributedLock.Postgres 1.0.3, DistributedLock.SqlServer 1.0.2). Thanks [@OskarKlintrot](https://github.com/OskarKlintrot) for testing! + - Fixed misleading error message why trying to disable auto-extension in Redis ([#130](https://github.com/madelson/DistributedLock/issues/130), DistributedLock.Redis 1.0.2) + - Fixed concurrency issue with canceling async waits on `WaitHandle`s ([#120](https://github.com/madelson/DistributedLock/issues/120), DistributedLock.WaitHandles 1.0.1) - 2.3.0 - - Added Oracle-based implementation ([#45](https://github.com/madelson/DistributedLock/issues/45), DistributedLock.Oracle 1.0.0). Thanks [@odin568](https://github.com/odin568) for testing! - - Made file-based locking more robust to transient `UnauthorizedAccessException`s ([#106](https://github.com/madelson/DistributedLock/issues/106) & [#109](https://github.com/madelson/DistributedLock/issues/109), DistributedLock.FileSystem 1.0.1) - - Work around cancellation bug in Npgsql command preparation ([#112](https://github.com/madelson/DistributedLock/issues/112), DistributedLock.Postgres 1.0.2) + - Added Oracle-based implementation ([#45](https://github.com/madelson/DistributedLock/issues/45), DistributedLock.Oracle 1.0.0). Thanks [@odin568](https://github.com/odin568) for testing! + - Made file-based locking more robust to transient `UnauthorizedAccessException`s ([#106](https://github.com/madelson/DistributedLock/issues/106) & [#109](https://github.com/madelson/DistributedLock/issues/109), DistributedLock.FileSystem 1.0.1) + - Work around cancellation bug in Npgsql command preparation ([#112](https://github.com/madelson/DistributedLock/issues/112), DistributedLock.Postgres 1.0.2) - 2.2.0 - - Added MySQL/MariaDB-based implementation ([#95](https://github.com/madelson/DistributedLock/issues/95), DistributedLock.MySql 1.0.0). Thanks [@theplacefordev](https://github.com/theplacefordev) for testing! + - Added MySQL/MariaDB-based implementation ([#95](https://github.com/madelson/DistributedLock/issues/95), DistributedLock.MySql 1.0.0). Thanks [@theplacefordev](https://github.com/theplacefordev) for testing! - 2.1.0 - - Added ZooKeeper-based implementation ([#41](https://github.com/madelson/DistributedLock/issues/41), DistributedLock.ZooKeeper 1.0.0) + - Added ZooKeeper-based implementation ([#41](https://github.com/madelson/DistributedLock/issues/41), DistributedLock.ZooKeeper 1.0.0) - 2.0.2 - - Fixed bug where `HandleLostToken` would hang when accessed on a SqlServer or Postgres lock handle that used keepalive ([#85](https://github.com/madelson/DistributedLock/issues/85), DistributedLock.Core 1.0.1) - - Fixed bug where broken database connections could result in future lock attempts failing when using SqlServer or Postgres locks with multiplexing ([#83](https://github.com/madelson/DistributedLock/issues/83), DistributedLock.Core 1.0.1) - - Updated Npgsql dependency to 5.x to take advantage of various bugfixes ([#61](https://github.com/madelson/DistributedLock/issues/61), DistributedLock.Postgres 1.0.1) + - Fixed bug where `HandleLostToken` would hang when accessed on a SqlServer or Postgres lock handle that used keepalive ([#85](https://github.com/madelson/DistributedLock/issues/85), DistributedLock.Core 1.0.1) + - Fixed bug where broken database connections could result in future lock attempts failing when using SqlServer or Postgres locks with multiplexing ([#83](https://github.com/madelson/DistributedLock/issues/83), DistributedLock.Core 1.0.1) + - Updated Npgsql dependency to 5.x to take advantage of various bugfixes ([#61](https://github.com/madelson/DistributedLock/issues/61), DistributedLock.Postgres 1.0.1) - 2.0.1 - - Fixed Redis lock behavior when using a database with `WithKeyPrefix` ([#66](https://github.com/madelson/DistributedLock/issues/66), DistributedLock.Redis 1.0.1). Thanks [@skomis-mm](https://github.com/skomis-mm) for contributing! + - Fixed Redis lock behavior when using a database with `WithKeyPrefix` ([#66](https://github.com/madelson/DistributedLock/issues/66), DistributedLock.Redis 1.0.1). Thanks [@skomis-mm](https://github.com/skomis-mm) for contributing! - 2.0.0 (see also [Migrating from 1.x to 2.x](docs/Migrating%20from%201.x%20to%202.x.md#migrating-from-1x-to-2x)) - - Revamped package structure so that DistributedLock is now an umbrella package and each implementation technology has its own package (BREAKING CHANGE) - - Added Postgresql-based locking ([#56](https://github.com/madelson/DistributedLock/issues/56), DistributedLock.Postgres 1.0.0) - - Added Redis-based locking ([#24](https://github.com/madelson/DistributedLock/issues/24), DistributedLock.Redis 1.0.0) - - Added Azure blob-based locking ([#42](https://github.com/madelson/DistributedLock/issues/42), DistributedLock.Azure 1.0.0) - - Added file-based locking ([#28](https://github.com/madelson/DistributedLock/issues/28), DistributedLock.FileSystem 1.0.0) - - Added provider classes for improved IOC integration ([#13](https://github.com/madelson/DistributedLock/issues/13)) - - Added strong naming to assemblies. Thanks [@pedropaulovc](https://github.com/pedropaulovc) for contributing! ([#47](https://github.com/madelson/DistributedLock/issues/47), BREAKING CHANGE) - - Made lock handles implement `IAsyncDisposable` in addition to `IDisposable` [#20](https://github.com/madelson/DistributedLock/issues/20), BREAKING CHANGE) - - Exposed implementation-agnostic interfaces (e. g. `IDistributedLock`) for all synchronization primitives ([#10](https://github.com/madelson/DistributedLock/issues/10)) - - Added `HandleLostToken` API for tracking if a lock's underlying connection dies ([#6](https://github.com/madelson/DistributedLock/issues/6), BREAKING CHANGE) - - Added SourceLink support ([#57](https://github.com/madelson/DistributedLock/issues/57)) - - Removed `GetSafeName` API in favor of safe naming by default (BREAKING CHANGE) - - Renamed "SystemDistributedLock" to "EventWaitHandleDistributedLock" (DistributedLock.WaitHandles 1.0.0) - - Stopped supporting net45 (BREAKING CHANGE) - - Removed `DbConnection` and `DbTransaction` constructors form `SqlDistributedLock`, leaving the constructors that take `IDbConnection`/`IDbTransaction` ([#35](https://github.com/madelson/DistributedLock/issues/35), BREAKING CHANGE) - - Changed methods returning `Task` to instead return `ValueTask`, making it so that `using (@lock.AcquireAsync()) { ... } without an `await` no longer compiles (#34, BREAKING CHANGE) - - Changed `UpgradeableLockHandle.UpgradeToWriteLock` to return `void` ([#33](https://github.com/madelson/DistributedLock/issues/33), BREAKING CHANGE) - - Switched to Microsoft.Data.SqlClient by default for all target frameworks (BREAKING CHANGE) - - Changed all locking implementations to be non-reentrant (BREAKING CHANGE) + - Revamped package structure so that DistributedLock is now an umbrella package and each implementation technology has its own package (BREAKING CHANGE) + - Added Postgresql-based locking ([#56](https://github.com/madelson/DistributedLock/issues/56), DistributedLock.Postgres 1.0.0) + - Added Redis-based locking ([#24](https://github.com/madelson/DistributedLock/issues/24), DistributedLock.Redis 1.0.0) + - Added Azure blob-based locking ([#42](https://github.com/madelson/DistributedLock/issues/42), DistributedLock.Azure 1.0.0) + - Added file-based locking ([#28](https://github.com/madelson/DistributedLock/issues/28), DistributedLock.FileSystem 1.0.0) + - Added provider classes for improved IOC integration ([#13](https://github.com/madelson/DistributedLock/issues/13)) + - Added strong naming to assemblies. Thanks [@pedropaulovc](https://github.com/pedropaulovc) for contributing! ([#47](https://github.com/madelson/DistributedLock/issues/47), BREAKING CHANGE) + - Made lock handles implement `IAsyncDisposable` in addition to `IDisposable` [#20](https://github.com/madelson/DistributedLock/issues/20), BREAKING CHANGE) + - Exposed implementation-agnostic interfaces (e. g. `IDistributedLock`) for all synchronization primitives ([#10](https://github.com/madelson/DistributedLock/issues/10)) + - Added `HandleLostToken` API for tracking if a lock's underlying connection dies ([#6](https://github.com/madelson/DistributedLock/issues/6), BREAKING CHANGE) + - Added SourceLink support ([#57](https://github.com/madelson/DistributedLock/issues/57)) + - Removed `GetSafeName` API in favor of safe naming by default (BREAKING CHANGE) + - Renamed "SystemDistributedLock" to "EventWaitHandleDistributedLock" (DistributedLock.WaitHandles 1.0.0) + - Stopped supporting net45 (BREAKING CHANGE) + - Removed `DbConnection` and `DbTransaction` constructors form `SqlDistributedLock`, leaving the constructors that take `IDbConnection`/`IDbTransaction` ([#35](https://github.com/madelson/DistributedLock/issues/35), BREAKING CHANGE) + - Changed methods returning `Task` to instead return `ValueTask`, making it so that `using (@lock.AcquireAsync()) { ... } without an `await` no longer compiles (#34, BREAKING CHANGE) + - Changed `UpgradeableLockHandle.UpgradeToWriteLock` to return `void` ([#33](https://github.com/madelson/DistributedLock/issues/33), BREAKING CHANGE) + - Switched to Microsoft.Data.SqlClient by default for all target frameworks (BREAKING CHANGE) + - Changed all locking implementations to be non-reentrant (BREAKING CHANGE) - 1.5.0 - - Added cross-platform support via Microsoft.Data.SqlClient ([#25](https://github.com/madelson/DistributedLock/issues/25)). This feature is available for .NET Standard >= 2.0. Thanks to [@alesebi91](https://github.com/alesebi91) for helping with the implementation and testing! - - Added C#8 nullable annotations ([#31](https://github.com/madelson/DistributedLock/issues/31)) - - Fixed minor bug in connection multiplexing which could lead to more lock contention ([#32](https://github.com/madelson/DistributedLock/issues/32)) + - Added cross-platform support via Microsoft.Data.SqlClient ([#25](https://github.com/madelson/DistributedLock/issues/25)). This feature is available for .NET Standard >= 2.0. Thanks to [@alesebi91](https://github.com/alesebi91) for helping with the implementation and testing! + - Added C#8 nullable annotations ([#31](https://github.com/madelson/DistributedLock/issues/31)) + - Fixed minor bug in connection multiplexing which could lead to more lock contention ([#32](https://github.com/madelson/DistributedLock/issues/32)) - 1.4.0 - - Added a SQL-based distributed semaphore ([#7](https://github.com/madelson/DistributedLock/issues/7)) - - Fix bug where SqlDistributedLockConnectionStrategy.Azure would leak connections, relying on GC to reclaim them ([#14](https://github.com/madelson/DistributedLock/issues/14)). Thanks [zavalita1](https://github.com/zavalita1) for investigating this issue! - - Throw a specific exception type (`DeadlockException`) rather than the generic `InvalidOperationException` when a deadlock is detected ([#11](https://github.com/madelson/DistributedLock/issues/11)) + - Added a SQL-based distributed semaphore ([#7](https://github.com/madelson/DistributedLock/issues/7)) + - Fix bug where SqlDistributedLockConnectionStrategy.Azure would leak connections, relying on GC to reclaim them ([#14](https://github.com/madelson/DistributedLock/issues/14)). Thanks [zavalita1](https://github.com/zavalita1) for investigating this issue! + - Throw a specific exception type (`DeadlockException`) rather than the generic `InvalidOperationException` when a deadlock is detected ([#11](https://github.com/madelson/DistributedLock/issues/11)) - 1.3.1 Minor fix to avoid "leaking" isolation level changes in transaction-based locks ([#8](https://github.com/madelson/DistributedLock/issues/8)). Also switched to the VS2017 project file format - 1.3.0 Added an Azure connection strategy to keep lock connections from becoming idle and being reclaimed by Azure's connection governor ([#5](https://github.com/madelson/DistributedLock/issues/5)) - 1.2.0 - - Added a SQL-based distributed reader-writer lock - - .NET Core support via .NET Standard - - Changed the default locking scope for SQL distributed lock to be a connection rather than a transaction, avoiding cases where long-running transactions can block backups - - Allowed for customization of the SQL distributed lock connection strategy when connecting via a connection string - - Added a new connection strategy which allows for multiplexing multiple held locks onto one connection - - Added IDbConnection/IDbTransaction constructors ([#3](https://github.com/madelson/DistributedLock/issues/3)) + - Added a SQL-based distributed reader-writer lock + - .NET Core support via .NET Standard + - Changed the default locking scope for SQL distributed lock to be a connection rather than a transaction, avoiding cases where long-running transactions can block backups + - Allowed for customization of the SQL distributed lock connection strategy when connecting via a connection string + - Added a new connection strategy which allows for multiplexing multiple held locks onto one connection + - Added IDbConnection/IDbTransaction constructors ([#3](https://github.com/madelson/DistributedLock/issues/3)) - 1.1.0 Added support for SQL distributed locks scoped to existing connections/transactions - 1.0.1 Minor fix when using infinite timeouts - 1.0.0 Initial release diff --git a/docs/DistributedLock.MongoDB.md b/docs/DistributedLock.MongoDB.md new file mode 100644 index 00000000..231bd594 --- /dev/null +++ b/docs/DistributedLock.MongoDB.md @@ -0,0 +1,82 @@ +# DistributedLock.MongoDB + +[Download the NuGet package](https://www.nuget.org/packages/DistributedLock.MongoDB) [![NuGet Status](http://img.shields.io/nuget/v/DistributedLock.MongoDB.svg?style=flat)](https://www.nuget.org/packages/DistributedLock.MongoDB/) + +The DistributedLock.MongoDB package offers distributed locks based on [MongoDB](https://www.mongodb.com/). For example: + +```C# +var client = new MongoClient("mongodb://localhost:27017"); +var database = client.GetDatabase("myDatabase"); +var @lock = new MongoDistributedLock("MyLockName", database); +await using (await @lock.AcquireAsync()) +{ + // I have the lock +} +``` + +## APIs + +- The `MongoDistributedLock` class implements the `IDistributedLock` interface. +- The `MongoDistributedSynchronizationProvider` class implements the `IDistributedLockProvider` interface. + +## Implementation notes + +MongoDB-based locks use MongoDB's document upsert and update operations to implement distributed locking. The implementation works as follows: + +1. **Acquisition**: Attempts to insert or update a document with the lock key and a unique lock ID. +2. **Extension**: Automatically extends the lock expiry while held to prevent timeout. +3. **Release**: Deletes the lock document when disposed. +4. **Expiry**: Locks automatically expire if not extended, allowing recovery from crashed processes. + +MongoDB locks can be constructed with an `IMongoDatabase` and an optional collection name. If no collection name is specified, locks will be stored in a collection named `"DistributedLocks"`. The collection will automatically have an index created on the `expiresAt` field for efficient queries. + +When using the provider pattern, you can create multiple locks with different names from the same provider: + +```C# +var client = new MongoClient(connectionString); +var database = client.GetDatabase("myDatabase"); +var provider = new MongoDistributedSynchronizationProvider(database); + +var lock1 = provider.CreateLock("lock1"); +var lock2 = provider.CreateLock("lock2"); + +await using (await lock1.AcquireAsync()) +{ + // Do work with lock1 +} +``` + +**NOTE**: Lock extension happens automatically in the background while the lock is held. If lock extension fails (for example, due to network issues), the `HandleLostToken` will be signaled to notify you that the lock may have been lost. + +## Options + +In addition to specifying the name and database, several tuning options are available: + +- `Expiry` determines how long the lock will be initially claimed for. Because of automatic extension, locks can be held for longer than this value. Defaults to 30 seconds. +- `ExtensionCadence` determines how frequently the hold on the lock will be renewed to the full `Expiry`. Defaults to 1/3 of `Expiry` (approximately 10 seconds when using the default expiry). +- `BusyWaitSleepTime` specifies a range of times that the implementation will sleep between attempts to acquire a lock that is currently held by someone else. A random time in the range will be chosen for each sleep. If you expect contention, lowering these values may increase responsiveness (how quickly a lock detects that it can now be taken) but will increase the number of calls made to MongoDB. Raising the values will have the reverse effects. Defaults to a range of 10ms to 800ms. + +Example of using options: + +```C# +var @lock = new MongoDistributedLock( + "MyLockName", + database, + options => options + .Expiry(TimeSpan.FromSeconds(30)) + .ExtensionCadence(TimeSpan.FromSeconds(10)) + .BusyWaitSleepTime( + min: TimeSpan.FromMilliseconds(10), + max: TimeSpan.FromMilliseconds(800)) +); +``` + +You can also specify a custom collection name: + +```C# +var @lock = new MongoDistributedLock("MyLockName", database, "MyCustomLocks"); +``` + +## Stale lock cleanup + +Stale locks from crashed processes will automatically expire based on the `Expiry` setting. MongoDB's built-in TTL index support ensures that expired lock documents are cleaned up automatically by the database. This means that if a process crashes while holding a lock, the lock will become available again after the expiry time has elapsed. From 9b4ddf4392bd7787470bad97308ef934e63920b6 Mon Sep 17 00:00:00 2001 From: Joes Date: Wed, 22 Oct 2025 09:50:05 +0800 Subject: [PATCH 08/13] fix: ci --- .../Tests/MongoDB/MongoDistributedLockTest.cs | 2 ++ .../MongoDistributedSynchronizationOptionsBuilderTest.cs | 1 - .../MongoDB/MongoDistributedSynchronizationProviderTest.cs | 1 - 3 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs index a8f8ef1e..9c753478 100644 --- a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs +++ b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedLockTest.cs @@ -97,6 +97,7 @@ public async Task TestLockContentionAsync() } [Test] + [Category("CI")] public void TestName() { const string Name = "\0🐉汉字\b\r\n\\"; @@ -107,6 +108,7 @@ public void TestName() } [Test] + [Category("CI")] public void TestValidatesConstructorParameters() { var database = new Mock(MockBehavior.Strict).Object; diff --git a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs index c6d4a430..1e2ae9c0 100644 --- a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs +++ b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationOptionsBuilderTest.cs @@ -3,7 +3,6 @@ namespace Medallion.Threading.Tests.MongoDB; -[Category("CI")] public class MongoDistributedSynchronizationOptionsBuilderTest { [Test] diff --git a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs index 54879238..1aa97e1e 100644 --- a/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs +++ b/src/DistributedLock.Tests/Tests/MongoDB/MongoDistributedSynchronizationProviderTest.cs @@ -4,7 +4,6 @@ namespace Medallion.Threading.Tests.MongoDB; -[Category("CI")] public class MongoDistributedSynchronizationProviderTest { [Test] From e26097973c930b8f1e5ee7691c6fe4a8cbd50020 Mon Sep 17 00:00:00 2001 From: Joes Date: Thu, 23 Oct 2025 09:43:13 +0800 Subject: [PATCH 09/13] =?UTF-8?q?tests:=20=E5=9C=A8=20CombinatorialTests.c?= =?UTF-8?q?s=20=E4=B8=AD=E6=B7=BB=E5=8A=A0=20MongoDB=20=E7=BB=84=E5=90=88?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E7=B1=BB=20Core=5FMongo=5FMongoDbSynchroniza?= =?UTF-8?q?tionStrategyTest?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/DistributedLock.Tests/Tests/CombinatorialTests.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/DistributedLock.Tests/Tests/CombinatorialTests.cs b/src/DistributedLock.Tests/Tests/CombinatorialTests.cs index cfa4c9ab..90dd2ca3 100644 --- a/src/DistributedLock.Tests/Tests/CombinatorialTests.cs +++ b/src/DistributedLock.Tests/Tests/CombinatorialTests.cs @@ -11,6 +11,11 @@ namespace Medallion.Threading.Tests.FileSystem [Category("CI")] public class Core_File_FileSynchronizationStrategyTest : DistributedLockCoreTestCases { } } +namespace Medallion.Threading.Tests.MongoDB +{ + public class Core_Mongo_MongoDbSynchronizationStrategyTest : DistributedLockCoreTestCases { } +} + namespace Medallion.Threading.Tests.MySql { public class ConnectionStringStrategy_MySql_ConnectionMultiplexingSynchronizationStrategy_MariaDbDb_MariaDbDb_ConnectionMultiplexingSynchronizationStrategy_MariaDbDb_MariaDbDbTest : ConnectionStringStrategyTestCases, TestingMariaDbDb>, TestingConnectionMultiplexingSynchronizationStrategy, TestingMariaDbDb> { } From 3a28ef0428579e4dc080ae8b281ac9adddc329af Mon Sep 17 00:00:00 2001 From: Joes Date: Thu, 23 Oct 2025 10:50:30 +0800 Subject: [PATCH 10/13] fix: ci error? MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 `MongoDistributedLock.IDistributedLock.cs` 文件中,`MongoDistributedLock` 类实现了 `IDistributedLock` 接口的 `TryAcquire` 方法,新增了自动生成的方法以支持分布式锁的获取。此外,更新了文档注释,修正了异步获取锁的代码示例格式。 --- .../MongoDistributedLock.IDistributedLock.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs b/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs index 89936ac7..b2fd5714 100644 --- a/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs +++ b/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs @@ -4,6 +4,8 @@ namespace Medallion.Threading.MongoDB; public partial class MongoDistributedLock { + // AUTO-GENERATED + IDistributedSynchronizationHandle? IDistributedLock.TryAcquire(TimeSpan timeout, CancellationToken cancellationToken) { return TryAcquire(timeout, cancellationToken); @@ -84,7 +86,7 @@ public MongoDistributedLockHandle Acquire(TimeSpan? timeout = null, Cancellation /// await using (await myLock.AcquireAsync(...)) /// { /// /* we have the lock! */ - /// } + /// } /// // dispose releases the lock /// /// From cd141257708fc80244263996f0ef27b83f2ce396 Mon Sep 17 00:00:00 2001 From: Joes Date: Thu, 23 Oct 2025 10:54:40 +0800 Subject: [PATCH 11/13] style: fix code format --- .../MongoDistributedLock.IDistributedLock.cs | 71 +++++++------------ 1 file changed, 26 insertions(+), 45 deletions(-) diff --git a/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs b/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs index b2fd5714..33ffa43a 100644 --- a/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs +++ b/src/DistributedLock.MongoDB/MongoDistributedLock.IDistributedLock.cs @@ -6,28 +6,17 @@ public partial class MongoDistributedLock { // AUTO-GENERATED - IDistributedSynchronizationHandle? IDistributedLock.TryAcquire(TimeSpan timeout, CancellationToken cancellationToken) - { - return TryAcquire(timeout, cancellationToken); - } - - IDistributedSynchronizationHandle IDistributedLock.Acquire(TimeSpan? timeout, CancellationToken cancellationToken) - { - return Acquire(timeout, cancellationToken); - } - - ValueTask IDistributedLock.TryAcquireAsync(TimeSpan timeout, CancellationToken cancellationToken) - { - return TryAcquireAsync(timeout, cancellationToken).Convert(To.ValueTask); - } - - ValueTask IDistributedLock.AcquireAsync(TimeSpan? timeout, CancellationToken cancellationToken) - { - return AcquireAsync(timeout, cancellationToken).Convert(To.ValueTask); - } + IDistributedSynchronizationHandle? IDistributedLock.TryAcquire(TimeSpan timeout, CancellationToken cancellationToken) => + this.TryAcquire(timeout, cancellationToken); + IDistributedSynchronizationHandle IDistributedLock.Acquire(TimeSpan? timeout, CancellationToken cancellationToken) => + this.Acquire(timeout, cancellationToken); + ValueTask IDistributedLock.TryAcquireAsync(TimeSpan timeout, CancellationToken cancellationToken) => + this.TryAcquireAsync(timeout, cancellationToken).Convert(To.ValueTask); + ValueTask IDistributedLock.AcquireAsync(TimeSpan? timeout, CancellationToken cancellationToken) => + this.AcquireAsync(timeout, cancellationToken).Convert(To.ValueTask); /// - /// Attempts to acquire the lock synchronously. Usage: + /// Attempts to acquire the lock synchronously. Usage: /// /// using (var handle = myLock.TryAcquire(...)) /// { @@ -38,14 +27,12 @@ ValueTask IDistributedLock.AcquireAsync(TimeS /// /// How long to wait before giving up on the acquisition attempt. Defaults to 0 /// Specifies a token by which the wait can be canceled - /// A which can be used to release the lock or null on failure - public MongoDistributedLockHandle? TryAcquire(TimeSpan timeout = default, CancellationToken cancellationToken = default) - { - return DistributedLockHelpers.TryAcquire(this, timeout, cancellationToken); - } + /// A which can be used to release the lock or null on failure + public MongoDistributedLockHandle? TryAcquire(TimeSpan timeout = default, CancellationToken cancellationToken = default) => + DistributedLockHelpers.TryAcquire(this, timeout, cancellationToken); /// - /// Acquires the lock synchronously, failing with if the attempt times out. Usage: + /// Acquires the lock synchronously, failing with if the attempt times out. Usage: /// /// using (myLock.Acquire(...)) /// { @@ -54,16 +41,14 @@ ValueTask IDistributedLock.AcquireAsync(TimeS /// // dispose releases the lock /// /// - /// How long to wait before giving up on the acquisition attempt. Defaults to + /// How long to wait before giving up on the acquisition attempt. Defaults to /// Specifies a token by which the wait can be canceled - /// A which can be used to release the lock - public MongoDistributedLockHandle Acquire(TimeSpan? timeout = null, CancellationToken cancellationToken = default) - { - return DistributedLockHelpers.Acquire(this, timeout, cancellationToken); - } + /// A which can be used to release the lock + public MongoDistributedLockHandle Acquire(TimeSpan? timeout = null, CancellationToken cancellationToken = default) => + DistributedLockHelpers.Acquire(this, timeout, cancellationToken); /// - /// Attempts to acquire the lock asynchronously. Usage: + /// Attempts to acquire the lock asynchronously. Usage: /// /// await using (var handle = await myLock.TryAcquireAsync(...)) /// { @@ -74,14 +59,12 @@ public MongoDistributedLockHandle Acquire(TimeSpan? timeout = null, Cancellation /// /// How long to wait before giving up on the acquisition attempt. Defaults to 0 /// Specifies a token by which the wait can be canceled - /// A which can be used to release the lock or null on failure - public ValueTask TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) - { - return this.As>().InternalTryAcquireAsync(timeout, cancellationToken); - } + /// A which can be used to release the lock or null on failure + public ValueTask TryAcquireAsync(TimeSpan timeout = default, CancellationToken cancellationToken = default) => + this.As>().InternalTryAcquireAsync(timeout, cancellationToken); /// - /// Acquires the lock asynchronously, failing with if the attempt times out. Usage: + /// Acquires the lock asynchronously, failing with if the attempt times out. Usage: /// /// await using (await myLock.AcquireAsync(...)) /// { @@ -90,11 +73,9 @@ public MongoDistributedLockHandle Acquire(TimeSpan? timeout = null, Cancellation /// // dispose releases the lock /// /// - /// How long to wait before giving up on the acquisition attempt. Defaults to + /// How long to wait before giving up on the acquisition attempt. Defaults to /// Specifies a token by which the wait can be canceled - /// A which can be used to release the lock - public ValueTask AcquireAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) - { - return DistributedLockHelpers.AcquireAsync(this, timeout, cancellationToken); - } + /// A which can be used to release the lock + public ValueTask AcquireAsync(TimeSpan? timeout = null, CancellationToken cancellationToken = default) => + DistributedLockHelpers.AcquireAsync(this, timeout, cancellationToken); } \ No newline at end of file From ba277b3ba751e7b65527f600e2c19d7a57070bff Mon Sep 17 00:00:00 2001 From: Joes Date: Wed, 17 Dec 2025 18:05:16 +0800 Subject: [PATCH 12/13] chore: Performance Optimization --- src/Directory.Packages.props | 2 +- src/DistributedLock.Core/packages.lock.json | 6 +- .../MongoDistributedLock.cs | 154 ++++++++++++------ .../MongoDistributedLockHandle.cs | 91 +++++++---- .../packages.lock.json | 42 ++--- .../packages.lock.json | 6 +- src/DistributedLock.Tests/packages.lock.json | 16 +- src/DistributedLock/packages.lock.json | 28 ++-- src/DistributedLockTaker/packages.lock.json | 38 ++--- 9 files changed, 235 insertions(+), 148 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 2894c36a..c8bdc85e 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -7,7 +7,7 @@ - + diff --git a/src/DistributedLock.Core/packages.lock.json b/src/DistributedLock.Core/packages.lock.json index 0e0884af..86c0653a 100644 --- a/src/DistributedLock.Core/packages.lock.json +++ b/src/DistributedLock.Core/packages.lock.json @@ -148,9 +148,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.21, )", - "resolved": "8.0.21", - "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" + "requested": "[8.0.22, )", + "resolved": "8.0.22", + "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/src/DistributedLock.MongoDB/MongoDistributedLock.cs b/src/DistributedLock.MongoDB/MongoDistributedLock.cs index 1fb4ec8d..ab2e2554 100644 --- a/src/DistributedLock.MongoDB/MongoDistributedLock.cs +++ b/src/DistributedLock.MongoDB/MongoDistributedLock.cs @@ -1,5 +1,7 @@ using Medallion.Threading.Internal; +using MongoDB.Bson; using MongoDB.Driver; +using System.Collections.Concurrent; namespace Medallion.Threading.MongoDB; @@ -8,6 +10,13 @@ namespace Medallion.Threading.MongoDB; /// public sealed partial class MongoDistributedLock : IInternalDistributedLock { +#if !NETSTANDARD2_1_OR_GREATER && !NET8_0_OR_GREATER + private static readonly DateTime EpochUtc = new(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); +#endif + + // We want to ensure indexes at most once per process per (db, collection) + private static readonly ConcurrentDictionary> IndexInitializationTasks = new(StringComparer.Ordinal); + private readonly string _collectionName; private readonly IMongoDatabase _database; private readonly MongoDistributedLockOptions _options; @@ -20,7 +29,7 @@ public sealed partial class MongoDistributedLock : IInternalDistributedLock /// Implements /// - public string Name => Key; + public string Name => this.Key; /// /// Constructs a lock named using the provided and . @@ -36,10 +45,10 @@ public MongoDistributedLock(string key, IMongoDatabase database, Action? options = null) { if (string.IsNullOrEmpty(key)) { throw new ArgumentNullException(nameof(key)); } - _database = database ?? throw new ArgumentNullException(nameof(database)); - _collectionName = collectionName ?? throw new ArgumentNullException(nameof(collectionName)); - Key = key; - _options = MongoDistributedSynchronizationOptionsBuilder.GetOptions(options); + this._database = database ?? throw new ArgumentNullException(nameof(database)); + this._collectionName = collectionName ?? throw new ArgumentNullException(nameof(collectionName)); + this.Key = key; + this._options = MongoDistributedSynchronizationOptionsBuilder.GetOptions(options); } ValueTask IInternalDistributedLock.InternalTryAcquireAsync(TimeoutValue timeout, CancellationToken cancellationToken) @@ -47,70 +56,117 @@ public MongoDistributedLock(string key, IMongoDatabase database, string collecti return BusyWaitHelper.WaitAsync(this, (@this, ct) => @this.TryAcquireAsync(ct), timeout, - _options.MinBusyWaitSleepTime, - _options.MaxBusyWaitSleepTime, + this._options.MinBusyWaitSleepTime, + this._options.MaxBusyWaitSleepTime, cancellationToken); } private async ValueTask TryAcquireAsync(CancellationToken cancellationToken) { - var collection = _database.GetCollection(_collectionName); - - // Ensure index exists for efficient queries - await EnsureIndexAsync(collection, cancellationToken).ConfigureAwait(false); - var lockId = Guid.NewGuid().ToString(); - var now = DateTime.UtcNow; - var expiresAt = now.Add(_options.Expiry.TimeSpan); - - // Filter: lock with this key doesn't exist OR has expired - var filter = Builders.Filter.Or(Builders.Filter.Eq(d => d.Id, Key) & Builders.Filter.Lt(d => d.ExpiresAt, now), - Builders.Filter.Eq(d => d.Id, Key) & Builders.Filter.Exists(d => d.ExpiresAt, false)); - var update = Builders.Update - .Set(d => d.Id, Key) - .Set(d => d.LockId, lockId) - .Set(d => d.ExpiresAt, expiresAt) - .Set(d => d.AcquiredAt, now); - var options = new FindOneAndUpdateOptions { IsUpsert = true, ReturnDocument = ReturnDocument.After }; - try - { - var result = await collection.FindOneAndUpdateAsync(filter, update, options, cancellationToken).ConfigureAwait(false); + var collection = this._database.GetCollection(this._collectionName); - // Verify we actually got the lock - if (result != null && result.LockId == lockId) - { - return new(_database, - _collectionName, - Key, - lockId, - _options.Expiry, - _options.ExtensionCadence); - } - } - catch (MongoWriteException ex) when (ex.WriteError?.Category == ServerErrorCategory.DuplicateKey) + // Ensure indexes exist (TTL cleanup); do this at most once per process per (db, collection) + await EnsureIndexesCreatedAsync(collection).ConfigureAwait(false); + + // Use a unique token per acquisition attempt (like Redis' value token) + var lockId = Guid.NewGuid().ToString("N"); + var expiryMs = this._options.Expiry.InMilliseconds; + + // We avoid exception-driven contention (DuplicateKey) by using a single upsert on {_id == Key} + // and an update pipeline that only overwrites fields when the existing lock is expired. + // This is conceptually similar to Redis: SET key value NX PX . + var filter = Builders.Filter.Eq(d => d.Id, this.Key); + var update = CreateAcquireUpdate(lockId, expiryMs); + var options = new FindOneAndUpdateOptions { - // Lock is already held by someone else - return null; - } - catch (MongoCommandException) + IsUpsert = true, + ReturnDocument = ReturnDocument.After + }; + + var result = await collection.FindOneAndUpdateAsync(filter, update, options, cancellationToken).ConfigureAwait(false); + + // Verify we actually got the lock + if (result != null && result.LockId == lockId) { - // Lock is already held - return null; + return new(collection, + this.Key, + lockId, + this._options.Expiry, + this._options.ExtensionCadence); } + return null; } - private static async Task EnsureIndexAsync(IMongoCollection collection, CancellationToken cancellationToken) + private static UpdateDefinition CreateAcquireUpdate(string lockId, int expiryMs) + { + // expired := ifNull(expiresAt, epoch) <= $$NOW + var expiredOrMissing = new BsonDocument( + "$lte", + new BsonArray + { + new BsonDocument("$ifNull", new BsonArray { "$expiresAt", new BsonDateTime( +#if NETSTANDARD2_1_OR_GREATER || NET8_0_OR_GREATER + DateTime.UnixEpoch +#else + EpochUtc +#endif + ) }), + "$$NOW" + } + ); + + var newExpiresAt = new BsonDocument( + "$dateAdd", + new BsonDocument + { + { "startDate", "$$NOW" }, + { "unit", "millisecond" }, + { "amount", expiryMs } + } + ); + + var setStage = new BsonDocument( + "$set", + new BsonDocument + { + // Only overwrite lock fields when the previous lock is expired/missing + { "lockId", new BsonDocument("$cond", new BsonArray { expiredOrMissing, lockId, "$lockId" }) }, + { "expiresAt", new BsonDocument("$cond", new BsonArray { expiredOrMissing, newExpiresAt, "$expiresAt" }) }, + { "acquiredAt", new BsonDocument("$cond", new BsonArray { expiredOrMissing, "$$NOW", "$acquiredAt" }) } + } + ); + + return new PipelineUpdateDefinition(new[] { setStage }); + } + + private static Task EnsureIndexesCreatedAsync(IMongoCollection collection) + { + // Best-effort TTL index to clean up expired rows over time. + // Note: TTL monitors run on a schedule; correctness MUST NOT depend on this. + var databaseName = collection.Database.DatabaseNamespace.DatabaseName; + var key = databaseName + "/" + collection.CollectionNamespace.CollectionName; + + var lazy = IndexInitializationTasks.GetOrAdd(key, _ => new Lazy(() => CreateIndexesAsync(collection))); + return lazy.Value; + } + + private static async Task CreateIndexesAsync(IMongoCollection collection) { try { var indexKeys = Builders.IndexKeys.Ascending(d => d.ExpiresAt); - var indexOptions = new CreateIndexOptions { Background = true }; + var indexOptions = new CreateIndexOptions + { + // TTL cleanup: remove documents once expiresAt < now + ExpireAfter = TimeSpan.Zero, + }; var indexModel = new CreateIndexModel(indexKeys, indexOptions); - await collection.Indexes.CreateOneAsync(indexModel, cancellationToken: cancellationToken).ConfigureAwait(false); + await collection.Indexes.CreateOneAsync(indexModel, cancellationToken: CancellationToken.None).ConfigureAwait(false); } catch { - // Index might already exist, ignore errors + // Index may already exist, or server may reject options (e.g., conflicts). Ignore. } } } \ No newline at end of file diff --git a/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs b/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs index 352c698c..ee8f96d8 100644 --- a/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs +++ b/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs @@ -1,4 +1,5 @@ using Medallion.Threading.Internal; +using MongoDB.Bson; using MongoDB.Driver; namespace Medallion.Threading.MongoDB; @@ -8,9 +9,8 @@ namespace Medallion.Threading.MongoDB; /// public sealed class MongoDistributedLockHandle : IDistributedSynchronizationHandle { - private readonly string _collectionName; private readonly CancellationTokenSource _cts; - private readonly IMongoDatabase _database; + private readonly IMongoCollection _collection; private readonly Task _extensionTask; private readonly string _key; private readonly string _lockId; @@ -19,24 +19,22 @@ public sealed class MongoDistributedLockHandle : IDistributedSynchronizationHand /// /// Implements /// - public CancellationToken HandleLostToken => _cts.Token; + public CancellationToken HandleLostToken => this._cts.Token; internal MongoDistributedLockHandle( - IMongoDatabase database, - string collectionName, + IMongoCollection collection, string key, string lockId, TimeoutValue expiry, TimeoutValue extensionCadence) { - _database = database; - _collectionName = collectionName; - _key = key; - _lockId = lockId; - _cts = new(); + this._collection = collection; + this._key = key; + this._lockId = lockId; + this._cts = new(); // Start background task to extend the lock - _extensionTask = ExtendLockAsync(expiry, extensionCadence, _cts.Token); + this._extensionTask = this.ExtendLockAsync(expiry, extensionCadence, this._cts.Token); } /// @@ -44,14 +42,16 @@ internal MongoDistributedLockHandle( /// public void Dispose() { - if (Interlocked.Exchange(ref _disposed, 1) is not 0) + if (Interlocked.Exchange(ref this._disposed, 1) is not 0) { return; } - _cts.Cancel(); + + this._cts.Cancel(); try { - _extensionTask.Wait(HandleLostToken); + // Do not use HandleLostToken here: it is backed by _cts and has been canceled above. + this._extensionTask.GetAwaiter().GetResult(); } catch { @@ -59,8 +59,15 @@ public void Dispose() } finally { - _cts.Dispose(); - ReleaseLockAsync(CancellationToken.None).AsTask().Wait(HandleLostToken); + this._cts.Dispose(); + try + { + this.ReleaseLockAsync(CancellationToken.None).AsTask().GetAwaiter().GetResult(); + } + catch + { + // Ignore errors during release + } } } @@ -69,12 +76,16 @@ public void Dispose() /// public async ValueTask DisposeAsync() { - if (Interlocked.Exchange(ref _disposed, 1) is 0) + if (Interlocked.Exchange(ref this._disposed, 1) is 0) { - _cts.Cancel(); +#if NET8_0_OR_GREATER + await this._cts.CancelAsync(); +#else + this._cts.Cancel(); +#endif try { - await _extensionTask.ConfigureAwait(false); + await this._extensionTask.ConfigureAwait(false); } catch { @@ -82,8 +93,8 @@ public async ValueTask DisposeAsync() } finally { - _cts.Dispose(); - await ReleaseLockAsync(CancellationToken.None).ConfigureAwait(false); + this._cts.Dispose(); + await this.ReleaseLockAsync(CancellationToken.None).ConfigureAwait(false); } } } @@ -95,17 +106,34 @@ private async Task ExtendLockAsync(TimeoutValue expiry, TimeoutValue extensionCa while (!cancellationToken.IsCancellationRequested) { await Task.Delay(extensionCadence.TimeSpan, cancellationToken).ConfigureAwait(false); - var collection = _database.GetCollection(_collectionName); - var filter = Builders.Filter.Eq(d => d.Id, _key) & Builders.Filter.Eq(d => d.LockId, _lockId); - var update = Builders.Update.Set(d => d.ExpiresAt, DateTime.UtcNow.Add(expiry.TimeSpan)); - var result = await collection.UpdateOneAsync(filter, update, cancellationToken: cancellationToken).ConfigureAwait(false); + var filter = Builders.Filter.Eq(d => d.Id, this._key) & Builders.Filter.Eq(d => d.LockId, this._lockId); + + // Use server time ($$NOW) for expiry to avoid client clock skew. + var newExpiresAt = new BsonDocument( + "$dateAdd", + new BsonDocument + { + { "startDate", "$$NOW" }, + { "unit", "millisecond" }, + { "amount", expiry.InMilliseconds } + } + ); + var update = new PipelineUpdateDefinition( + new[] { new BsonDocument("$set", new BsonDocument("expiresAt", newExpiresAt)) } + ); + + var result = await this._collection.UpdateOneAsync(filter, update, cancellationToken: cancellationToken).ConfigureAwait(false); // If we failed to extend, the lock was lost if (result.MatchedCount is not 0) { continue; } - _cts.Cancel(); +#if NET8_0_OR_GREATER + await this._cts.CancelAsync(); +#else + this._cts.Cancel(); +#endif break; } } @@ -116,7 +144,11 @@ private async Task ExtendLockAsync(TimeoutValue expiry, TimeoutValue extensionCa catch { // Lock extension failed, signal that the lock is lost - _cts.Cancel(); +#if NET8_0_OR_GREATER + await this._cts.CancelAsync(); +#else + this._cts.Cancel(); +#endif } } @@ -124,9 +156,8 @@ private async ValueTask ReleaseLockAsync(CancellationToken cancellationToken) { try { - var collection = _database.GetCollection(_collectionName); - var filter = Builders.Filter.Eq(d => d.Id, _key) & Builders.Filter.Eq(d => d.LockId, _lockId); - await collection.DeleteOneAsync(filter, cancellationToken).ConfigureAwait(false); + var filter = Builders.Filter.Eq(d => d.Id, this._key) & Builders.Filter.Eq(d => d.LockId, this._lockId); + await this._collection.DeleteOneAsync(filter, cancellationToken).ConfigureAwait(false); } catch { diff --git a/src/DistributedLock.MongoDB/packages.lock.json b/src/DistributedLock.MongoDB/packages.lock.json index 11abd24a..bfc80002 100644 --- a/src/DistributedLock.MongoDB/packages.lock.json +++ b/src/DistributedLock.MongoDB/packages.lock.json @@ -20,13 +20,13 @@ }, "MongoDB.Driver": { "type": "Direct", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", @@ -78,8 +78,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -252,13 +252,13 @@ }, "MongoDB.Driver": { "type": "Direct", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", @@ -301,8 +301,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -391,9 +391,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.21, )", - "resolved": "8.0.21", - "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" + "requested": "[8.0.22, )", + "resolved": "8.0.22", + "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", @@ -407,13 +407,13 @@ }, "MongoDB.Driver": { "type": "Direct", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", @@ -459,8 +459,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" diff --git a/src/DistributedLock.Postgres/packages.lock.json b/src/DistributedLock.Postgres/packages.lock.json index 041eb72b..80b0048d 100644 --- a/src/DistributedLock.Postgres/packages.lock.json +++ b/src/DistributedLock.Postgres/packages.lock.json @@ -518,9 +518,9 @@ }, "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.21, )", - "resolved": "8.0.21", - "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" + "requested": "[8.0.22, )", + "resolved": "8.0.22", + "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" }, "Microsoft.SourceLink.GitHub": { "type": "Direct", diff --git a/src/DistributedLock.Tests/packages.lock.json b/src/DistributedLock.Tests/packages.lock.json index 8fcb7525..9178bc4c 100644 --- a/src/DistributedLock.Tests/packages.lock.json +++ b/src/DistributedLock.Tests/packages.lock.json @@ -259,8 +259,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -523,7 +523,7 @@ "DistributedLock.MySql": "[1.0.2, )", "DistributedLock.Oracle": "[1.0.4, )", "DistributedLock.Postgres": "[1.3.0, )", - "DistributedLock.Redis": "[1.1.0, )", + "DistributedLock.Redis": "[1.1.1, )", "DistributedLock.SqlServer": "[1.0.6, )", "DistributedLock.WaitHandles": "[1.0.1, )", "DistributedLock.ZooKeeper": "[1.0.0, )" @@ -549,7 +549,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.8, )", - "MongoDB.Driver": "[3.5.0, )" + "MongoDB.Driver": "[3.5.2, )" } }, "distributedlock.mysql": { @@ -629,13 +629,13 @@ }, "MongoDB.Driver": { "type": "CentralTransitive", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", diff --git a/src/DistributedLock/packages.lock.json b/src/DistributedLock/packages.lock.json index 8d115faf..ad3986a5 100644 --- a/src/DistributedLock/packages.lock.json +++ b/src/DistributedLock/packages.lock.json @@ -763,8 +763,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -1088,7 +1088,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.8, )", - "MongoDB.Driver": "[3.5.0, )" + "MongoDB.Driver": "[3.5.2, )" } }, "distributedlock.mysql": { @@ -1168,13 +1168,13 @@ }, "MongoDB.Driver": { "type": "CentralTransitive", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", @@ -2100,8 +2100,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -2459,7 +2459,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.8, )", - "MongoDB.Driver": "[3.5.0, )" + "MongoDB.Driver": "[3.5.2, )" } }, "distributedlock.mysql": { @@ -2546,13 +2546,13 @@ }, "MongoDB.Driver": { "type": "CentralTransitive", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", diff --git a/src/DistributedLockTaker/packages.lock.json b/src/DistributedLockTaker/packages.lock.json index 0feb1eeb..83e81fa8 100644 --- a/src/DistributedLockTaker/packages.lock.json +++ b/src/DistributedLockTaker/packages.lock.json @@ -172,8 +172,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -482,7 +482,7 @@ "DistributedLock.MySql": "[1.0.2, )", "DistributedLock.Oracle": "[1.0.4, )", "DistributedLock.Postgres": "[1.3.0, )", - "DistributedLock.Redis": "[1.1.0, )", + "DistributedLock.Redis": "[1.1.1, )", "DistributedLock.SqlServer": "[1.0.6, )", "DistributedLock.WaitHandles": "[1.0.1, )", "DistributedLock.ZooKeeper": "[1.0.0, )" @@ -512,7 +512,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.8, )", - "MongoDB.Driver": "[3.5.0, )" + "MongoDB.Driver": "[3.5.2, )" } }, "distributedlock.mysql": { @@ -592,13 +592,13 @@ }, "MongoDB.Driver": { "type": "CentralTransitive", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", @@ -790,9 +790,9 @@ "net8.0": { "Microsoft.NET.ILLink.Tasks": { "type": "Direct", - "requested": "[8.0.21, )", - "resolved": "8.0.21", - "contentHash": "s8H5PZQs50OcNkaB6Si54+v3GWM7vzs6vxFRMlD3aXsbM+aPCtod62gmK0BYWou9diGzmo56j8cIf/PziijDqQ==" + "requested": "[8.0.22, )", + "resolved": "8.0.22", + "contentHash": "MhcMithKEiyyNkD2ZfbDZPmcOdi0GheGfg8saEIIEfD/fol3iHmcV8TsZkD4ZYz5gdUuoX4YtlVySUU7Sxl9SQ==" }, "Azure.Core": { "type": "Transitive", @@ -964,8 +964,8 @@ }, "MongoDB.Bson": { "type": "Transitive", - "resolved": "3.5.0", - "contentHash": "JGNK6BanLDEifgkvPLqVFCPus5EDCy416pxf1dxUBRSVd3D9+NB3AvMVX190eXlk5/UXuCxpsQv7jWfNKvppBQ==", + "resolved": "3.5.2", + "contentHash": "aTlbKclBjXi4j3AkrHiaeuKCvdoGA029VbvXJUmmsCXDz+DEfBFq6n9uOvXJu2YXcJiW9Vay1ZctY5Iu0JRXdw==", "dependencies": { "System.Memory": "4.5.5", "System.Runtime.CompilerServices.Unsafe": "5.0.0" @@ -1185,7 +1185,7 @@ "DistributedLock.MySql": "[1.0.2, )", "DistributedLock.Oracle": "[1.0.4, )", "DistributedLock.Postgres": "[1.3.0, )", - "DistributedLock.Redis": "[1.1.0, )", + "DistributedLock.Redis": "[1.1.1, )", "DistributedLock.SqlServer": "[1.0.6, )", "DistributedLock.WaitHandles": "[1.0.1, )", "DistributedLock.ZooKeeper": "[1.0.0, )" @@ -1211,7 +1211,7 @@ "type": "Project", "dependencies": { "DistributedLock.Core": "[1.0.8, )", - "MongoDB.Driver": "[3.5.0, )" + "MongoDB.Driver": "[3.5.2, )" } }, "distributedlock.mysql": { @@ -1291,13 +1291,13 @@ }, "MongoDB.Driver": { "type": "CentralTransitive", - "requested": "[3.5.0, )", - "resolved": "3.5.0", - "contentHash": "ST90u7psyMkNNOWFgSkexsrB3kPn7Ynl2DlMFj2rJyYuc6SIxjmzu4ufy51yzM+cPVE1SvVcdb5UFobrRw6cMg==", + "requested": "[3.5.2, )", + "resolved": "3.5.2", + "contentHash": "Z1EW+CI18wOQqyAnhZkQXvUVXycrHoOb8/dlJzPHKFfNp9XPB6sEJJR67GUVYyB03B5xzt1RlwSyLXAx9B5+yQ==", "dependencies": { "DnsClient": "1.6.1", "Microsoft.Extensions.Logging.Abstractions": "2.0.0", - "MongoDB.Bson": "3.5.0", + "MongoDB.Bson": "3.5.2", "SharpCompress": "0.30.1", "Snappier": "1.0.0", "System.Buffers": "4.5.1", From e70c35c15700caf5ba1b8097d1cecaabfb8266c3 Mon Sep 17 00:00:00 2001 From: Joes Date: Wed, 17 Dec 2025 18:18:04 +0800 Subject: [PATCH 13/13] fix: Ensure proper cancellation token usage with ConfigureAwait(false) --- src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs b/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs index ee8f96d8..2c0b694f 100644 --- a/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs +++ b/src/DistributedLock.MongoDB/MongoDistributedLockHandle.cs @@ -79,7 +79,7 @@ public async ValueTask DisposeAsync() if (Interlocked.Exchange(ref this._disposed, 1) is 0) { #if NET8_0_OR_GREATER - await this._cts.CancelAsync(); + await this._cts.CancelAsync().ConfigureAwait(false); #else this._cts.Cancel(); #endif @@ -130,7 +130,7 @@ private async Task ExtendLockAsync(TimeoutValue expiry, TimeoutValue extensionCa continue; } #if NET8_0_OR_GREATER - await this._cts.CancelAsync(); + await this._cts.CancelAsync().ConfigureAwait(false); #else this._cts.Cancel(); #endif @@ -145,7 +145,7 @@ private async Task ExtendLockAsync(TimeoutValue expiry, TimeoutValue extensionCa { // Lock extension failed, signal that the lock is lost #if NET8_0_OR_GREATER - await this._cts.CancelAsync(); + await this._cts.CancelAsync().ConfigureAwait(false); #else this._cts.Cancel(); #endif