You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@logging.apache.org by fr...@apache.org on 2026/09/20 19:41:13 UTC

(logging-log4net) 05/05: unlock the event cache only while fixing #323

This is an automated email from the ASF dual-hosted git repository.

FreeAndNil pushed a commit to branch Feature/323-event-and-context-state
in repository https://gitbox.apache.org/repos/asf/logging-log4net.git

commit e123b497dd06b4875d1af9ebeb81aef64b62fbf5
Author: Jan Friedrich <fr...@apache.org>
AuthorDate: Sun Sep 20 21:15:35 2026 +0200

    unlock the event cache only while fixing #323
    
    - the cache was unlocked before the fields to fix were determined
    - a redundant Fix reopened it with nothing to do
    - a reader then cached its own identity, thread name or location
    - measured: a hit after 56000 to 63000 calls, five runs of five
    
    audit da18b6fd-f039.
---
 .../3.5.0/323-logging-event-fix-cache.xml          | 13 +++++++
 src/log4net.Tests/Core/FixingTest.cs               | 41 ++++++++++++++++++++++
 src/log4net/Core/LoggingEvent.cs                   | 10 +++---
 3 files changed, 58 insertions(+), 6 deletions(-)

diff --git a/src/changelog/3.5.0/323-logging-event-fix-cache.xml b/src/changelog/3.5.0/323-logging-event-fix-cache.xml
new file mode 100644
index 00000000..45b4939f
--- /dev/null
+++ b/src/changelog/3.5.0/323-logging-event-fix-cache.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+       xmlns="https://logging.apache.org/xml/ns"
+       xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
+       type="fixed">
+  <issue id="323" link="https://github.com/apache/logging-log4net/pull/323"/>
+  <description format="asciidoc">Stop a repeated `LoggingEvent.Fix` from reopening the value cache of an
+  already fixed event. The cache was unlocked before the fields still to fix were determined, so a
+  redundant call reopened it even when there was nothing to do, and another thread reading a field
+  the event never captured stored its own thread name, identity or location in it. It is now
+  unlocked only while fields are actually being fixed (audit da18b6fd-f039, fixed by
+  @FreeAndNil)</description>
+</entry>
diff --git a/src/log4net.Tests/Core/FixingTest.cs b/src/log4net.Tests/Core/FixingTest.cs
index 840431fc..4e7c474a 100644
--- a/src/log4net.Tests/Core/FixingTest.cs
+++ b/src/log4net.Tests/Core/FixingTest.cs
@@ -19,7 +19,9 @@
 
 using System;
 using System.Linq;
+using System.Security.Principal;
 using System.Threading;
+using System.Threading.Tasks;
 
 using log4net.Core;
 using log4net.Util;
@@ -210,6 +212,45 @@ public void UserNameIsCapturedWhenItIsFixed()
     Assert.That(loggingEvent.UserName, Is.Not.EqualTo(SystemInfo.NotAvailableText));
   }
 
+  /// <summary>
+  /// A redundant Fix must not reopen the cache: a thread reading a field the event never captured
+  /// would otherwise store its own principal in it. 500000 calls, the defect hit within 63000.
+  /// </summary>
+  [Test]
+  public void ARedundantFixDoesNotLetAnotherThreadCacheItsIdentity()
+  {
+    LoggingEvent loggingEvent = CreateEvent();
+    // Partial leaves Identity out, so it stays null unless the cache reopens.
+    loggingEvent.Fix = FixFlags.Partial;
+
+    bool stop = false;
+    long spins = 0;
+    TaskCompletionSource<bool> running = new();
+    Thread reader = new(() =>
+    {
+      Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity("Intruder"), null);
+      running.TrySetResult(true);
+      while (!Volatile.Read(ref stop) && loggingEvent.Identity is null)
+      {
+        Interlocked.Increment(ref spins);
+      }
+    })
+    { IsBackground = true };
+    reader.Start();
+    Assert.That(running.Task.Wait(TimeSpan.FromSeconds(10)), Is.True);
+
+    for (int i = 0; i < 500_000; i++)
+    {
+      loggingEvent.Fix = FixFlags.Partial;
+    }
+    Volatile.Write(ref stop, true);
+    Assert.That(reader.Join(TimeSpan.FromSeconds(10)), Is.True);
+
+    // A reader that never got scheduled would pass the identity assertion for the wrong reason.
+    Assert.That(Interlocked.Read(ref spins), Is.GreaterThan(0));
+    Assert.That(loggingEvent.Identity, Is.Null);
+  }
+
   /// <summary>
   /// Creates an event in the test repository.
   /// </summary>
diff --git a/src/log4net/Core/LoggingEvent.cs b/src/log4net/Core/LoggingEvent.cs
index 4f7c852e..b5a8790d 100644
--- a/src/log4net/Core/LoggingEvent.cs
+++ b/src/log4net/Core/LoggingEvent.cs
@@ -1137,16 +1137,14 @@ public LoggingEventData GetLoggingEventData(FixFlags fixFlags)
   /// </remarks>
   protected virtual void FixVolatileData(FixFlags flags)
   {
-    // Unlock the cache so that new values can be stored
-    // This may not be ideal if we are no longer in the correct context
-    // and someone calls fix. 
-    _cacheUpdatable = true;
-
     // determine the flags that we are actually fixing
     FixFlags updateFlags = (flags ^ _fixFlags) & flags;
 
     if (updateFlags > 0)
     {
+      // Unlocked only while there is something to fix, so a redundant call cannot reopen the cache.
+      _cacheUpdatable = true;
+
       if ((updateFlags & FixFlags.Message) != 0)
       {
         // Force the message to be rendered
@@ -1448,7 +1446,7 @@ public PropertiesDictionary GetProperties()
   /// This is a separate flag to fixFlags as it allows incremental fixing and simpler
   /// changes in the caching strategy.
   /// </remarks>
-  private bool _cacheUpdatable = true;
+  private volatile bool _cacheUpdatable = true;
 
   /// <summary>
   /// The key into the Properties map for the host name value.