Skip to content

Feature/fusion cache#501

Merged
NielsPilgaard merged 7 commits into
mainfrom
feature/fusion-cache
Feb 10, 2026
Merged

Feature/fusion cache#501
NielsPilgaard merged 7 commits into
mainfrom
feature/fusion-cache

Conversation

@NielsPilgaard

Copy link
Copy Markdown
Owner

No description provided.

@coderabbitai

coderabbitai Bot commented Feb 8, 2026

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Adds ZiggyCreatures.FusionCache integration and a WebApplicationBuilder extension to register it; migrates several cache usages from IMemoryCache to IFusionCache or alternate in-memory strategies; updates ProfileCache API (invalidate vs set) and related Razor call sites; adds NuGet package reference.

Changes

Cohort / File(s) Summary
FusionCache registration & project
src/web/Jordnaer/Extensions/WebApplicationBuilderExtensions.cs, src/web/Jordnaer/Program.cs, src/web/Jordnaer/Jordnaer.csproj
Adds AddFusionCache(this WebApplicationBuilder) extension (registers FusionCache with defaults) and replaces AddMemoryCache() usage in Program.cs. Adds NuGet package ZiggyCreatures.FusionCache v2.5.0.
Ad caching
src/web/Jordnaer/Features/Ad/AdProvider.cs
AdProvider now depends on IFusionCache and uses GetOrSetAsync to cache partner ads under "PartnerAds", tags entries, wires cancellation tokens, and filters cached entries by display window at read time.
Category caching
src/web/Jordnaer/Features/Category/CategoryCache.cs
Replaced IMemoryCache with IFusionCache + IDbContextFactory; uses fusionCache.GetOrSetAsync for categories with 15-minute expiration and tag-based keys.
Profile caching & API changes
src/web/Jordnaer/Features/Profile/ProfileCache.cs, src/web/Jordnaer/Pages/Profile/CompleteProfile.razor, src/web/Jordnaer/Pages/Profile/CompleteProfileInterests.razor, src/web/Jordnaer/Pages/Profile/MyProfile.razor
ProfileCache now uses IFusionCache; interface removes SetProfile and adds InvalidateProfile. Razor pages updated to call InvalidateProfile(...) instead of SetProfile(...).
Chat message caching refactor
src/web/Jordnaer/Features/Chat/ChatMessageCache.cs
Replaces IMemoryCache usage with a circuit-scoped in-memory approach using ConcurrentDictionary<Guid, ImmutableList<ChatMessageDto>> plus per-chat inflight load tracking and explicit Load/Refresh semantics to avoid concurrent initial loads.
TopBar JS interop change
src/web/Jordnaer/Pages/Shared/TopBar.razor
Replaces eval-based JS interop calls with JsRuntime.GetValueAsync / JsRuntime.SetValueAsync for document.title.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Client
participant AdProvider
participant FusionCache
participant Database
participant Logger

Client->>AdProvider: Request ads
AdProvider->>FusionCache: GetOrSetAsync("PartnerAds", cancellationToken)
alt cache miss
    FusionCache->>Database: Query Partner entities
    Database-->>FusionCache: Partner rows
    FusionCache-->>AdProvider: Cached partner entries
else cache hit
    FusionCache-->>AdProvider: Cached partner entries
end
AdProvider->>AdProvider: Filter by display window (utcNow)
AdProvider->>Logger: Log on error (if any)
AdProvider-->>Client: Return ads

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

