Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Conversation

snakefoot
Copy link
Contributor

@snakefoot snakefoot commented Oct 13, 2025

The Dictionary needs to allocate 3 objects upfront, and additional allocates 2 larger array when inserting more than 3 items:

  • Dictionary-object
  • integer-array (bonus allocation on resize)
  • value-array (bonus allocation on resize)

The linked list will reduce allocations, when only needing to cache 1-4 layout-outputs. The linked-list should still have fast lookup when less than 10 layout-outputs.

After having removed the implicit caching of layout-output on Layout.Render(), and the caching only happens on precalculate, then the linked-list should not grow aggressively with many duplicate entries.

Copy link

coderabbitai bot commented Oct 13, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

Refactors LogEventInfo’s internal layout caching from a locked Dictionary to a lock-free singly linked list using Interlocked.CompareExchange. Adds a private LayoutCacheLinkedNode type, updates Add and TryGet cache paths to use list insertion and traversal. Public API and exception behavior are unchanged.

Changes

Cohort / File(s) Summary of Changes
Layout cache refactor
src/NLog/LogEventInfo.cs
Replaced dictionary-based, lock-guarded layout cache with a lock-free singly linked list. Introduced private sealed LayoutCacheLinkedNode (Layout, Value, Next). Add operation uses Interlocked.CompareExchange to prepend nodes; lookup iterates the list. Removed dictionary and locking code; public surface unchanged.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant LogEventInfo
  participant Layout
  participant Cache as LayoutCache (internal)

  Caller->>LogEventInfo: TryGetCachedLayoutValue(layout)
  LogEventInfo->>Cache: Traverse linked nodes
  alt Match found
    Cache-->>LogEventInfo: Return cached value
    LogEventInfo-->>Caller: Value
  else Not found
    LogEventInfo-->>Caller: Miss
  end
Loading
sequenceDiagram
  autonumber
  participant Caller
  participant LogEventInfo
  participant Interlocked
  participant Cache as LayoutCache (internal)

  Caller->>LogEventInfo: AddCachedLayoutValue(layout, value)
  LogEventInfo->>Cache: Read current head
  loop Retry until success
    LogEventInfo->>Interlocked: CompareExchange(head, newNode{Next=head}, head)
    alt CAS success
      Interlocked-->>LogEventInfo: Swapped
      LogEventInfo-->>Caller: Done
    else CAS failed
      Interlocked-->>LogEventInfo: No swap
      LogEventInfo->>Cache: Reload head
    end
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

I hop through nodes, one link at a time,
No locks to block my caching climb.
A swift CAS—then off I go,
Prepending truths in a silent flow.
Carrot in paw, I chase the byte—
Cache hits twinkle, moonlit-night. 🥕✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title clearly specifies the impacted class and succinctly summarizes the core change of replacing the dictionary‐based layout cache with a linked list, making it specific and directly aligned with the main change set without extraneous detail.
Description Check ✅ Passed The pull request description clearly outlines the allocation drawbacks of the current Dictionary-based cache and explains how the linked-list implementation reduces allocations for small numbers of layouts and maintains lookup performance. It directly references the changeset’s motivation, behavior, and expected outcome. Therefore, it is relevant and descriptive with respect to the changes made.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (2)
src/NLog/LogEventInfo.cs (2)

72-72: Strengthen memory visibility on lock‑free cache reads (optional)

To guarantee readers always see fully published nodes across CPUs, consider volatile semantics for the head and volatile reads during traversal.

Option A (simplest): make the head volatile.

-        private LayoutCacheLinkedNode? _layoutCache;
+        private volatile LayoutCacheLinkedNode? _layoutCache;

Option B: use Volatile.Read on head and next in TryGet.

-            var nodeHead = _layoutCache;
+            var nodeHead = Volatile.Read(ref _layoutCache);
             while (nodeHead != null)
             {
                 if (ReferenceEquals(nodeHead.Key, layout))
                 {
                     value = nodeHead.Value;
                     return true;
                 }
-                nodeHead = nodeHead.Next;
+                nodeHead = Volatile.Read(ref nodeHead.Next);
             }

Also applies to: 637-646


814-825: Invalidate layout cache when message/parameters change (avoid stale values)

If Message/Parameters/FormatProvider mutate after precalc, cached layout outputs can become stale. Consider clearing the cache in ResetFormattedMessage.

         private void ResetFormattedMessage(bool rebuildMessageTemplateParameters)
         {
             if (_messageFormatter is null || _messageFormatter.Target is ILogMessageFormatter)
             {
                 _formattedMessage = null;
+                // Cached layout values may depend on message/parameters/format-provider
+                Volatile.Write(ref _layoutCache, null);
             }

Please confirm this matches intended lifecycle (i.e., LogEventInfo not mutated after layouts start precalculating). If mutation never happens post-precalc, this can be skipped.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5df46fa and 32222e7.

📒 Files selected for processing (1)
  • src/NLog/LogEventInfo.cs (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
src/NLog/LogEventInfo.cs (1)
src/NLog/Layouts/SimpleLayout.cs (1)
  • Layout (57-487)
🔇 Additional comments (2)
src/NLog/LogEventInfo.cs (2)

625-633: CAS prepend loop looks correct

Lock-free insertion is correct and wait-free for writers. No correctness issues spotted.


841-852: Node type is minimal and appropriate

Immutable key/value, single Next pointer—fits the prepend pattern. LGTM.

Copy link

@snakefoot snakefoot requested a review from Copilot October 13, 2025 14:44
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Replaces Dictionary-based layout caching with a linked list implementation to reduce memory allocations for small cache sizes. The change aims to optimize performance when caching 1-4 layout outputs by eliminating the upfront allocation overhead of Dictionary objects.

  • Replaced IDictionary<Layout, object?> with custom LayoutCacheLinkedNode linked list
  • Implemented lock-free concurrent operations using Interlocked.CompareExchange
  • Added new LayoutCacheLinkedNode nested class for the linked list structure

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

src/NLog/LogEventInfo.cs Show resolved Hide resolved
src/NLog/LogEventInfo.cs Show resolved Hide resolved
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

Morty Proxy This is a proxified and sanitized view of the page, visit original site.