Skip to content
bitzorcas
中EN

Reference

Distributed Locking and Deadlock Prevention: IDistributedLock Deep Dive

Explore the BitzOrcas.Modern distributed locking core. Learn atomic acquisition, owner-checked Lua releases, lease renewals, and fallback strategies.

Last updated

In distributed environments, the most common source of financial inconsistency is duplicate operations (e.g. double invoicing or charging). For example: If an operator double-clicks “Settle Invoice”, two API instances receive the request concurrently. Without cross-process mutual exclusion, duplicate transactions will occur!

BitzOrcas.Modern provides the typed IDistributedLock abstraction: Powered by Redis atomic commands (SET NX PX) and Lua scripts verifying handle ownership, eliminating deadlocks and accidental lock releases.

Distributed Locking Flow

Acquired handleLong operationFinishedTimeout / Busy

1. Critical Operation Request

2. IDistributedLock.AcquireAsync(ResourceKey)

3. Execute Critical Section

4. Handle.RenewAsync (Extend lease)

5. await using Handle.DisposeAsync (Atomic release)

6. Short-Circuit 409 Conflict


Step 1: Acquiring and Disposing Lock Handles

Use await using to guarantee safe handle disposal:

GenerateInvoiceCommandHandler.cs: Distributed Lock in Action
using BitzOrcas.Infrastructure.Locking;
using BitzOrcas.Domain.Results;
public static class BillingErrors
{
public static readonly Error InvoiceBusy =
Error.Conflict("Billing.InvoiceBusy", "Invoice is currently being processed.");
}
public sealed class GenerateInvoiceCommandHandler(
IDistributedLock distributedLock,
IInvoiceService invoiceService)
{
public async ValueTask<Result> Handle(GenerateInvoiceCommand command, CancellationToken ct)
{
// ① Resource keys must include TenantId and unique business identifier
var resourceKey = $"billing:{command.TenantId}:invoice:{command.InvoiceId}";
// ② Attempt acquisition: 30s lease expiry, max 2s bounded wait
await using var handle = await distributedLock.AcquireAsync(
resourceKey,
expiry: TimeSpan.FromSeconds(30),
waitTimeout: TimeSpan.FromSeconds(2),
cancellationToken: ct);
// ③ Short-circuit on failure with 409 Conflict
if (handle is null || !handle.IsHeld)
{
return Result.Failure(BillingErrors.InvoiceBusy);
}
// ④ Execute idempotent business processing inside critical section
return await invoiceService.ProcessInvoiceAsync(command.InvoiceId, ct);
}
}

Step 2: Architectural Red Lines


Summary

IDistributedLock provides safe, reliable clustering synchronization:

  • await using Guarantee: Prevents leaked locks on unexpected exceptions;
  • Bounded Wait Timeouts: Eliminates thread pool exhaustion;
  • Tenant Key Namespacing: Enforces strict SaaS tenant isolation.

Renewal and key conventions

Renewal calls ILockHandle.RenewAsync(expiry, ct) (returning ValueTask<bool>) for holders still inside a critical section whose lease is expiring. Redis lock keys carry the bitzorcas:lock: prefix; behavior on acquisition failure is governed by DistributedLockOptions.FallbackMode, defaulting to Deny — callers receive null instead of queueing.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%