Reimaginate.ProcessingLockService.Cosmos
4.0.0
dotnet add package Reimaginate.ProcessingLockService.Cosmos --version 4.0.0
NuGet\Install-Package Reimaginate.ProcessingLockService.Cosmos -Version 4.0.0
<PackageReference Include="Reimaginate.ProcessingLockService.Cosmos" Version="4.0.0" />
<PackageVersion Include="Reimaginate.ProcessingLockService.Cosmos" Version="4.0.0" />
<PackageReference Include="Reimaginate.ProcessingLockService.Cosmos" />
paket add Reimaginate.ProcessingLockService.Cosmos --version 4.0.0
#r "nuget: Reimaginate.ProcessingLockService.Cosmos, 4.0.0"
#:package Reimaginate.ProcessingLockService.Cosmos@4.0.0
#addin nuget:?package=Reimaginate.ProcessingLockService.Cosmos&version=4.0.0
#tool nuget:?package=Reimaginate.ProcessingLockService.Cosmos&version=4.0.0
Reimaginate.ProcessingLockService.Cosmos
Optional Azure Cosmos DB for NoSQL provider for the processing lock service (.NET 10).
This provider now targets the 4.0.0 package family and does not bring in Redis dependencies.
See the 4.0 migration guide when upgrading existing applications.
It implements ITenantAwareProcessingLockRepository and preserves the service's tenant,
ownership-token, same-owner reacquisition, renewal and partial-batch semantics.
Configure an existing container
Use a dedicated container with one partition key path, /tenantId, partition key version 2,
and container TTL enabled with no default expiry (DefaultTimeToLive = -1). Use a single
write region. The runtime does not provision infrastructure or own/dispose the supplied
CosmosClient. Reuse a long-lived client. Resource keys are hashed to safe document IDs;
the original key is retained in the document. Tenant IDs support up to 2048 UTF-8 bytes.
using Microsoft.Azure.Cosmos;
using Microsoft.Extensions.DependencyInjection;
using Reimaginate.ProcessingLockService;
using Reimaginate.ProcessingLockService.Cosmos;
// Reuse the application's existing client (including its authentication configuration).
Container container = cosmosClient.GetContainer("application", "processing-locks");
using var startupTimeout = new CancellationTokenSource(TimeSpan.FromSeconds(30));
ConsistencyLevel accountConsistency =
await container.ValidateCosmosLockContainerAsync(startupTimeout.Token);
services.AddProcessingLockService(options => options.WithCosmosRepository(container, cosmos =>
{
cosmos.OperationTimeout = TimeSpan.FromSeconds(5);
cosmos.MaxConcurrencyRetries = 8;
}));
ValidateCosmosLockContainerAsync checks partition configuration, TTL and write-region
count without changing them. Call it during startup; constructors perform no network I/O.
It returns account consistency for the host to check against its failover requirements.
The client/database/container overload is also available:
options.WithCosmosRepository(cosmosClient, "application", "processing-locks").
The store uses stream APIs with its own JSON schema, independently of client serializer settings.
Provision the container separately, for example during environment setup:
await cosmosClient.GetDatabase("application").CreateContainerAsync(
new ContainerProperties("processing-locks", "/tenantId")
{
PartitionKeyDefinitionVersion = PartitionKeyDefinitionVersion.V2,
DefaultTimeToLive = -1
});
The application identity needs item read/query/create/replace/delete and metadata read permissions. Startup validation also reads account metadata. Provisioning permissions are only needed by the environment setup identity.
Correctness and limits
- New leases use create-if-absent. Expired-document takeovers, renewals and releases use
the ETag read from Cosmos. Release and renewal also require the current ownership token.
Key-only release is rejected with
BadRequest, including the obsolete repository overload. - Null and empty-string owners are anonymous. An active anonymous lock cannot be reacquired by owner value, but its token permits explicit renewal and release, matching Redis.
- If any renewal/release batch item has a null or empty token, the entire batch is rejected
before storage changes:
BatchFailureoverall andInUsefor every item. Nonempty but incorrect tokens retain per-item failure behaviour, allowing other valid items to proceed. requireAll: trueuses one transaction in the tenant partition, up to 100 distinct keys. Every key participates, including same-owner leases returned without renewal. Requests above the limit are rejected before writes, never split into independent transactions.- Serialized documents plus a conservative per-operation framing allowance must fit a
1,900,000-byte budget below Cosmos's 2 MB batch limit. Oversized requests return
BadRequest. requireAll: falseacquires keys independently, skips held keys and allows more than 100 keys. Storage errors can leave earlier successful acquisitions in place; returned results identify them when available. A timeout/cancellation can leave an unknown write outcome.- Explicit UTC expiry controls lease validity, including subsecond durations. Cosmos TTL only cleans up later (duration rounded up to seconds, plus 60 seconds). Reads exclude logically expired leases even while their documents remain stored.
- UTC comes from
TimeProvider.Systemby default. All participants must have synchronized clocks. A customTimeProvideris available for testing. This is not a server-clock lease or a fencing mechanism for writes to external resources. Choose lease durations with enough margin for network latency, clock uncertainty and pauses, and renew before expiry. - After ambiguous acquisition/renewal responses (HTTP 408/500/503/504 or transport loss), the provider verifies every written document's operation ID, token and expiry. It returns success only if all can be confirmed and remain active; it does not blindly reacquire. An exhausted/cancelled verification returns failure. Unconfirmed leases expire normally. An ambiguous delete returns failure even if it actually committed.
- An operation timeout (default 5 seconds) covers the entire repository call, SDK throttling
retries and verification. ETag races have a bounded retry count and short backoff.
Cancellation/timeouts/throttling map to
TimeOut; authorization/availability failures map toUnexpectedFailure, rather thanInUse. Cosmos 400/413 map toBadRequest. - Multi-region writes and conflict resolution are unsupported. Single-writer ETag checks protect normal operation with weaker read consistency, but stale reads can cause retries or conservative failures. Regional failover with possible loss of acknowledged writes is outside the lease guarantee. Use a strong-consistency account if preserving acknowledged locks across regional failover is required, and validate the actual account/client setup. This provider does not orchestrate failover. Recheck configuration after account changes.
- Each tenant shares a logical partition. Benchmark busy tenants, polling frequency, request-unit consumption and achievable lease durations before production use.
Tests
The default suite runs the shared provider contract against the real Cosmos repository with an atomic test store, plus deterministic races, timeout/lost-response tests and SDK stream/batch/query adapter tests. These tests do not establish live-service behaviour.
With Docker Desktop, PowerShell 7 and the .NET 10 SDK available, run the repository's local emulator command from the repository root:
./tools/test-cosmos-emulator.ps1
./tools/test-cosmos-emulator.ps1 -AllTests
The first command executes the live Cosmos integration tests and fails if any are skipped.
-AllTests runs the full regression suite with Cosmos enabled. Results are written under
artifacts/cosmos-emulator/. See the local emulator guide.
The runner uses compose.cosmos.yml with a pinned official vNext emulator image, HTTPS,
localhost-only ports, and a dedicated data volume. It exports the container's public root
certificate and trusts it only in the test client's certificate chain policy. It does not
modify Windows certificate stores or disable certificate/hostname validation. The test
database is created only when emulator mode is explicitly enabled for an HTTPS loopback
endpoint. The container and database remain available after the run.
For an existing Azure test database or separately managed emulator, configure these variables:
PROCESSINGLOCK_COSMOS_CONNECTION_STRING: the emulator connection string.PROCESSINGLOCK_COSMOS_DATABASE_ID: the existing test database ID.
The fixture uses gateway mode and normal certificate verification unless the explicit local
emulator root is supplied. It creates a unique
processing-lock-tests-<guid> container with /tenantId and TTL enabled, then deletes
only that container at teardown. The existing-database mode does not create/delete the database. Configured
connection failures fail tests; tests skip only when neither environment configuration nor
local emulator settings are present. You can
also use a dedicated Azure test database; operations consume request units and container
creation may allocate throughput according to that database's configuration.
The runner temporarily sets PROCESSINGLOCK_COSMOS_EMULATOR=true to create the local test
database if absent and PROCESSINGLOCK_COSMOS_EMULATOR_CERTIFICATE to the exported public
root certificate. Both features reject non-loopback or non-HTTPS endpoints. All four
environment variables are restored when the script exits.
The runner also saves Git-ignored cosmos-emulator.local.json settings in the test project
for Visual Studio Test Explorer and ordinary dotnet test runs. Build the test project after
setup; the fixture reads the copied local settings when no explicit environment configuration
is present. Keep the emulator running. See the emulator guide for precedence and opt-out details.
The vNext emulator does not enforce authentication. These runs validate provider behaviour, not Azure authentication/authorization or multi-region failover. Use an Azure test account for those environment-specific checks.
dotnet test test/Reimaginate.ProcessingLockService.UnitTests/Reimaginate.ProcessingLockService.UnitTests.csproj --filter FullyQualifiedName~Cosmos
The live suite includes the shared contract and concurrent requests through independent repositories, bypassing the service's process-local semaphore. Very short contract leases (100 ms) require a low-latency local environment; lease expiry before confirmation is a correct failure rather than a successful acquisition.
Package and release
Build/pack locally with:
dotnet build src/Reimaginate.ProcessingLockService.Cosmos/Reimaginate.ProcessingLockService.Cosmos.csproj -c Release
This produces a separate Reimaginate.ProcessingLockService.Cosmos package. The public
release manifest includes it alongside Abstractions, Core and Redis at version 4.0.0.
The existing GitHub Actions NuGet Release workflow packs/uploads all four on manual
runs and publishes them on release-* tag pushes through the nuget-publish environment,
after confirming the tagged commit is reachable from main. Adding the manifest entry
does not publish anything; a release still requires the explicit tag-driven release process.
References: ETag concurrency, transactional batches and limits, TTL, consistency.
| Product | Versions 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. |
-
net10.0
- Microsoft.Azure.Cosmos (>= 3.63.3)
- Newtonsoft.Json (>= 13.0.4)
- Reimaginate.ProcessingLockService (>= 4.0.0)
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 |
|---|---|---|
| 4.0.0 | 95 | 10/27/2026 |