Skip to content

feat: v2.0.0 — stateless allocation-conscious rewrite (net8/9/10) - #3

Merged
slipalison merged 16 commits into
masterfrom
feature/v2-modernization
Jul 19, 2026
Merged

slipalison merged 16 commits into
masterfrom
feature/v2-modernization

Conversation

@slipalison

Copy link
Copy Markdown
Owner

AspNet.Cache v2.0.0

Modernization of the CacheAttribute response-cache filter. Full plan in PLANO-MODERNIZACAO.md.

Fixes

  • Cross-request race condition (data leak). MVC shares the attribute instance across concurrent requests; v1 kept per-request state in instance fields, so one user's response could be served to another. v2 is stateless by construction (regression-tested with 500 concurrent requests).
  • Non-compiling HEAD (missing usings), sync-over-async, max-age Seconds→TotalSeconds, and Headers.Clear() wiping middleware headers.

Highlights

  • Stateless async filter on IDistributedCache; IBufferDistributedCache fast path (net9+) with automatic fallback.
  • Allocation-conscious: span/ArrayPool key generation, SHA256.HashData, pooled buffers, direct BodyWriter writes on hits. Measured: key-gen 344 B/op (v1 ~1784), buffered hit 104 B/op, 0 Gen1/Gen2 across all paths.
  • LOH-safe by default (MaxPayloadBytes = 84_000); fail-open on provider errors.
  • Multi-targets net8.0 / net9.0 / net10.0.

Validation (local)

  • Build -warnaserror: 0 warnings across all TFMs.
  • Tests: 34 passing, 96% line / 92% branch coverage.
  • Allocation gate (build/check-allocation-budgets.ps1): all budgets respected, Gen2=0.
  • Soak (sample under load): Gen2=0, LOH flat ~96KB, working set stable.

CI / Release

  • .github/workflows/ci.yml: build + test (opencover) + SonarQube + allocation gate.
  • .github/workflows/release.yml: tag v* → pack (SourceLink + snupkg) → NuGet.org + GitHub Release.

Required before publish (owner)

  • Repo secrets/vars: SONAR_TOKEN, SONAR_PROJECT_KEY, SONAR_ORGANIZATION, NUGET_API_KEY.
  • Merge, then push tag v2.0.0 to trigger the NuGet release.

https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU

…ffers

Fixes cross-request race condition (shared attribute instance held per-request
state), the non-compiling HEAD, sync-over-async, the max-age Seconds/TotalSeconds
bug, and Headers.Clear() wiping middleware headers. Adds IBufferDistributedCache
fast path, LOH-safe MaxPayloadBytes default, and fail-open cache error handling.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Removes flaky timing-based v1 tests. Adds unit coverage for the stateless
filter, key builder and pooled buffer writer; a 500-request concurrency
regression for the fixed cross-request race; fail-open, cancellation, oversized
payload and buffered-provider paths; and TestServer integration tests. Line
coverage 96%, branch 92%. Test project targets net9.0 to match an installed
runtime; the library under test stays net8.0.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
BenchmarkDotNet MemoryDiagnoser over key generation, buffered/legacy hits and
buffered store. budgets.json + check-allocation-budgets.ps1 fail the build on
any Gen1/Gen2 collection or bytes/op regression. Measured (net9 host, short job):
KeyGen 344 B/op, BufferedHit 104 B/op, LegacyHit 144 B/op, Store 864 B/op, all
Gen0-only.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Minimal ASP.NET Core app targeting the cache attribute, used as the load-test
target for the soak validation and as a runnable README example.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Drops the .NET Core 2.2 / codecov workflow. Adds build+test with opencover
coverage feeding SonarQube (SonarCloud by default, self-hosted supported),
gated on the SONAR_TOKEN secret so fork PRs stay green. Separate job runs the
benchmarks and enforces the allocation budgets.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Pushing a v* tag builds, tests, packs (deterministic + SourceLink + snupkg),
pushes to NuGet.org and creates a GitHub Release. Version is derived from the
tag, replacing the abandoned third-party publish action.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Bilingual (EN/PT-BR) README with badges, quick start, attribute settings,
global options, key format, measured performance numbers, and a v1→v2 migration
table. Honest framing of the 1M rps premise (scale-out; the library's job is
zero added GC pressure).

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
coverage.opencover.xml is a build artifact; it was accidentally committed with
the test rewrite. Remove from tracking and ignore coverage/*.opencover.xml.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Adds the UTF-8 BOM the .editorconfig requires for C# files. No code changes.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Ships lib/net8.0, lib/net9.0 and lib/net10.0 in the package so consumers on
each LTS/STS get a matching build. IBufferDistributedCache comes from the 9.0
package on net8.0 and from the shared framework on net9.0+. CI/release install
the 10.0.x SDK; README documents the target set.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
… secret env)

- CacheAttribute.TryServeBufferedAsync: dispose the pooled buffer once in a
  finally guarded by a handed-off flag (S3966 redundant dispose).
- CacheKeyBuilder: hoist the rented-array assignment out of the span
  initializer (S1121 assignment in sub-expression), keeping stackalloc ref-safe.
- Pin all GitHub Actions to full commit SHAs (S7637).
- Pass NUGET_API_KEY via env instead of expanding the secret in the run block
  (S7636).

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
…serror

The library keeps TreatWarningsAsErrors via its csproj; not forcing it across
the solution stops SonarAnalyzer smells in the Sonar-excluded sample/benchmark
projects from failing the analysis build. Strict -warnaserror still runs in the
allocation-gate job and locally.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
- CacheKeyBuilder: use var for the rented array and destination span (IDE0007),
  consistent with the rest of the method.
- PooledArrayBufferWriter: expose the write cursor as an auto-property (IDE0032).
- Suppress CA1822 on the integration-test controller action with a justification
  (MVC action methods must be instance methods).

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
Replace the NUGET_API_KEY secret with NuGet Trusted Publishing: request an
id-token, exchange it through NuGet/login for a short-lived key, then push.
No long-lived API key to store or rotate. Action pinned to its commit SHA.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
The NuGet Trusted Publisher policy pins Environment=production, so the OIDC
token must carry that claim. Declare the environment on the release job.

Claude-Session: https://claude.ai/code/session_01V1QeCENrvWQ1u6zN1qzBAU
@sonarqubecloud

Copy link
Copy Markdown

@slipalison
slipalison merged commit 9d9ea7f into master Jul 19, 2026
3 checks passed
@slipalison
slipalison deleted the feature/v2-modernization branch July 19, 2026 21:13
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