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
Step 1: Acquiring and Disposing Lock Handles
Use await using to guarantee safe handle disposal:
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 usingGuarantee: 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.