GM.DistributedLock 1.0.2

dotnet add package GM.DistributedLock --version 1.0.2
                    
NuGet\Install-Package GM.DistributedLock -Version 1.0.2
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="GM.DistributedLock" Version="1.0.2" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="GM.DistributedLock" Version="1.0.2" />
                    
Directory.Packages.props
<PackageReference Include="GM.DistributedLock" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add GM.DistributedLock --version 1.0.2
                    
#r "nuget: GM.DistributedLock, 1.0.2"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package GM.DistributedLock@1.0.2
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=GM.DistributedLock&version=1.0.2
                    
Install as a Cake Addin
#tool nuget:?package=GM.DistributedLock&version=1.0.2
                    
Install as a Cake Tool

<p align="center"> <img src="https://raw.githubusercontent.com/gmetskhvarishvili/GM.DistributedLock/master/icon.png" alt="GM.DistributedLock" width="140" height="140" /> </p>

GM.DistributedLock

CI NuGet License: MIT

A small, provider-agnostic distributed lock for .NET. Depend on one interface — IDistributedLock — with TryAcquire (single shot) and Acquire (blocking with retry), each returning an IAsyncDisposable handle that releases on dispose and is owner-safe (it never releases a lock that expired and was re-acquired by someone else). Ships a single-process in-memory implementation; add a real backend with GM.DistributedLock.Redis. Targets .NET 10.

Packages

The two packages version and release together (lockstep):

Package What it gives you
GM.DistributedLock IDistributedLock, ILockHandle, and a single-process in-memory implementation (AddGMDistributedLock()) for dev and tests.
GM.DistributedLock.Redis A real cross-process lock over StackExchange.Redis — SET NX PX to acquire, owner-checked Lua to release (AddGMRedisDistributedLock()).
dotnet add package GM.DistributedLock          # in-process
dotnet add package GM.DistributedLock.Redis     # + Redis backend

Quick start

Register

using GM.DistributedLock;         // in-process
builder.Services.AddGMDistributedLock();

// or, cross-process:
using GM.DistributedLock.Redis;
builder.Services.AddGMRedisDistributedLock(o => o.ConnectionString = "localhost:6379");
// or bind from configuration: AddGMRedisDistributedLock(builder.Configuration, "Redis");

Use it

Inject IDistributedLock — the same code works with either backend:

public class PayoutJob(IDistributedLock locks)
{
    public async Task RunAsync(Guid accountId)
    {
        // Block up to 30s, retrying every 200ms, holding the lock for at most 60s.
        await using var handle = await locks.AcquireAsync(
            resource: $"payout:{accountId}",
            expiry: TimeSpan.FromSeconds(60),
            wait: TimeSpan.FromSeconds(30),
            retryInterval: TimeSpan.FromMilliseconds(200));

        // ... critical section: only one worker runs this per account ...
    }   // lock released here
}

Prefer a non-blocking attempt? Use TryAcquireAsync, which returns null when the lock is held:

await using var handle = await locks.TryAcquireAsync($"report:{id}", TimeSpan.FromMinutes(5));
if (handle is null) return; // someone else is already generating it

The interface

Task<ILockHandle?> TryAcquireAsync(string resource, TimeSpan expiry, CancellationToken ct = default);
Task<ILockHandle>  AcquireAsync(string resource, TimeSpan expiry, TimeSpan wait, TimeSpan retryInterval, CancellationToken ct = default);

AcquireAsync throws LockAcquisitionException if it can't take the lock within wait. Every lock has an expiry (TTL) so a crashed holder can't wedge the resource forever.

The Redis provider targets a single Redis endpoint (SET NX PX + owner-checked release), which is the right trade-off for the vast majority of apps. It is not a multi-node Redlock quorum.

Repository layout

GM.DistributedLock/              # IDistributedLock, ILockHandle, in-memory implementation
GM.DistributedLock.Redis/        # Redis implementation (StackExchange.Redis)
tests/GM.DistributedLock.Tests/  # xUnit tests for the in-memory lock

Building & testing

dotnet build -c Release
dotnet test  -c Release

Releasing

Versioning is automated from Conventional Commits — see CONTRIBUTING.md. Both packages share one version (Directory.Build.props) and publish together to nuget.org on each release.

License

MIT — see LICENSE.

Product Compatible and additional computed target framework versions.
.NET net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (8)

Showing the top 5 NuGet packages that depend on GM.DistributedLock:

Package Downloads
GM.API

An opinionated ASP.NET Core Web API starter for .NET. A single AddGMAPI/UseGMAPI pair wires up controllers, CORS, API versioning, Swagger docs, FluentValidation, Serilog, and middlewares for exception handling, request logging (with sensitive-data masking) and request localization. Loads secrets into configuration from a local appsecrets.json or the store the SECRETS_PROVIDER environment variable names (Doppler, AWS Secrets Manager, Azure Key Vault, HashiCorp Vault). Bundles the GM.API.* companion packages.

GM.Idempotency

Idempotency-key management for the GM.* ecosystem. A provider-agnostic IIdempotencyService (IsProcessed / MarkAsProcessed / TryGetCachedResult, plus a race-safe ExecuteAsync check-and-set) backed by GM.Caching for TTL-based dedup-key storage and guarded by GM.DistributedLock so two near-simultaneous duplicates cannot both pass the "not yet processed" gate. Stores enough of the original result to replay it safely. Flexible key strategy (caller-supplied or derived/hashed) and one call — AddGMIdempotency() — to wire it up. HTTP middleware and GM.Mediator behavior ship in the GM.Idempotency.Http / .Mediator packages.

GM.DistributedLock.Redis

A Redis-backed IDistributedLock for GM.DistributedLock (StackExchange.Redis): acquire with SET NX PX and release with an owner-checked Lua script, so a lock is only ever released by its owner. Register with AddGMRedisDistributedLock().

GM.RateLimiting

Distributed rate limiting for the GM.* ecosystem. A provider-agnostic IRateLimiterService with policy-based checks (TryAcquireAsync(key, policy)) and three per-policy algorithms — fixed window, sliding window, and token bucket. Counters live in GM.Caching so limits hold across every service instance (unlike ASP.NET Core's per-process limiter), and the check-and-increment is made atomic by a swappable store (default backed by GM.DistributedLock). Composable key strategies (per-user / per-API-key / per-IP / per-endpoint), policies from appsettings or fluent code, and a 429-friendly result (limit / remaining / retry-after). One call — AddGMRateLimiting() — to wire it up.

GM.RealTime.Persistence

Cache-backed IConnectionRegistry for GM.RealTime: stores user↔connection and group membership in GM.Caching (Redis-ready) and guards the read-modify-write with GM.DistributedLock, so presence is correct and shared across server nodes. Register with AddGMRealTimeCacheStore().

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.2 415 8/31/2026
1.0.1 135 8/31/2026
1.0.0 687 8/2/2026