DistributedLock source
This page is part of the documentation for Orleans.Lattice 9.9.0 (release line 9.9), built 2026-10-04. It is also published as markdown, with every table and list, at source.md, and llms.txt lists every page.The source of the DistributedLock sample.
Program.cs
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using Orleans.Hosting;
using Orleans.Lattice;
// ---------------------------------------------------------------------------
// DistributedLock - a cluster-wide, FIFO-fair distributed lock / lease.
//
// ILatticeLockGrain packages the single-threaded-grain FIFO mutual-exclusion
// pattern and gets the failure modes right: monotonic fencing tokens (so a
// superseded holder is detectable) and bounded leases (so a crashed holder cannot
// wedge the lock forever). This sample demonstrates:
// 1. Acquire -> fencing token -> renew -> release.
// 2. Non-blocking TryAcquire: null while held, granted after release.
// 3. FIFO blocking: a queued waiter is granted the instant the holder releases,
// with a strictly higher fencing token.
// ---------------------------------------------------------------------------
using var host = Host.CreateDefaultBuilder(args)
.ConfigureLogging(logging =>
{
// Silence Orleans so the console shows only the feature narration.
logging.ClearProviders();
logging.SetMinimumLevel(LogLevel.None);
})
.UseOrleans(silo =>
{
silo.UseLocalhostClustering();
silo.AddMemoryGrainStorageAsDefault();
silo.UseInMemoryReminderService();
silo.AddLattice((s, name) => s.AddMemoryGrainStorage(name));
})
.Build();
await host.StartAsync();
var grainFactory = host.Services.GetRequiredService<IGrainFactory>();
// All callers naming the same lock contend for the same activation, serialized FIFO.
var theLock = grainFactory.GetGrain<ILatticeLockGrain>("inventory/sku-42");
Console.WriteLine("== DistributedLock sample ==");
Console.WriteLine();
// --- 1. Acquire, renew, release --------------------------------------------
Console.WriteLine("1) Acquiring the lock (30s lease, willing to wait 5s)...");
var lease = await theLock.AcquireAsync(new LockAcquireRequest(
LeaseDuration: TimeSpan.FromSeconds(30),
MaxWait: TimeSpan.FromSeconds(5)));
Console.WriteLine($" granted, fencing token = {lease.Token.FencingToken}");
var heldStatus = await theLock.GetStatusAsync();
Console.WriteLine($" status: held={heldStatus.IsHeld} queueDepth={heldStatus.QueueDepth}");
lease = await theLock.RenewAsync(lease.Token, TimeSpan.FromSeconds(30));
Console.WriteLine(" renewed the lease.");
await theLock.ReleaseAsync(lease.Token);
Console.WriteLine(" released. The next caller can acquire immediately.");
Console.WriteLine();
// --- 2. Non-blocking try-acquire under contention --------------------------
// TryAcquire never queues: it returns a lease if the lock is free right now, or
// null if it is currently held. Use it for a singleton job that should be skipped
// when another worker already holds the lock.
var holder = await theLock.AcquireAsync(new LockAcquireRequest(
LeaseDuration: TimeSpan.FromSeconds(30),
MaxWait: TimeSpan.FromSeconds(5)));
Console.WriteLine($"2) Holder acquired, fencing token = {holder.Token.FencingToken}.");
var contended = await theLock.TryAcquireAsync(TimeSpan.FromSeconds(30));
Console.WriteLine($" TryAcquire while held -> {(contended is null ? "null (skipped)" : "granted")}");
await theLock.ReleaseAsync(holder.Token);
var afterRelease = await theLock.TryAcquireAsync(TimeSpan.FromSeconds(30));
Console.WriteLine($" TryAcquire after release -> {(afterRelease is null ? "null" : $"granted, fencing token = {afterRelease.Value.Token.FencingToken}")}");
if (afterRelease is not null)
{
await theLock.ReleaseAsync(afterRelease.Value.Token);
}
Console.WriteLine();
// --- 3. FIFO blocking: a queued waiter is granted on release ----------------
// A blocking AcquireAsync enqueues the caller FIFO and completes from a later turn
// - a release, a lease expiry, or its own wait-timeout - without ever blocking the
// grain's activation turn.
var first = await theLock.AcquireAsync(new LockAcquireRequest(
LeaseDuration: TimeSpan.FromSeconds(30),
MaxWait: TimeSpan.FromSeconds(5)));
Console.WriteLine($"3) First holder acquired, fencing token = {first.Token.FencingToken}.");
// A second caller starts waiting in the FIFO queue while the lock is held.
var waiter = theLock.AcquireAsync(new LockAcquireRequest(
LeaseDuration: TimeSpan.FromSeconds(30),
MaxWait: TimeSpan.FromSeconds(10)));
await Task.Delay(250); // let the waiter reach the queue
var queuedStatus = await theLock.GetStatusAsync();
Console.WriteLine($" a second caller is queued: queueDepth={queuedStatus.QueueDepth}");
Console.WriteLine(" first holder releases...");
await theLock.ReleaseAsync(first.Token);
var second = await waiter; // completes the instant the release hands the lock over
Console.WriteLine($" queued waiter granted, fencing token = {second.Token.FencingToken} (strictly higher).");
await theLock.ReleaseAsync(second.Token);
Console.WriteLine();
Console.WriteLine("Done.");
await host.StopAsync();
DistributedLock.csproj
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<RootNamespace>Orleans.Lattice.Samples.DistributedLock</RootNamespace>
<AssemblyName>Orleans.Lattice.Samples.DistributedLock</AssemblyName>
<IsPackable>false</IsPackable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Hosting" Version="10.0.11" />
<PackageReference Include="Microsoft.Orleans.Server" Version="10.2.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\lattice\Orleans.Lattice.csproj" />
</ItemGroup>
</Project>