Skip to content

release: 1.1.0 — Redis fail-fast, RedisCacheOptions, MIT licence correction - #16

Merged
teghoz merged 6 commits into
mainfrom
develop
Sep 20, 2026
Merged

teghoz merged 6 commits into
mainfrom
develop

Conversation

@teghoz

@teghoz teghoz commented Sep 20, 2026

Copy link
Copy Markdown
Owner

Release PR for 1.1.0. Merging this, then tagging v1.1.0, triggers the NuGet publish workflow.

What's in it

Fix: an unreachable Redis cost ~10–12s per request (#14)

AddCacheWeaveRedis set AbortOnConnectFail = false but left BacklogPolicy at its StackExchange.Redis default, which queues commands while disconnected. A cache-decorated request pays a read on the way in and a write on the way out, each blocking for the full SyncTimeout — indefinitely, not as a one-time connect cost. CacheWeave converted the failures to cache misses, so every log and metric showed a healthy 200 while the service was effectively down. One consumer ran that way for nine days.

BacklogPolicy.FailFast is now set alongside AbortOnConnectFail: never throw on connect, and never block waiting for a connection that isn't there. It is never consulted on the success path, so a reachable Redis is unaffected.

Evictions are the exception — a dropped [CacheWeaveEvict] delete leaves a stale entry served until it expires — so they wait up to 1.5s for a reconnect and retry once.

Configuration

  • configureConnection callback: overrides the defaults above and reaches settings no connection string can express (TLS certs, Sentinel, Azure token auth).
  • RedisCacheOptions via configureCache: EvictionRetryWindow, ReconnectPollInterval, ScanPageSize — available on the multiplexer overload too, which previously offered no provider configuration at all.
  • TryAddSingleton for IConnectionMultiplexer, matching CacheWeave.Legacy.

Also

  • The identical defect and fix in CacheWeave.Legacy (net48).
  • Licence correction (docs: correct README licence to MIT #15): the README said GPL-3.0 while LICENSE and every csproj say MIT. Since the README is packed into each .nupkg, the shipped README contradicted its own package metadata.

Compatibility

Scenario Change
Reachable Redis None
Unreachable Redis Commands fail immediately instead of after SyncTimeout; the filter already converted either into a cache miss, so only latency changes
Brief blip (failover, restart) Reads/writes become misses instead of waiting; evictions still land via the retry

A caller relying on the backlog to ride out a blip now takes a cache miss instead of up to SyncTimeout of latency. That is the behaviour change that makes this a minor, not a patch — hence 1.1.0 and the configureConnection escape hatch.

Verification

242 tests passing on net8.0/9.0/10.0 in Release with coverage; RedisCacheProvider and RedisCacheOptions at 100% line and branch. The net48 Legacy suite passes in CI. Both behavioural tests were mutation-checked against reverted fixes.

Docs

The wiki is already updated with the connection behaviour, RedisCacheOptions and the eviction retry. It also drops two snippets that never compiled — AddCacheWeaveRedis/AddCacheWeaveDistributedCache shown taking a Func<IServiceProvider, IConnectionMultiplexer>, an overload neither package has.

Note

Directory.Build.props still declares 1.0.7, which is stale regardless (1.0.8 is released). The publish workflow takes its version from the tag, so it does not affect the package — worth a tidy-up commit at some point.

Known gap, not in this release

IKeyContextProvider fails open: when GetContextSegmentAsync returns null, DefaultCacheKeyBuilder silently omits the tenant segment, producing one cache key shared across all tenants. Security-relevant, wants its own fix and a decision on whether null should mean skip caching.

AddCacheWeaveRedis set AbortOnConnectFail = false but left BacklogPolicy at
its default, which queues commands while disconnected. With no reachable
Redis, a cache-decorated request paid a StringGetAsync on the way in and a
StringSetAsync on the way out, each blocking for the full SyncTimeout —
~10-12s of added latency per request, indefinitely, not a one-time connect
cost. CacheWeave already converts any cache failure to a miss, so the request
returned correct data with a 200 and the degradation was invisible in logs
and metrics.

Set BacklogPolicy.FailFast alongside AbortOnConnectFail: both settings express
one policy — never throw on connect, and never block a request waiting for a
connection that isn't there. FailFast is not consulted on the success path, so
behaviour against a reachable Redis is unchanged.

Evictions are the one case where a dropped command matters: a [CacheWeaveEvict]
delete lost during a failover leaves a stale entry served until it expires.
RemoveAsync and RemoveByPrefixAsync therefore wait up to 1.5s for the
multiplexer to reconnect and retry once; deleting an already-deleted key is a
no-op, so replaying a partially applied eviction is safe. Reads and writes stay
fail-fast.

Also:
- Add an Action<ConfigurationOptions> overload, as a separate overload so
  existing calls still bind. It runs after the defaults, so callers can
  override them — including back to BacklogPolicy.Default — and reach settings
  no connection string can express (TLS certs, Sentinel, Azure token auth).
- Use TryAddSingleton for IConnectionMultiplexer, matching CacheWeave.Legacy.
  A multiplexer registered after the call still wins, so the existing
  re-registration workaround keeps working.
- Apply the same fixes to CacheWeave.Legacy (net48), which had the same defect.
- Document connection behaviour and the eviction trade-off in the README.

Warrants a minor version bump: an app relying on the backlog to ride out a
brief blip now takes a cache miss instead of up to SyncTimeout of latency.
The eviction retry window, its poll interval and the SCAN page size were
hard-coded constants, reachable only from an internal constructor meant for
tests. That repeated the original complaint about this package: a policy the
library decided for you with no way to change it.

Add RedisCacheOptions, bound through IOptions<T> like SQLiteCacheOptions and
DynamoDbCacheOptions already are, and configured via a configureCache callback
on every AddCacheWeaveRedis overload — including the one taking an existing
IConnectionMultiplexer, which previously offered no provider configuration at
all. Defaults are unchanged: 1.5s window, 50ms poll, 250-key batches.

EvictionRetryWindow = TimeSpan.Zero now genuinely disables the retry. It did
not before: WaitForReconnectAsync returns true immediately when the multiplexer
is already connected, so the retry still ran regardless of the window. Caught
by the test written for the documented behaviour.

Constructor validates the options and throws ArgumentOutOfRangeException on a
negative window, a non-positive poll interval, or a scan page size below 1.

Applied to CacheWeave.Legacy too. The connection-configure parameter is renamed
configureConnection to sit alongside configureCache; both are new in this
unreleased branch, so no shipped signature changes.
The README said GPL-3.0 while LICENSE, Directory.Build.props and every
csproj declare MIT, so the packaged README contradicted the package
metadata it shipped alongside. A licence review before adoption would
stall on the discrepancy.

MIT is the real licence — the switch happened in
feature/switch-to-mit-license, which updated LICENSE and the package
metadata but missed this line.
fix: fail fast on unreachable Redis instead of queueing every command
docs: correct README licence to MIT
@teghoz teghoz self-assigned this Sep 20, 2026
@teghoz
teghoz merged commit 749b6b1 into main Sep 20, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant