HostLoom.Caching
0.4.0
See the version list below for details.
dotnet add package HostLoom.Caching --version 0.4.0
NuGet\Install-Package HostLoom.Caching -Version 0.4.0
<PackageReference Include="HostLoom.Caching" Version="0.4.0" />
<PackageVersion Include="HostLoom.Caching" Version="0.4.0" />
<PackageReference Include="HostLoom.Caching" />
paket add HostLoom.Caching --version 0.4.0
#r "nuget: HostLoom.Caching, 0.4.0"
#:package HostLoom.Caching@0.4.0
#addin nuget:?package=HostLoom.Caching&version=0.4.0
#tool nuget:?package=HostLoom.Caching&version=0.4.0
HostLoom.Caching
A two-tier cache for services that need one: an in-process tier in front of an optional
distributed tier, per-key single-flight, a best-effort cluster-wide lease, cross-instance
invalidation, and fail-open behaviour when the distributed store misbehaves. This package is the
kernel: contracts, the in-process stores, the serializer, the tiered composition, metrics, and an
execution-free probe. It references only Microsoft.Extensions.Logging.Abstractions, so a
consumer that takes ICache in a constructor pulls in nothing else. Registration lives in
HostLoom.Caching.DependencyInjection; backends such as Redis live in their own package.
What a consumer sees
public sealed class CatalogService(ICache cache)
{
public ValueTask<Catalog?> GetAsync(string region, CancellationToken cancellationToken) =>
cache.GetOrCreateAsync(
$"catalog:{region}",
region,
static (region, token) => LoadCatalogAsync(region, token),
new CacheEntryOptions(TimeSpan.FromMinutes(10)) { Tags = ["catalog"] },
cancellationToken);
}
GetOrCreateAsync looks in the in-process tier, then the distributed tier, then takes the
per-key guard and runs the factory once per key per process. A distributed hit repopulates the
in-process tier with the remaining time to live. The state-carrying overload shown above captures
nothing, so an in-process hit allocates nothing on the caller's side; the simpler
Func<CancellationToken, ValueTask<T>> overload exists for ergonomics.
TryGetAsync<T> returns a CacheLookup<T> with Found, Value, Tier, and Degraded, and is
the member new code uses. GetAsync<T> returns default(T) on a miss, which for a value type
makes a cached 0 indistinguishable from an absence; it exists for call sites written against
that contract. SetAsync, SetIfAbsentAsync, RemoveAsync, RemoveByTagAsync, GetManyAsync,
and WarmupAsync complete the surface. Every member returns ValueTask, takes a trailing
optional CancellationToken, and is thread-safe.
Fail-open
A distributed-store failure never reaches a consumer as an exception from a read or a
get-or-create. The cache degrades to the factory, keeps the in-process tier, records
hostloom.cache.errors and a degraded outcome on hostloom.cache.operation.duration, and logs
one warning per key per Caching:Diagnostics:DegradedLogInterval. The exceptions to this rule are
deliberate: SetIfAbsentAsync throws CacheUnavailableException when the caller chose
UnavailableBehavior.Throw, so a rate limiter can choose between allow and deny, and cancellation
always propagates. A factory exception propagates unchanged and nothing is stored.
Keys
CachingOptions.Namespace is required and prefixes every key. Each kind of key has its own domain,
so a consumer key can never collide with a lease, a tag index, or a lock:
| Purpose | Key |
|---|---|
| cache entry | {namespace}:cache:data:{key} |
| stampede lease | {namespace}:cache:lease:{key} |
| tag index | {namespace}:cache:tag:{tag} |
| invalidation channel | {namespace}:cache:invalidate |
Consumers never see or repeat the prefix. Keys are opaque strings without whitespace or control
characters, bounded by Caching:MaxKeyLength. CacheKey.FromSensitive hashes a credential so it
never reaches the store or a log; CacheKey.Versioned appends a per-call-site schema version;
CachingOptions.PayloadVersion bumps the whole cache.
Serialization
Payloads go through ICacheValueSerializer, generic over T so a source-generated
JsonSerializerContext works without reflection. SystemTextJsonCacheValueSerializer requires
JsonSerializerOptions.TypeInfoResolver to be set; CreateReflectionBased is the annotated
opt-out. Each payload carries a one-byte header with the format version and flags; payloads at or
above Caching:Compression:ThresholdBytes are Brotli-compressed. A payload from another format
version is a silent miss so a rolling deploy does not log errors; a payload that fails to
deserialize is a miss logged at error level and overwritten by the next factory result.
Composing without a container
Every runtime type has a public constructor. A test or a non-hosted program composes a cache with
new, and the dependency-injection package composes exactly the same object graph:
var options = new CachingOptions { Namespace = "catalog" };
var store = new InMemoryDistributedCacheStore(timeProvider);
var serializer = new SystemTextJsonCacheValueSerializer(jsonOptions);
await using var cache = new TieredCache(options, store, serializer, timeProvider: timeProvider);
TieredCache with no store is the in-process-only cache. InMemoryDistributedCacheStore is a
byte-payload store and invalidation channel in process memory: two caches over one instance share
payloads and invalidate each other, which is what the conformance suite and the Native AOT sample
use. LocalCacheStore is the in-process tier on its own.
Observability
Meter and activity source are both named HostLoom.Caching. Instruments are
hostloom.cache.operation.duration, hostloom.cache.factory.duration, hostloom.cache.entries,
hostloom.cache.guards.active, hostloom.cache.stampede.lease_missed,
hostloom.cache.invalidations, hostloom.cache.invalidation.resubscribed,
hostloom.cache.errors, and hostloom.cache.compressions; identity is the
hostloom.cache.namespace tag. CachingProbe.Describe(cache) returns the composition, each line
naming the option that decided it, without executing anything.
| 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.Extensions.Logging.Abstractions (>= 10.0.11)
NuGet packages (3)
Showing the top 3 NuGet packages that depend on HostLoom.Caching:
| Package | Downloads |
|---|---|
|
HostLoom.Caching.DependencyInjection
Microsoft.Extensions.DependencyInjection registration, options validation, warmup, and health checks for HostLoom caching. |
|
|
HostLoom.Caching.Pipelines
Cache and deduplication filters for HostLoom pipelines, built on the HostLoom caching kernel. |
|
|
HostLoom.Caching.Testing
Container-free cache composition, recording and fault-injecting store decorators for testing HostLoom caching consumers. |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 0.17.0 | 59 | 10/25/2026 |
| 0.16.1 | 116 | 10/22/2026 |
| 0.16.0 | 214 | 10/14/2026 |
| 0.15.1 | 153 | 10/12/2026 |
| 0.15.0 | 149 | 10/4/2026 |
| 0.14.0 | 139 | 10/4/2026 |
| 0.13.0 | 131 | 9/28/2026 |
| 0.12.0 | 124 | 9/27/2026 |
| 0.11.0 | 136 | 9/25/2026 |
| 0.10.0 | 142 | 9/22/2026 |
| 0.9.0 | 136 | 9/22/2026 |
| 0.8.0 | 142 | 9/19/2026 |
| 0.7.0 | 141 | 9/17/2026 |
| 0.6.0 | 159 | 9/6/2026 |
| 0.5.0 | 148 | 9/5/2026 |
| 0.4.0 | 147 | 9/3/2026 |