Innovabit.DotNet.Interceptors.Coordination.Abstractions
2.0.117
dotnet add package Innovabit.DotNet.Interceptors.Coordination.Abstractions --version 2.0.117
NuGet\Install-Package Innovabit.DotNet.Interceptors.Coordination.Abstractions -Version 2.0.117
<PackageReference Include="Innovabit.DotNet.Interceptors.Coordination.Abstractions" Version="2.0.117" />
<PackageVersion Include="Innovabit.DotNet.Interceptors.Coordination.Abstractions" Version="2.0.117" />
<PackageReference Include="Innovabit.DotNet.Interceptors.Coordination.Abstractions" />
paket add Innovabit.DotNet.Interceptors.Coordination.Abstractions --version 2.0.117
#r "nuget: Innovabit.DotNet.Interceptors.Coordination.Abstractions, 2.0.117"
#:package Innovabit.DotNet.Interceptors.Coordination.Abstractions@2.0.117
#addin nuget:?package=Innovabit.DotNet.Interceptors.Coordination.Abstractions&version=2.0.117
#tool nuget:?package=Innovabit.DotNet.Interceptors.Coordination.Abstractions&version=2.0.117
Innovabit.DotNet.Interceptors.Coordination.Abstractions
Three answers to "this must not happen twice" for Innovabit.DotNet.Interceptors, in one package because they share one question — which calls are the same call? Pick a provider for the two that need somewhere to keep state:
Innovabit.DotNet.Interceptors.Coordination.Memory— in this process.Innovabit.DotNet.Interceptors.Coordination.Redis— shared across instances.
Which one do you want
| Stops | Use it when | |
|---|---|---|
[Idempotent] |
The same side effect happening twice | A client retries a charge, a broker redelivers a message |
[SingleFlight] |
The same work running twice at once, in this process | A hot cache entry expires and a hundred callers all miss |
[DistributedLock] |
The same work running twice at once, anywhere | A nightly job must not run in two instances; a per-aggregate write must be serialised |
They compose: [SingleFlight] collapses the local stampede, [DistributedLock] serialises what is
left across instances, [Idempotent] makes the whole thing safe to retry.
Usage
services
.AddInterceptors()
.AddRedisCoordination(); // or .AddInMemoryCoordination()
services.AddScopedWithInterception<IPaymentService, PaymentService>();
public class PaymentService : IPaymentService
{
// Runs once per idempotency key; a replay gets the original receipt back.
[Idempotent(KeyFrom = nameof(idempotencyKey))]
public Task<Receipt> ChargeAsync(string idempotencyKey, Order order) { ... }
// One run per tenant at a time, across every instance.
[DistributedLock(KeyFrom = nameof(tenantId), LeaseSeconds = 300)]
public Task RebuildAsync(string tenantId) { ... }
// A hundred simultaneous misses become one call.
[SingleFlight(KeyFrom = nameof(symbol))]
[Cache(DurationSeconds = 30)]
public Task<Quote> QuoteAsync(string symbol) { ... }
}
[Idempotent]
| Property | Meaning |
|---|---|
KeyFrom |
Required. Parameter holding the key — a client's Idempotency-Key, a message id. |
Name |
Namespace for the key. Share it between methods the same request may arrive through. |
RetentionSeconds |
How long a result is remembered. Defaults to 86400 (one day). |
KeyFrom is required on purpose: an idempotency key identifies the request, and there is nothing
in a method signature a library could safely invent one from. Omitting it throws.
A result is only remembered once the call succeeds. A failure leaves no trace and the next attempt runs normally — retrying a failure has to be allowed, which is the other half of retrying a success being free.
Retention is how long you are protected for: a retry arriving after it runs again. Match it to how long the caller might keep retrying, not to the smallest number that seems to work.
This is not caching. A cache is an optimisation you could switch off; this is a guarantee about a side effect happening once, which is why it runs outside both the cache and the transaction.
[SingleFlight]
A cache stops repeated work after the first call finishes. It does nothing about the callers that
all miss at the same instant — exactly what happens when a hot entry expires. [SingleFlight] makes
them share one call.
Coalescing is per process: sharing an in-flight call across a network is not something a lock can do, so behind four instances a stampede of four remains. That is the difference between a manageable spike and an outage.
A failure is shared too — everyone waiting on the one call that ran sees its exception — and is never remembered: the next call starts fresh. Synchronous methods pass straight through, since a thread that is blocked has nothing to join.
[DistributedLock]
| Property | Meaning |
|---|---|
KeyFrom |
Parameter that scopes the lock, so two aggregates do not queue behind each other. |
Name |
Name the lock is held under. |
WaitSeconds |
How long to wait before giving up. Defaults to 0 — fail rather than queue. |
LeaseSeconds |
How long the lock survives unreleased. Defaults to 30. |
A caller that cannot take the lock in time gets DistributedLockNotAcquiredException and never
reaches the method.
The lease is also the limit of the promise. It exists so a holder that dies does not block everyone forever, which means a method that outlives its lease loses the lock while still running and two copies can overlap. No lease-based lock avoids this. Size the lease above the method's worst case, and treat the lock as protection against the common case rather than a guarantee of mutual exclusion under arbitrary pauses.
Ordering
… Idempotency 180 → Caching 200 → SingleFlight 250 → DistributedLock 270 → … Retry 500 … Transaction 700
- Idempotency outside caching, so a replay answers without even a cache lookup, and outside the transaction, so a result is only remembered once the work it describes has committed.
- SingleFlight inside caching, because what it collapses is concurrent cache misses, and outside the lock, because coalescing locally is free and taking a remote lock is not.
- DistributedLock inside caching so a cache hit takes no lock, and outside retry and transaction so one holder owns the whole unit of work, attempts included.
Keys
All three resolve a key the same way: the operation's Name (or Type.Method), optionally narrowed
by the value of the KeyFrom parameter. Values are rendered with the invariant culture, so the same
request cannot resolve to two keys depending on the thread that served it. A KeyFrom matching no
parameter throws and names the parameters that do exist — silently widening the scope is the kind of
mistake that only shows up once it is already doing the wrong thing.
License
MIT
| 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
- Innovabit.DotNet.Interceptors.Abstractions (>= 2.0.117)
-
net8.0
- Innovabit.DotNet.Interceptors.Abstractions (>= 2.0.117)
-
net9.0
- Innovabit.DotNet.Interceptors.Abstractions (>= 2.0.117)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on Innovabit.DotNet.Interceptors.Coordination.Abstractions:
| Package | Downloads |
|---|---|
|
Innovabit.DotNet.Interceptors.All
Meta-package: every interceptor concern in the suite (logging, validation, caching, notifications, telemetry, audit, authorization, rate limiting, coordination, feature gates, transactions, retry/timeout/circuit breaker/fallback) in one reference. Providers are separate packages — pick the ones you use. |
|
|
Innovabit.DotNet.Interceptors.Coordination.Redis
Redis idempotency and distributed lock stores for interceptor-based coordination. |
|
|
Innovabit.DotNet.Interceptors.Coordination.Memory
In-process idempotency and lock stores for interceptor-based coordination. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.117 | 0 | 10/31/2026 |
| 2.0.116 | 130 | 10/26/2026 |
| 2.0.115 | 144 | 10/17/2026 |
| 2.0.114 | 134 | 10/10/2026 |
| 2.0.113 | 150 | 10/3/2026 |
| 2.0.112 | 154 | 9/28/2026 |
| 2.0.111 | 144 | 9/28/2026 |
| 2.0.110 | 146 | 9/27/2026 |
| 2.0.109 | 149 | 9/26/2026 |
| 2.0.108 | 148 | 9/26/2026 |
| 2.0.107 | 150 | 9/26/2026 |
| 2.0.106 | 151 | 9/25/2026 |
| 2.0.105 | 141 | 9/25/2026 |
| 1.0.0.104 | 131 | 9/25/2026 |
| 1.0.0.103 | 133 | 9/25/2026 |