🚥 Pre-merge checks | ❌ 3
❌ Failed checks (1 warning, 2 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title 'Feature/fusion cache' is vague and generic, using a branch-naming convention rather than describing the actual changes made in the pull request. Use a more descriptive title that clearly summarizes the main change, such as 'Replace memory cache with FusionCache across application' or 'Migrate caching implementation to FusionCache'.
Description check ❓ Inconclusive No pull request description was provided by the author, making it impossible to assess whether the description relates to the changeset. Add a pull request description explaining the purpose of migrating to FusionCache, any benefits or motivations, and a summary of which components were affected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/fusion-cache

Tip

We've launched Issue Planner and it is currently in beta. Please try it out and share your feedback on Discord!


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Fix all issues with AI agents
In `@src/web/Jordnaer/Features/Ad/AdProvider.cs`:
- Around line 32-56: The cached result is built using DateTime.UtcNow inside the
fusionCache factory (fusionCache.GetOrSetAsync) so time-windowed partners can be
stale for the full cache TTL; fix by either (A) making the cache value a
superset and applying the active-window filter at read time (i.e., after
partnerAds is returned, filter by DateTime.UtcNow vs
DisplayStartUtc/DisplayEndUtc) or (B) compute the nearest upcoming
DisplayStartUtc/DisplayEndUtc from the DB inside the factory and set the cache
expiration to that delta so the factory refreshes when visibility changes;
update the code paths around partnerAds and the factory passed to
fusionCache.GetOrSetAsync and ensure comparisons use DateTime.UtcNow and the
Partner.DisplayStartUtc/DisplayEndUtc properties.

In `@src/web/Jordnaer/Features/Chat/ChatMessageCache.cs`:
- Around line 25-40: The cache-hit path in ChatMessageCache is racy: multiple
concurrent callers can read the same cachedMessages.Count and both fetch and
AddRange identical new messages; fix by serializing per-chatId incremental loads
in the method that contains the shown block (the cache-hit path) — either add a
ConcurrentDictionary<chatId, SemaphoreSlim> or reuse the existing _inflightLoads
pattern to ensure only one fetch-and-merge runs per chatId at a time, await that
lock before calling chatService.GetChatMessagesAsync, then re-read the current
cached list and compute the real delta (e.g., snapshot currentCount and append
only messages with index >= currentCount) before calling _cache.AddOrUpdate or
replacing the entry to avoid duplicates.

In `@src/web/Jordnaer/Jordnaer.csproj`:
- Line 59: Update the ZiggyCreatures.FusionCache PackageReference from
Version="2.0.0" to Version="2.5.0" in the project file entry that contains
PackageReference Include="ZiggyCreatures.FusionCache"; after changing the
version, run dotnet restore (or your CI restore step) and execute tests/build to
verify compatibility and address any API changes introduced between FusionCache
2.0.0 and 2.5.0.
🧹 Nitpick comments (2)
src/web/Jordnaer/Features/Chat/ChatMessageCache.cs (1)

56-69: Loading all messages (take: int.MaxValue) could be expensive for long-lived chats.

Both the initial load and the incremental fetch use int.MaxValue as the take parameter. For chats with large message histories, this will load the entire history into memory per circuit. Consider paginating or capping the initial load.

src/web/Jordnaer/Features/Profile/ProfileCache.cs (1)

22-23: UserTag as a computed property recalculates on every access.

This is fine since currentUser.Id is stable within a circuit-scoped lifetime, but a minor consideration: if RemoveByTag or tag assignment is called frequently, you're allocating a new string each time.

Consider caching the tag string
 private const string Tag = "profile";
-private string UserTag => $"{Tag}:{currentUser.Id}";
+private string? _userTag;
+private string UserTag => _userTag ??= $"{Tag}:{currentUser.Id}";

Comment thread src/web/Jordnaer/Features/Ad/AdProvider.cs Outdated
Comment thread src/web/Jordnaer/Features/Chat/ChatMessageCache.cs Outdated
Comment thread src/web/Jordnaer/Jordnaer.csproj Outdated
@github-project-automation github-project-automation Bot moved this from Todo to In Progress in Jordnaer Community Website Feb 8, 2026

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/web/Jordnaer/Features/Chat/ChatMessageCache.cs`:
- Around line 56-79: The method RefreshMessagesAsync (and similarly
LoadMessagesAsync) currently discards the Error<string> branch from
chatService.GetChatMessagesAsync, causing silent failures and stale/empty
returns; inject an ILogger<ChatMessageCache> into the ChatMessageCache class
and, when calling GetChatMessagesAsync inside RefreshMessagesAsync (and
LoadMessagesAsync), handle the error variant by logging the error with context
(userId, chatId) and either rethrowing or returning an appropriate failure
signal to the caller instead of swallowing it; update the Match(...) usages that
currently use "_" to capture the error and replace them with logic that logs via
the injected logger and then decides whether to propagate (throw) or surface an
error result to the caller.
🧹 Nitpick comments (2)
src/web/Jordnaer/Features/Ad/AdProvider.cs (1)

50-72: Consider extracting the cache key to a constant for consistency with the Tag pattern.

The code defines Tag = "ads" as a constant (line 36) for the cache invalidation tag, but uses the inline string "PartnerAds" for the cache key (line 51). For consistency and to reduce the risk of accidental typos, extract the cache key to a named constant:

Suggested change
+	private const string PartnerAdsCacheKey = "PartnerAds";
 	private const string Tag = "ads";

Then update line 51:

- cachedPartners = await fusionCache.GetOrSetAsync<List<PartnerAdEntry>>(
-     "PartnerAds",
+ cachedPartners = await fusionCache.GetOrSetAsync<List<PartnerAdEntry>>(
+     PartnerAdsCacheKey,

FusionCache defaults are properly configured during service registration (WebApplicationBuilderExtensions.cs) with a 10-minute default duration, fail-safe enabled, and eager refresh at 90%. No explicit options are needed here.

src/web/Jordnaer/Features/Chat/ChatMessageCache.cs (1)

81-94: Loading unbounded messages with int.MaxValue could be a concern for large chats.

Both LoadMessagesAsync (line 86) and RefreshMessagesAsync (line 61) use take: int.MaxValue, which fetches the entire chat history into memory. For a circuit-scoped cache this may be acceptable today, but worth keeping in mind if chat histories grow large.

Comment thread src/web/Jordnaer/Features/Chat/ChatMessageCache.cs
@NielsPilgaard NielsPilgaard merged commit aff3130 into main Feb 10, 2026
3 of 4 checks passed
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Jordnaer Community Website Feb 10, 2026
@NielsPilgaard NielsPilgaard deleted the feature/fusion-cache branch February 10, 2026 14:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Development

Successfully merging this pull request may close these issues.

1 participant