C# .NET 8.0 library implementing read-only, range-based caches with decision-driven background maintenance. Three packages:
Intervals.NET.Caching— shared foundation: interfaces, DTOs, layered cache infrastructure, concurrency primitives (non-packable)Intervals.NET.Caching.SlidingWindow— sliding window cache (sequential-access optimized, single contiguous window, prefetch)Intervals.NET.Caching.VisitedPlaces— visited places cache (random-access optimized, non-contiguous segments, eviction, TTL)
Prerequisites: .NET SDK 8.0 (see global.json).
dotnet build Intervals.NET.Caching.sln
dotnet build Intervals.NET.Caching.sln --configuration Release
# All tests
dotnet test Intervals.NET.Caching.sln --configuration Release
# SlidingWindow tests
dotnet test tests/Intervals.NET.Caching.SlidingWindow.Unit.Tests/Intervals.NET.Caching.SlidingWindow.Unit.Tests.csproj
dotnet test tests/Intervals.NET.Caching.SlidingWindow.Integration.Tests/Intervals.NET.Caching.SlidingWindow.Integration.Tests.csproj
dotnet test tests/Intervals.NET.Caching.SlidingWindow.Invariants.Tests/Intervals.NET.Caching.SlidingWindow.Invariants.Tests.csproj
# VisitedPlaces tests
dotnet test tests/Intervals.NET.Caching.VisitedPlaces.Unit.Tests/Intervals.NET.Caching.VisitedPlaces.Unit.Tests.csproj
dotnet test tests/Intervals.NET.Caching.VisitedPlaces.Integration.Tests/Intervals.NET.Caching.VisitedPlaces.Integration.Tests.csproj
dotnet test tests/Intervals.NET.Caching.VisitedPlaces.Invariants.Tests/Intervals.NET.Caching.VisitedPlaces.Invariants.Tests.csproj
# Single test
dotnet test --filter "FullyQualifiedName=Full.Test.Name"
dotnet test --filter "FullyQualifiedName~PartialMatch"
# Local CI validation
.github/test-ci-locally.ps1Commits are made exclusively by a human. Agents must NOT create git commits. Present a summary of all changes for human review.
- Format: Conventional Commits, passive voice, multi-type allowed (e.g.,
feat: X; test: Y; docs: Z) - Documentation follows code: every implementation MUST be finalized by updating relevant documentation (see Pre-Change Reference Guide below)
Standard C# conventions apply. Below are project-specific rules only:
- Always use braces for all control flow (
if,else,for,foreach,while,do,using), even single-line bodies - File-scoped namespace declarations. Internal classes:
internal sealed - Generic type parameters:
TRange(boundary),TData(cached data),TDomain(range domain) — use consistently - Async methods always end with
Async. UseValueTask<T>for hot paths if not async possible,Taskfor infrequent operations - Prefer
recordtypes andinitproperties for configuration/DTOs. Usesealedfor non-inheritable classes - XML documentation required on all public APIs. Internal components should reference invariant IDs (e.g.,
SWC.A.1,VPC.B.1) - XML doc style: see "XML Documentation Policy" section below for the mandatory slim format
- Error handling: User Path exceptions propagate to caller. Background Path exceptions are swallowed and reported via
ICacheDiagnostics— background exceptions must NEVER crash the application - Tests: xUnit with
[Fact]/[Theory]. Naming:MethodName_Scenario_ExpectedBehavior. Arrange-Act-Assert pattern with#regiongrouping. UseRecord.Exception/Record.ExceptionAsyncto separate ACT from ASSERT WaitForIdleAsyncsemantics: completes when the system was idle at some point, not "is idle now". New activity may start immediately after completion. Guarantees degrade under parallel access (see invariant S.H.3)
All three packages follow the same internal layer convention: Public/ (API surface) → Core/ (business logic, internal) → Infrastructure/ (storage, concurrency, internal).
Core package (Intervals.NET.Caching) is non-packable (IsPackable=false). Its types compile into SWC/VPC assemblies via ProjectReference with PrivateAssets="all". Internal types shared via InternalsVisibleTo.
Namespace pattern: Intervals.NET.Caching.{Package}.{Layer}.{Subsystem} — e.g., Intervals.NET.Caching.SlidingWindow.Core.Rebalance.Decision, Intervals.NET.Caching.VisitedPlaces.Core.Eviction.
Test projects (Unit, Integration, Invariants for each package) plus shared test infrastructure: tests/*.Tests.Infrastructure/. Reuse existing test helpers and builders rather than reinventing.
CI: Two GitHub Actions workflows, one per publishable package (.github/workflows/intervals-net-caching-swc.yml, .github/workflows/intervals-net-caching-vpc.yml). Both validate WebAssembly compilation (net8.0-browser target).
Read docs/shared/invariants.md, docs/sliding-window/invariants.md, and docs/visited-places/invariants.md for full specifications. Below are the invariants most likely to be violated by code changes.
SlidingWindow (SWC):
- Single-writer (SWC.A.1): only
RebalanceExecutormutates cache state; User Path is strictly read-only - Cache contiguity (SWC.A.12b):
CacheDatamust always be a single contiguous range — no gaps, no partial materialization - Atomic state updates (SWC.B.2):
CacheDataandCurrentCacheRangemust change atomically — no intermediate inconsistent states - Intent = signal, not command (SWC.C.8): publishing an intent does NOT guarantee rebalance; the Decision Engine may skip it at any of 5 stages
- Multi-stage decision validation (SWC.D.5): rebalance executes only if ALL stages confirm necessity. Stage 2 MUST evaluate against the pending execution's
DesiredNoRebalanceRange, not the current cache's
VisitedPlaces (VPC):
- Single-writer (VPC.A.1): only the Background Storage Loop mutates segment collection; User Path is strictly read-only
- Strict FIFO event ordering (VPC.B.1): every
CacheNormalizationRequestprocessed in order — no supersession, no discards. Violating corrupts eviction metadata (e.g., LRU timestamps) - Segment non-overlap (VPC.C.3): no two segments share any discrete domain point —
End[i] < Start[i+1]strictly - Segments never merge (VPC.C.2): even adjacent segments remain separate forever
- Just-stored segment immunity (VPC.E.3): segment stored in the current background step is excluded from eviction candidates. Without this, infinite fetch-store-evict loops occur under LRU
- Idempotent removal (VPC.T.1):
ISegmentStorage.TryRemove()checkssegment.IsRemovedbefore callingsegment.MarkAsRemoved()(Volatile.Write) — only the first caller (TTL normalization or eviction) performs storage removal and decrements the count
Shared:
- Activity counter ordering (S.H.1/S.H.2): increment BEFORE work is made visible; decrement in
finallyblocks ALWAYS. Violating causesWaitForIdleAsyncto hang or return prematurely - Disposal (S.J): post-disposal guard on public methods, idempotent disposal, cooperative cancellation of background ops
- Bounded range requests (S.R): requested ranges must be finite on both ends; unbounded ranges throw
ArgumentException
These packages share interfaces but have fundamentally different internals. Do NOT apply patterns from one to the other.
| Aspect | SlidingWindow | VisitedPlaces |
|---|---|---|
| Event processing | Latest-intent-wins (supersession via Interlocked.Exchange) |
Strict FIFO (every event processed in order) |
| Cache structure | Single contiguous window; contiguity mandatory | Non-contiguous segment collection; gaps valid |
| Background I/O | RebalanceExecutor calls IDataSource.FetchAsync |
Background Path does NO I/O; data delivered via User Path events |
| Prefetch | Geometry-based expansion (LeftCacheSize/RightCacheSize) |
Strictly demand-driven; never prefetches |
| Cancellation | Rebalance execution is cancellable via CTS | Background events are NOT cancellable |
| Consistency modes | Eventual, Hybrid, Strong | Eventual, Strong (no Hybrid) |
| Execution contexts | User Thread + Intent Loop + Execution Loop | User Thread + Background Storage Loop |
These changes appear reasonable but silently violate invariants. Functional tests typically still pass.
- Adding writes in User Path (either package): introduces write-write races with Background Path. User Path must be strictly read-only
- Changing VPC event processing to supersession: corrupts eviction metadata (LRU timestamps for skipped events are lost)
- Merging VPC segments: resets eviction metadata, breaks
FindIntersectingbinary search ordering - Moving activity counter increment after publish:
WaitForIdleAsyncreturns prematurely (nanosecond race window, nearly impossible to reproduce) - Removing
finallyfromDecrementActivitycall sites: any exception leaves counter permanently incremented;WaitForIdleAsynchangs forever - Making SWC
Rematerialize()non-atomic (split data + range update): User Path reads see inconsistent data/range — silent data corruption - Removing just-stored segment immunity: causes infinite fetch-store-evict loops under LRU (just-stored segment has earliest
LastAccessedAt) - Adding
IDataSourcecalls to VPC Background Path: blocks FIFO event processing, delays metadata updates, no cancellation infrastructure for I/O - Publishing intents from SWC Rebalance Execution: creates positive feedback loop — system never reaches idle, disposal hangs
- Removing the
IsRemovedcheck fromSegmentStorageBase.TryRemove(): both TTL normalization and eviction proceed to callMarkAsRemoved()and decrement the policy aggregate count, corrupting eviction pressure calculations - Swallowing exceptions in User Path: user receives empty/partial data with no failure signal;
CacheInteractionclassification becomes misleading - Adding locks around SWC
CacheStatereads: creates lock contention between User Path and Rebalance — violates "user requests never block on rebalance"
Before modifying a subsystem, read the relevant docs. After completing changes, update the same docs plus any listed under "Also Update."
| Modification Area | Read Before Changing | Also Update After |
|---|---|---|
| SWC rebalance / decision logic | docs/sliding-window/invariants.md, docs/sliding-window/architecture.md |
docs/sliding-window/state-machine.md, docs/sliding-window/scenarios.md |
| SWC storage strategies | docs/sliding-window/storage-strategies.md |
same |
| SWC components | docs/sliding-window/components/overview.md, relevant component doc |
docs/sliding-window/actors.md |
| VPC eviction (policy/selector) | docs/visited-places/eviction.md, docs/visited-places/invariants.md (VPC.E group) |
same |
| VPC TTL | docs/visited-places/invariants.md (VPC.T group), docs/visited-places/architecture.md |
same |
| VPC background processing | docs/visited-places/architecture.md, docs/visited-places/invariants.md (VPC.B group) |
docs/visited-places/scenarios.md |
| VPC storage strategies | docs/visited-places/storage-strategies.md |
same |
| VPC components | docs/visited-places/components/overview.md |
docs/visited-places/actors.md |
IDataSource contract |
docs/shared/boundary-handling.md |
same |
AsyncActivityCounter |
docs/shared/invariants.md (S.H group), docs/shared/architecture.md |
same |
| Layered cache | docs/shared/glossary.md, README.md |
same |
| Public API changes | README.md |
README.md |
| Diagnostics events | docs/shared/diagnostics.md or package-specific diagnostics doc |
same |
| New terms or semantic changes | docs/shared/glossary.md or package-specific glossary |
same |
Canonical terminology: see docs/shared/glossary.md, docs/sliding-window/glossary.md, docs/visited-places/glossary.md. Each includes a "Common Misconceptions" section.
XML docs are slim by design. Architecture, rationale, examples, and concurrency rules belong in docs/ — never in XML. Model files: RebalanceDecisionEngine.cs, IWorkScheduler.cs, EvictionEngine.cs, CacheNormalizationRequest.cs.
| Element | Rule |
|---|---|
<summary> |
1-2 sentences. Classes/interfaces end with See docs/{path} for design details. Use single-line form when it fits. |
<param> |
Keep where meaning is non-obvious from type + name. Omit when self-evident. |
<returns> |
Keep only for non-obvious semantics. Omit for void and self-evident returns. |
<typeparam> |
On top-level declarations only. Never repeat across overloads — omit or use <inheritdoc/>. |
<inheritdoc/> |
Bare /// <inheritdoc/> on implementations. May add a short <remarks> for invariant notes only. |
<remarks> |
Only for short invariant notes (e.g., Enforces VPC.C.3). Never multi-paragraph; never <para>, <list>, <code>, or <example>. |
| Constructors | Omit or minimal: Initializes a new <see cref="TypeName{...}"/>. |
| Private fields | Use // inline comments, not ///. |
| Invariant IDs | Keep inline (Enforces VPC.C.3, See invariant S.H.1) — essential for code review. |
When writing or modifying code: implement first → update the relevant docs/ markdown → add a slim XML summary with See docs/{path} and invariant IDs as needed. Never grow <remarks> for design decisions.