Ruvio.AspNetCore.DistributedLock 0.1.0

dotnet add package Ruvio.AspNetCore.DistributedLock --version 0.1.0
                    
NuGet\Install-Package Ruvio.AspNetCore.DistributedLock -Version 0.1.0
                    
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="Ruvio.AspNetCore.DistributedLock" Version="0.1.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Ruvio.AspNetCore.DistributedLock" Version="0.1.0" />
                    
Directory.Packages.props
<PackageReference Include="Ruvio.AspNetCore.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 Ruvio.AspNetCore.DistributedLock --version 0.1.0
                    
#r "nuget: Ruvio.AspNetCore.DistributedLock, 0.1.0"
                    
#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 Ruvio.AspNetCore.DistributedLock@0.1.0
                    
#: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=Ruvio.AspNetCore.DistributedLock&version=0.1.0
                    
Install as a Cake Addin
#tool nuget:?package=Ruvio.AspNetCore.DistributedLock&version=0.1.0
                    
Install as a Cake Tool

Ruvio.AspNetCore.DistributedLock

Distributed mutual exclusion on Ruvio. A lock is one key holding a random owner token, and every step is a single server command that only the owner can win:

Step Command
acquire SET key token NX PX lease
renew SET key token IFEQ token PX lease
release DELEX key IFEQ token

Release and renewal can never touch a lock another owner took after yours expired, which is the bug a plain DEL has. This needs a Ruvio server with SET … IFEQ and DELEX.

services.AddRuvioDistributedLock(options => options.KeyPrefix = "myapp:lock:");

await using IRuvioLock? held = await locks.TryAcquireAsync(
    "nightly-report", lease: TimeSpan.FromSeconds(30), wait: TimeSpan.FromSeconds(5));

if (held is null) return;               // someone else runs it

await RunReportAsync(held.LockLost);    // stops if the lock is lost mid-run
  • TryAcquireAsync returns null when the lock stayed taken; AcquireAsync throws TimeoutException.
  • With AutoRenew (default) the lease is renewed every third of its length. If a whole lease passes without a successful renewal, or a renewal finds another owner, IsHeld turns false and LockLost is cancelled; pass that token to the guarded work.
  • Disposing releases the lock; a failed release is swallowed because the lease expires on its own.

ASP.NET Core: [RuvioLock] for endpoints

Why. Some endpoints must never run twice at once for the same thing: charging an order, numbering an invoice, importing a file, closing a day. A lock or SemaphoreSlim only protects one instance. With two pods, or a double click that reaches a different pod, both requests run. [RuvioLock] moves the guard into Ruvio so it holds across every instance, and removes the acquire / try-finally / renew / map-to-HTTP boilerplate.

builder.Services.AddRuvioDistributedLock();          // needs an IRuvioClient registered
app.UseRouting();
app.UseAuthentication();
app.UseRuvioLock();                                  // after routing and auth

// minimal API
app.MapPost("/orders/{id}/pay", PayAsync).WithMetadata(new RuvioLockAttribute("order:{id}"));

// controller
[RuvioLock("nightly-import", leaseSeconds: 120, WaitMilliseconds = 0)]
[HttpPost("import")]
public Task<IActionResult> Import(CancellationToken ct) => ...;   // ct is cancelled if the lock is lost
Behaviour Detail
Name Fixed ("nightly-import") or template: {id} = route value, then query value; {user} = user name. An unresolved placeholder answers 400.
Taken Waits WaitMilliseconds (default 0), then answers RejectStatusCode (default 409) with Retry-After; your code does not run.
Running The lease is renewed automatically; the lock is released when the response finishes, also when the endpoint throws or the client disconnects.
Lock lost HttpContext.RequestAborted is replaced by a token that also fires when the lock is lost, so EF Core, HttpClient and your code stop instead of running next to the new owner.
Server unreachable Fails closed with 503 instead of running unguarded.
Cost Endpoints without the attribute return the next delegate's task directly: no allocation.

Telemetry

Meter and ActivitySource Ruvio.AspNetCore.DistributedLock (same shape as the rate-limiting package): counters ruvio.lock.acquired, ruvio.lock.rejected (contention), ruvio.lock.lost (unhealthy server link) and ruvio.lock.errors, plus histogram ruvio.lock.wait_duration (ms). Fixed names are tagged resource; templated names are not, to keep cardinality bounded.

builder.Services.AddOpenTelemetry().WithMetrics(m => m.AddMeter(RuvioLockTelemetry.Name));

Limits

  • One lock lives on one server. If a failover loses the key, two owners can overlap for up to a lease. Work that must never overlap should also check a version or fencing value on the resource it writes. This package does not issue fencing tokens: handing out an increasing number and taking the lock are two commands, so they cannot be made atomic here.
  • Locks are not reentrant. Acquiring a held resource again from the same process waits like any caller.
  • Lease times are wall-clock server TTLs; keep the lease well above the longest pause your process can have.
Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 was computed.  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

This package is not used by any NuGet packages.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.1.0 50 10/12/2026