OrionLock.Postgres
3.0.0
dotnet add package OrionLock.Postgres --version 3.0.0
NuGet\Install-Package OrionLock.Postgres -Version 3.0.0
<PackageReference Include="OrionLock.Postgres" Version="3.0.0" />
<PackageVersion Include="OrionLock.Postgres" Version="3.0.0" />
<PackageReference Include="OrionLock.Postgres" />
paket add OrionLock.Postgres --version 3.0.0
#r "nuget: OrionLock.Postgres, 3.0.0"
#:package OrionLock.Postgres@3.0.0
#addin nuget:?package=OrionLock.Postgres&version=3.0.0
#tool nuget:?package=OrionLock.Postgres&version=3.0.0
OrionLock.Postgres
PostgreSQL pg_try_advisory_lock backend for OrionLock distributed locking.
Lock lifetime is the PostgreSQL session lifetime: a crashed process releases its locks automatically with no clock-based expiry.
services.AddOrionLock()
.UsePostgres("Host=localhost;Database=app;Username=...;Password=...", o =>
{
o.KeyPrefix = "app:";
o.CommandTimeout = TimeSpan.FromSeconds(30);
});
Notes
- 64-bit integer keys. Postgres advisory locks are keyed by
bigint. The provider hashesKeyPrefix + keywith SHA-256 and takes the first 8 bytes as a little-endianint64. Collision risk is negligible for realistic key counts; useKeyPrefixto namespace if you also share the database withpg_advisory_lockfrom other code paths. - Session-scoped, no clock expiry. A crashed process releases its locks the moment the database session terminates. There is no lease timer in Postgres itself; OrionLock's renewal watchdog only probes the connection liveness.
- Connection pooling. The provider holds each dedicated
NpgsqlConnectionopen for the lifetime of the lock and disposes it on release, returning it to the Npgsql pool only afterpg_advisory_unlockhas run.
Waiting blocks in PostgreSQL, it does not poll
A contended AcquireAsync no longer re-issues pg_try_advisory_lock every RetryInterval. It blocks on
pg_advisory_lock, which returns the instant the lock frees, and bounds that block with
statement_timeout set from the caller's remaining budget in the same round trip. PostgreSQL cancels its
own blocked statement when the budget lapses and reports SQLSTATE 57014, which the provider reads as "not
acquired" rather than as a fault. The single-shot TryAcquireAsync still uses pg_try_advisory_lock.
CommandTimeoutcovers the wait. It still bounds the network round trip, with the wait budget added on top for the blocking call only - otherwise Npgsql would abort a legitimate wait.- The session is left as it was found.
statement_timeoutisRESETonce the lock is granted, so the renewal probe and the release that run on that connection for the rest of the lease do not inherit the wait's budget. - Nothing to configure on the server. A cancelled caller's statement is cancelled, the connection is best-effort unlocked and then disposed, so no backend is left parked in the wait queue.
Reader-writer (shared/exclusive) lock
services.AddOrionLock()
.UsePostgresSharedExclusive("Host=localhost;Database=app;Username=...;Password=...", o =>
{
o.TableName = "orionlock_rw_holds"; // default; idempotently created on first use
o.AutoCreateTable = true; // set false to manage the schema out of band
});
// resolve ISharedExclusiveLock and acquire shared/exclusive holds
For a given key, any number of Shared (read) holders coexist, OR exactly one Exclusive (write) holder owns it.
- Clock-leased table, not advisory locks. Unlike the exclusive-only backend, the reader-writer provider models each hold as a row (reader, writer, or pending-writer) in a table with an explicit
expires_at, so it can track readers individually with their own fencing token and reclaim a dead reader at its own expiry. Lease durations are therefore honoured as a wall-clock TTL against the PostgreSQL server clock (now()). - Atomic transitions. Every acquire / renew / release runs in a transaction that first serializes all transitions for the key with
pg_advisory_xact_lock(auto-released at commit), then prunes expired rows, evaluates, and writes. There is no read-then-write race. - Per-reader fencing. The caller's owner token is the fencing token. Renew and release affect only the caller's own share; releasing an already-expired share is a no-op.
- Writer fairness. A blocked writer plants a lease-bounded pending-writer marker that holds off NEW readers (an existing reader may still refresh) so in-flight readers drain and the writer proceeds. The marker carries the writer's own lease, so a crashed writer cannot block readers past that TTL. This is writer-preference, not strict FIFO among writers.
Acquire-or-give-up-by-deadline
ISharedExclusiveLock also exposes deadline overloads that poll until a deadline and return null on expiry instead of throwing LockAcquisitionTimeoutException:
var handle = await rwLock.TryAcquireExclusiveAsync("k", deadline: TimeSpan.FromSeconds(2));
if (handle is null) { /* could not acquire in time - ordinary control flow */ }
Lease durations
pg_advisory_lock is session-scoped: the hold lives until release or session end, and no wall clock
bounds it. LeaseDuration therefore sets the renewal cadence and the watchdog's grace period but does
not expire anything, so handle.EffectiveLeaseDuration reports Timeout.InfiniteTimeSpan rather than
the value you asked for. That is also why a crashed process releases its locks immediately, without
waiting out a TTL.
Requires the OrionLock package. See https://github.com/tunahanaliozturk/OrionLock.
| Product | Versions 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 is compatible. 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 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.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Npgsql (>= 8.0.5)
- OrionLock (>= 3.0.0)
-
net8.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Npgsql (>= 8.0.5)
- OrionLock (>= 3.0.0)
-
net9.0
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 9.0.0)
- Npgsql (>= 8.0.5)
- OrionLock (>= 3.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 |
|---|---|---|
| 3.0.0 | 91 | 9/20/2026 |
| 2.0.0 | 144 | 7/29/2026 |
| 1.0.1 | 116 | 7/27/2026 |
| 1.0.0 | 126 | 6/30/2026 |
| 0.6.0 | 117 | 6/27/2026 |
| 0.5.0 | 138 | 6/26/2026 |
| 0.4.1 | 163 | 6/20/2026 |
| 0.4.0 | 129 | 6/19/2026 |
| 0.3.32 | 134 | 6/17/2026 |
| 0.3.31 | 127 | 6/16/2026 |
| 0.3.30 | 131 | 6/16/2026 |
| 0.3.28 | 113 | 6/15/2026 |
| 0.3.27 | 114 | 6/15/2026 |
| 0.3.26 | 117 | 6/13/2026 |
| 0.3.25 | 110 | 6/13/2026 |
| 0.3.24 | 118 | 6/12/2026 |
| 0.3.23 | 117 | 6/12/2026 |
| 0.3.22 | 118 | 6/11/2026 |
| 0.3.21 | 125 | 6/11/2026 |
| 0.3.20 | 116 | 6/11/2026 |