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

vy pushed a commit to branch 2.x
in repository https://gitbox.apache.org/repos/asf/logging-log4j2.git


The following commit(s) were added to refs/heads/2.x by this push:
     new a4dfb37e34 Minimize lock usage in `InternalLoggerRegistry` (#3418)
a4dfb37e34 is described below

commit a4dfb37e34acf530833748bbbee2b8be9e5de9f4
Author: Piotr P. Karwasz <[email protected]>
AuthorDate: Thu Feb 6 15:18:37 2025 +0100

    Minimize lock usage in `InternalLoggerRegistry` (#3418)
    
    Co-authored-by: Volkan Yazıcı <[email protected]>
---
 .../core/util/internal/InternalLoggerRegistry.java | 96 +++++++++++++---------
 src/changelog/.2.x.x/3399_logger_registry.xml      | 10 +++
 2 files changed, 65 insertions(+), 41 deletions(-)

diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistry.java
 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistry.java
index 59df674961..eff1d46b77 100644
--- 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistry.java
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistry.java
@@ -22,7 +22,6 @@ import java.lang.ref.WeakReference;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Optional;
 import java.util.WeakHashMap;
 import java.util.concurrent.locks.Lock;
 import java.util.concurrent.locks.ReadWriteLock;
@@ -31,17 +30,20 @@ import java.util.function.BiFunction;
 import java.util.stream.Collectors;
 import java.util.stream.Stream;
 import org.apache.logging.log4j.core.Logger;
+import org.apache.logging.log4j.core.LoggerContext;
 import org.apache.logging.log4j.message.MessageFactory;
 import org.apache.logging.log4j.status.StatusLogger;
 import org.jspecify.annotations.NullMarked;
 import org.jspecify.annotations.Nullable;
 
 /**
- * Convenience class used by {@link 
org.apache.logging.log4j.core.LoggerContext}
+ * A registry of {@link Logger}s namespaced by name and message factory.
+ * This class is internally used by {@link LoggerContext}.
  * <p>
- *   We don't use {@link org.apache.logging.log4j.spi.LoggerRegistry} from the 
Log4j API to keep Log4j Core independent
- *   from the version of the Log4j API at runtime.
+ * We don't use {@linkplain org.apache.logging.log4j.spi.LoggerRegistry the 
registry from Log4j API} to keep Log4j Core independent from the version of 
Log4j API at runtime.
+ * This also allows Log4j Core to evolve independently from Log4j API.
  * </p>
+ *
  * @since 2.25.0
  */
 @NullMarked
@@ -70,11 +72,15 @@ public final class InternalLoggerRegistry {
         requireNonNull(messageFactory, "messageFactory");
         readLock.lock();
         try {
-            return Optional.of(loggerRefByNameByMessageFactory)
-                    .map(loggerRefByNameByMessageFactory -> 
loggerRefByNameByMessageFactory.get(messageFactory))
-                    .map(loggerRefByName -> loggerRefByName.get(name))
-                    .map(WeakReference::get)
-                    .orElse(null);
+            final Map<String, WeakReference<Logger>> loggerRefByName =
+                    loggerRefByNameByMessageFactory.get(messageFactory);
+            if (loggerRefByName != null) {
+                final WeakReference<Logger> loggerRef = 
loggerRefByName.get(name);
+                if (loggerRef != null) {
+                    return loggerRef.get();
+                }
+            }
+            return null;
         } finally {
             readLock.unlock();
         }
@@ -147,43 +153,51 @@ public final class InternalLoggerRegistry {
             return logger;
         }
 
+        // Intentionally moving the logger creation outside the write lock, 
because:
+        //
+        // - Logger instantiation is expensive (causes contention on the 
write-lock)
+        //
+        // - User code might have circular code paths, though through 
different threads.
+        //   Consider `T1[ILR:computeIfAbsent] -> ... -> T1[Logger::new] -> 
... -> T2[ILR::computeIfAbsent]`.
+        //   Hence, having logger instantiation while holding a write lock 
might cause deadlocks:
+        //   https://github.com/apache/logging-log4j2/issues/3252
+        //   https://github.com/apache/logging-log4j2/issues/3399
+        //
+        // - Creating loggers without a lock, allows multiple threads to 
create loggers in parallel, which also improves
+        // performance.
+        //
+        // Since all loggers with the same parameters are equivalent, we can 
safely return the logger from the
+        // thread that finishes first.
+        Logger newLogger = loggerSupplier.apply(name, messageFactory);
+
+        // Report name and message factory mismatch if there are any
+        final String loggerName = newLogger.getName();
+        final MessageFactory loggerMessageFactory = 
newLogger.getMessageFactory();
+        if (!loggerName.equals(name) || 
!loggerMessageFactory.equals(messageFactory)) {
+            StatusLogger.getLogger()
+                    .error(
+                            "Newly registered logger with name `{}` and 
message factory `{}`, is requested to be associated with a different name `{}` 
or message factory `{}`.\n"
+                                    + "Effectively the message factory of the 
logger will be used and the other one will be ignored.\n"
+                                    + "This generally hints a problem at the 
logger context implementation.\n"
+                                    + "Please report this using the Log4j 
project issue tracker.",
+                            loggerName,
+                            loggerMessageFactory,
+                            name,
+                            messageFactory);
+        }
+
         // Write lock slow path: Insert the logger
         writeLock.lock();
         try {
-
-            // See if the logger is created by another thread in the meantime
-            final Map<String, WeakReference<Logger>> loggerRefByName =
-                    
loggerRefByNameByMessageFactory.computeIfAbsent(messageFactory, ignored -> new 
HashMap<>());
-            WeakReference<Logger> loggerRef = loggerRefByName.get(name);
-            if (loggerRef != null && (logger = loggerRef.get()) != null) {
-                return logger;
+            Map<String, WeakReference<Logger>> loggerRefByName = 
loggerRefByNameByMessageFactory.get(messageFactory);
+            // noinspection Java8MapApi (avoid the allocation of lambda passed 
to `Map::computeIfAbsent`)
+            if (loggerRefByName == null) {
+                loggerRefByNameByMessageFactory.put(messageFactory, 
loggerRefByName = new HashMap<>());
             }
-
-            // Create the logger
-            logger = loggerSupplier.apply(name, messageFactory);
-
-            // Report name and message factory mismatch if there are any
-            final String loggerName = logger.getName();
-            final MessageFactory loggerMessageFactory = 
logger.getMessageFactory();
-            if (!loggerMessageFactory.equals(messageFactory)) {
-                StatusLogger.getLogger()
-                        .error(
-                                "Newly registered logger with name `{}` and 
message factory `{}`, is requested to be associated with a different name `{}` 
or message factory `{}`.\n"
-                                        + "Effectively the message factory of 
the logger will be used and the other one will be ignored.\n"
-                                        + "This generally hints a problem at 
the logger context implementation.\n"
-                                        + "Please report this using the Log4j 
project issue tracker.",
-                                loggerName,
-                                loggerMessageFactory,
-                                name,
-                                messageFactory);
-                // Register logger under alternative keys
-                loggerRefByNameByMessageFactory
-                        .computeIfAbsent(loggerMessageFactory, ignored -> new 
HashMap<>())
-                        .putIfAbsent(loggerName, new WeakReference<>(logger));
+            final WeakReference<Logger> loggerRef = loggerRefByName.get(name);
+            if (loggerRef == null || (logger = loggerRef.get()) == null) {
+                loggerRefByName.put(name, new WeakReference<>(logger = 
newLogger));
             }
-
-            // Insert the logger
-            loggerRefByName.put(name, new WeakReference<>(logger));
             return logger;
         } finally {
             writeLock.unlock();
diff --git a/src/changelog/.2.x.x/3399_logger_registry.xml 
b/src/changelog/.2.x.x/3399_logger_registry.xml
new file mode 100644
index 0000000000..0eb56f1a64
--- /dev/null
+++ b/src/changelog/.2.x.x/3399_logger_registry.xml
@@ -0,0 +1,10 @@
+<?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="3399" 
link="https://github.com/apache/logging-log4j2/issues/3399"/>
+  <description format="asciidoc">
+    Minimize lock usage in `InternalLoggerRegistry`.
+  </description>
+</entry>

Reply via email to