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

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


The following commit(s) were added to refs/heads/main by this push:
     new 4dee591823 [main] Port `InternalLoggerRegistry` from `2.x` (#3418 and 
#3681 ports) (#4157)
4dee591823 is described below

commit 4dee591823e92007665ba9dd6b487b98536c131a
Author: Vasily Pelikh <[email protected]>
AuthorDate: Mon Jul 20 18:58:42 2026 +0300

    [main] Port `InternalLoggerRegistry` from `2.x` (#3418 and #3681 ports) 
(#4157)
    
    * Port `InternalLoggerRegistry` from `2.x`
    
    Minimizes lock usage by moving logger instantiation outside the write lock 
(PR #3418). Adds stale entry detection via ReferenceQueue (PR #3681).
    
    Signed-off-by: Vasily Pelikh <[email protected]>
    
    * Apply spotless formatting to InternalLoggerRegistryTest
    
    * Fix InternalLoggerRegistry null-MessageFactory lookup mismatch in Log4j 3
    
    InternalLoggerRegistry.getLogger(name, null) normalized null to a hardcoded 
ParameterizedMessageFactory.INSTANCE, but LoggerContext stored loggers under 
its DI-injected defaultMessageFactory (ReusableMessageFactory in Log4j 3). This 
store-key/get-key mismatch caused testGetLoggerRetrievesExistingLogger, 
testHasLoggerReturnsCorrectStatus, and testExpungeStaleWeakReferenceEntries to 
all fail.
    
    - InternalLoggerRegistry: constructor-inject defaultMessageFactory (final 
field), resolve null→defaultMessageFactory instead of hardcoded INSTANCE.
    - LoggerContext: construct InternalLoggerRegistry with its own 
defaultMessageFactory after DI resolution.
    - Test: iterate all message-factory buckets (.values()) instead of assuming 
ParameterizedMessageFactory.INSTANCE.
    
    * InternalLoggerRegistry: add Javadoc, align with 2.x naming, fix 
return-in-finally, add expungeStaleEntries
    
    ---------
    
    Signed-off-by: Vasily Pelikh <[email protected]>
---
 .../util/internal/InternalLoggerRegistryTest.java  | 145 +++++++++
 .../apache/logging/log4j/core/LoggerContext.java   |  16 +-
 .../core/util/internal/InternalLoggerRegistry.java | 353 +++++++++++++++++++++
 3 files changed, 504 insertions(+), 10 deletions(-)

diff --git 
a/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistryTest.java
 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistryTest.java
new file mode 100644
index 0000000000..28ca8863ed
--- /dev/null
+++ 
b/log4j-core-test/src/test/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistryTest.java
@@ -0,0 +1,145 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.util.internal;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+import static java.util.concurrent.TimeUnit.SECONDS;
+import static org.awaitility.Awaitility.await;
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertFalse;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import static org.junit.jupiter.api.Assertions.assertNull;
+import static org.junit.jupiter.api.Assertions.assertSame;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+import java.lang.ref.WeakReference;
+import java.lang.reflect.Field;
+import java.net.URI;
+import java.util.Map;
+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.message.SimpleMessageFactory;
+import org.apache.logging.log4j.plugins.di.DI;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInfo;
+
+class InternalLoggerRegistryTest {
+    private LoggerContext loggerContext;
+    private InternalLoggerRegistry registry;
+
+    @BeforeEach
+    void setUp(final TestInfo testInfo) throws Exception {
+        loggerContext = new LoggerContext(testInfo.getDisplayName(), null, 
(URI) null, DI.createInitializedFactory());
+        final Field registryField = 
LoggerContext.class.getDeclaredField("loggerRegistry");
+        registryField.setAccessible(true);
+        registry = (InternalLoggerRegistry) registryField.get(loggerContext);
+    }
+
+    @AfterEach
+    void tearDown() {
+        if (loggerContext != null) {
+            loggerContext.stop();
+        }
+    }
+
+    @Test
+    void testGetLoggerReturnsNullForNonExistentLogger() {
+        assertNull(registry.getLogger("nonExistent", null));
+    }
+
+    @Test
+    void testComputeIfAbsentCreatesLogger() {
+        final Logger logger = loggerContext.getLogger("testLogger", null);
+        assertNotNull(logger);
+        assertEquals("testLogger", logger.getName());
+    }
+
+    @Test
+    void testGetLoggerRetrievesExistingLogger() {
+        final Logger logger = loggerContext.getLogger("testLogger", null);
+        assertSame(logger, registry.getLogger("testLogger", null));
+    }
+
+    @Test
+    void testHasLoggerReturnsCorrectStatus() {
+        assertFalse(registry.hasLogger("testLogger", (MessageFactory) null));
+        loggerContext.getLogger("testLogger", null);
+        assertTrue(registry.hasLogger("testLogger", (MessageFactory) null));
+    }
+
+    @Test
+    void testExpungeStaleWeakReferenceEntries() throws Exception {
+        final String loggerNamePrefix = "testLogger_";
+        final int numberOfLoggers = 1000;
+
+        for (int i = 0; i < numberOfLoggers; i++) {
+            final Logger logger = loggerContext.getLogger(loggerNamePrefix + 
i, null);
+            logger.info("Using logger {}", logger.getName());
+        }
+
+        await().atMost(10, SECONDS).pollInterval(100, 
MILLISECONDS).untilAsserted(() -> {
+            System.gc();
+            registry.getLogger("triggerExpunge", null);
+
+            final Map<MessageFactory, Map<String, WeakReference<Logger>>> 
loggerRefByNameByMessageFactory =
+                    reflectAndGetLoggerMapFromRegistry();
+
+            int unexpectedCount = 0;
+            for (final Map<String, WeakReference<Logger>> loggerRefByName : 
loggerRefByNameByMessageFactory.values()) {
+                for (int i = 0; i < numberOfLoggers; i++) {
+                    if (loggerRefByName.containsKey(loggerNamePrefix + i)) {
+                        unexpectedCount++;
+                    }
+                }
+            }
+            assertEquals(
+                    0, unexpectedCount, "Found " + unexpectedCount + " 
unexpected stale entries for MessageFactory");
+        });
+    }
+
+    @Test
+    void testExpungeStaleMessageFactoryEntry() throws Exception {
+        final SimpleMessageFactory mockMessageFactory = new 
SimpleMessageFactory();
+        Logger logger = loggerContext.getLogger("testLogger", 
mockMessageFactory);
+        logger.info("Using logger {}", logger.getName());
+        logger = null;
+
+        await().atMost(10, SECONDS).pollInterval(100, 
MILLISECONDS).untilAsserted(() -> {
+            System.gc();
+            registry.getLogger("triggerExpunge", null);
+
+            final Map<MessageFactory, Map<String, WeakReference<Logger>>> 
loggerRefByNameByMessageFactory =
+                    reflectAndGetLoggerMapFromRegistry();
+            assertNull(
+                    loggerRefByNameByMessageFactory.get(mockMessageFactory),
+                    "Stale MessageFactory entry was not removed from the outer 
map");
+        });
+    }
+
+    private Map<MessageFactory, Map<String, WeakReference<Logger>>> 
reflectAndGetLoggerMapFromRegistry()
+            throws NoSuchFieldException, IllegalAccessException {
+        final Field loggerMapField = 
InternalLoggerRegistry.class.getDeclaredField("loggerRefByNameByMessageFactory");
+        loggerMapField.setAccessible(true);
+        @SuppressWarnings("unchecked")
+        final Map<MessageFactory, Map<String, WeakReference<Logger>>> 
loggerMap =
+                (Map<MessageFactory, Map<String, WeakReference<Logger>>>) 
loggerMapField.get(registry);
+        return loggerMap;
+    }
+}
diff --git 
a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
index 4b5d3dd0ed..1acbff7e8d 100644
--- a/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
+++ b/log4j-core/src/main/java/org/apache/logging/log4j/core/LoggerContext.java
@@ -51,6 +51,7 @@ import org.apache.logging.log4j.core.util.Constants;
 import org.apache.logging.log4j.core.util.ExecutorServices;
 import org.apache.logging.log4j.core.util.NetUtils;
 import org.apache.logging.log4j.core.util.ShutdownCallbackRegistry;
+import org.apache.logging.log4j.core.util.internal.InternalLoggerRegistry;
 import org.apache.logging.log4j.kit.env.PropertyEnvironment;
 import 
org.apache.logging.log4j.kit.env.internal.ContextualEnvironmentPropertySource;
 import 
org.apache.logging.log4j.kit.env.internal.ContextualJavaPropsPropertySource;
@@ -88,8 +89,8 @@ public class LoggerContext extends AbstractLifeCycle
 
     public static final Key<LoggerContext> KEY = 
Key.forClass(LoggerContext.class);
 
-    private final LoggerRegistry<Logger> loggerRegistry = new 
LoggerRegistry<>();
     private final MessageFactory defaultMessageFactory;
+    private final InternalLoggerRegistry loggerRegistry;
 
     private final Collection<Consumer<Configuration>> 
configurationStartedListeners = new ArrayList<>();
     private final Collection<Consumer<Configuration>> 
configurationStoppedListeners = new ArrayList<>();
@@ -147,6 +148,7 @@ public class LoggerContext extends AbstractLifeCycle
         this.environment = 
instanceFactory.getInstance(PropertyEnvironment.class);
         this.configurationScheduler = 
instanceFactory.getInstance(ConfigurationScheduler.class);
         this.defaultMessageFactory = 
instanceFactory.getInstance(MessageFactory.class);
+        this.loggerRegistry = new 
InternalLoggerRegistry(this.defaultMessageFactory);
 
         this.configuration = new DefaultConfiguration(this);
         this.nullConfiguration = new NullConfiguration(this);
@@ -571,15 +573,9 @@ public class LoggerContext extends AbstractLifeCycle
     @Override
     public Logger getLogger(final String name, final MessageFactory 
messageFactory) {
         final MessageFactory actualMessageFactory = messageFactory != null ? 
messageFactory : defaultMessageFactory;
-        // Note: This is the only method where we add entries to the 
'loggerRegistry' ivar.
-        Logger logger = loggerRegistry.getLogger(name, actualMessageFactory);
-        if (logger != null) {
-            checkMessageFactory(logger, actualMessageFactory);
-            return logger;
-        }
-        logger = newLogger(name, actualMessageFactory);
-        loggerRegistry.putIfAbsent(name, logger.getMessageFactory(), logger);
-        return loggerRegistry.getLogger(name, logger.getMessageFactory());
+        final Logger logger = loggerRegistry.computeIfAbsent(name, 
actualMessageFactory, this::newLogger);
+        checkMessageFactory(logger, actualMessageFactory);
+        return logger;
     }
 
     /**
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
new file mode 100644
index 0000000000..0f7aa959fe
--- /dev/null
+++ 
b/log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistry.java
@@ -0,0 +1,353 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to you under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.logging.log4j.core.util.internal;
+
+import static java.util.Objects.requireNonNull;
+
+import java.lang.ref.Reference;
+import java.lang.ref.ReferenceQueue;
+import java.lang.ref.WeakReference;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.WeakHashMap;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReadWriteLock;
+import java.util.concurrent.locks.ReentrantReadWriteLock;
+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.message.MessageFactory;
+import org.apache.logging.log4j.spi.LoggerRegistry;
+import org.apache.logging.log4j.status.StatusLogger;
+import org.jspecify.annotations.NullMarked;
+import org.jspecify.annotations.Nullable;
+
+/**
+ * A registry of {@link Logger}s namespaced by name and message factory using 
{@link WeakReference}s.
+ * <p>
+ * This class extends {@link LoggerRegistry} to provide garbage-free logger 
tracking
+ * with minimal lock contention. Loggers are created outside the write lock to 
prevent
+ * deadlocks and improve concurrency.
+ * </p>
+ * @since 3.0.0
+ */
+@NullMarked
+public class InternalLoggerRegistry extends LoggerRegistry<Logger> {
+
+    private final Map<MessageFactory, Map<String, WeakReference<Logger>>> 
loggerRefByNameByMessageFactory =
+            new WeakHashMap<>();
+
+    private final ReadWriteLock lock = new ReentrantReadWriteLock();
+    private final Lock readLock = lock.readLock();
+    private final Lock writeLock = lock.writeLock();
+
+    // ReferenceQueue to track stale WeakReferences
+    private final ReferenceQueue<Logger> staleLoggerRefs = new 
ReferenceQueue<>();
+
+    private final MessageFactory defaultMessageFactory;
+
+    /**
+     * Constructs a registry whose default message factory (used when a lookup 
is requested with a
+     * {@code null} message factory) matches the one a {@link 
org.apache.logging.log4j.spi.LoggerContext}
+     * uses to create loggers. The two must resolve {@code null} to the same 
instance, otherwise
+     * loggers stored under the context default will not be found by 
null-factory lookups.
+     *
+     * @param defaultMessageFactory the default message factory (non-null)
+     */
+    public InternalLoggerRegistry(final MessageFactory defaultMessageFactory) {
+        this.defaultMessageFactory = requireNonNull(defaultMessageFactory, 
"defaultMessageFactory");
+    }
+
+    /**
+     * Expunges stale entries for logger references and message factories.
+     */
+    private void expungeStaleEntries() {
+        final Reference<? extends Logger> loggerRef = staleLoggerRefs.poll();
+
+        if (loggerRef != null) {
+            writeLock.lock();
+            try {
+                while (staleLoggerRefs.poll() != null) {
+                    // Clear refQueue
+                }
+
+                final Iterator<Map.Entry<MessageFactory, Map<String, 
WeakReference<Logger>>>>
+                        loggerRefByNameByMessageFactoryEntryIt =
+                                
loggerRefByNameByMessageFactory.entrySet().iterator();
+                while (loggerRefByNameByMessageFactoryEntryIt.hasNext()) {
+                    final Map.Entry<MessageFactory, Map<String, 
WeakReference<Logger>>>
+                            loggerRefByNameByMessageFactoryEntry = 
loggerRefByNameByMessageFactoryEntryIt.next();
+                    final Map<String, WeakReference<Logger>> loggerRefByName =
+                            loggerRefByNameByMessageFactoryEntry.getValue();
+                    loggerRefByName.values().removeIf(weakRef -> weakRef.get() 
== null);
+                    if (loggerRefByName.isEmpty()) {
+                        loggerRefByNameByMessageFactoryEntryIt.remove();
+                    }
+                }
+            } finally {
+                writeLock.unlock();
+            }
+        }
+    }
+
+    /**
+     * Returns the logger associated with the given name and message factory.
+     * <p>
+     * In the absence of a message factory, there can be made no assumptions 
on the message factory of the returned
+     * logger. This lenient behaviour is only kept for backward compatibility. 
Callers are strongly advised to
+     * <b>provide a message factory parameter to the method!</b>
+     * </p>
+     *
+     * @param name a logger name
+     * @param messageFactory a message factory
+     * @return the logger associated with the given name and message factory
+     */
+    @Override
+    public @Nullable Logger getLogger(final String name, @Nullable final 
MessageFactory messageFactory) {
+        requireNonNull(name, "name");
+
+        expungeStaleEntries();
+
+        final MessageFactory mf = messageFactory != null ? messageFactory : 
defaultMessageFactory;
+
+        readLock.lock();
+        try {
+            final Map<String, WeakReference<Logger>> loggerRefByName = 
loggerRefByNameByMessageFactory.get(mf);
+            if (loggerRefByName != null) {
+                final WeakReference<Logger> loggerRef = 
loggerRefByName.get(name);
+                if (loggerRef != null) {
+                    return loggerRef.get();
+                }
+            }
+            return null;
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    /**
+     * Returns all registered loggers.
+     *
+     * @return all registered loggers
+     */
+    @Override
+    public Collection<Logger> getLoggers() {
+        expungeStaleEntries();
+
+        readLock.lock();
+        try {
+            // Return a new collection to allow concurrent iteration over the 
loggers
+            //
+            // https://github.com/apache/logging-log4j2/issues/3234
+            return loggerRefByNameByMessageFactory.values().stream()
+                    .flatMap(loggerRefByName -> 
loggerRefByName.values().stream())
+                    .flatMap(loggerRef -> {
+                        @Nullable Logger logger = loggerRef.get();
+                        return logger != null ? Stream.of(logger) : 
Stream.empty();
+                    })
+                    .collect(Collectors.toList());
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    /**
+     * Adds all registered loggers to the given destination collection.
+     *
+     * @param destination the collection to add loggers to
+     * @return the destination collection with all registered loggers added
+     */
+    @Override
+    public Collection<Logger> getLoggers(final Collection<Logger> destination) 
{
+        requireNonNull(destination, "destination");
+
+        expungeStaleEntries();
+
+        readLock.lock();
+        try {
+            for (final Map<String, WeakReference<Logger>> loggerRefByName : 
loggerRefByNameByMessageFactory.values()) {
+                for (final WeakReference<Logger> loggerRef : 
loggerRefByName.values()) {
+                    @Nullable Logger logger = loggerRef.get();
+                    if (logger != null) {
+                        destination.add(logger);
+                    }
+                }
+            }
+        } finally {
+            readLock.unlock();
+        }
+        return destination;
+    }
+
+    /**
+     * Checks if a logger associated with the given name and message factory 
exists.
+     * <p>
+     * In the absence of a message factory, there can be made no assumptions 
on the message factory of the found
+     * logger. This lenient behaviour is only kept for backward compatibility. 
Callers are strongly advised to
+     * <b>provide a message factory parameter to the method!</b>
+     * </p>
+     *
+     * @param name a logger name
+     * @param messageFactory a message factory
+     * @return {@code true}, if the logger exists; {@code false} otherwise.
+     */
+    @Override
+    public boolean hasLogger(final String name, @Nullable final MessageFactory 
messageFactory) {
+        requireNonNull(name, "name");
+        return getLogger(name, messageFactory) != null;
+    }
+
+    /**
+     * Checks if a logger associated with the given name and message factory 
type exists.
+     *
+     * @param name a logger name
+     * @param messageFactoryClass a message factory class
+     * @return {@code true}, if the logger exists; {@code false} otherwise.
+     */
+    @Override
+    public boolean hasLogger(final String name, final Class<? extends 
MessageFactory> messageFactoryClass) {
+        requireNonNull(name, "name");
+        requireNonNull(messageFactoryClass, "messageFactoryClass");
+
+        expungeStaleEntries();
+
+        readLock.lock();
+        try {
+            return loggerRefByNameByMessageFactory.entrySet().stream()
+                    .filter(entry -> 
messageFactoryClass.equals(entry.getKey().getClass()))
+                    .anyMatch(entry -> entry.getValue().containsKey(name));
+        } finally {
+            readLock.unlock();
+        }
+    }
+
+    /**
+     * Registers the provided logger.
+     * <p>
+     * The logger is registered using the {@code name} and {@code 
messageFactory} parameters as keys.
+     * </p>
+     *
+     * @param name a logger name
+     * @param messageFactory a message factory
+     * @param logger a logger instance
+     */
+    @Override
+    public void putIfAbsent(final String name, @Nullable final MessageFactory 
messageFactory, final Logger logger) {
+        requireNonNull(name, "name");
+        requireNonNull(logger, "logger");
+
+        expungeStaleEntries();
+
+        final MessageFactory mf = messageFactory != null ? messageFactory : 
defaultMessageFactory;
+
+        writeLock.lock();
+        try {
+            Map<String, WeakReference<Logger>> loggerRefByName = 
loggerRefByNameByMessageFactory.get(mf);
+            // noinspection Java8MapApi (avoid the allocation of lambda passed 
to `Map::computeIfAbsent`)
+            if (loggerRefByName == null) {
+                loggerRefByNameByMessageFactory.put(mf, loggerRefByName = new 
HashMap<>());
+            }
+            final WeakReference<Logger> loggerRef = loggerRefByName.get(name);
+            if (loggerRef == null || loggerRef.get() == null) {
+                loggerRefByName.put(name, new WeakReference<>(logger, 
staleLoggerRefs));
+            }
+        } finally {
+            writeLock.unlock();
+        }
+    }
+
+    /**
+     * Returns the logger associated with the given name and message factory, 
creating it if necessary
+     * using the provided supplier. The logger is created outside the write 
lock to avoid deadlocks
+     * and reduce contention.
+     *
+     * @param name a logger name
+     * @param messageFactory a message factory
+     * @param loggerSupplier a function to create the logger
+     * @return the existing or newly created logger
+     */
+    public Logger computeIfAbsent(
+            final String name,
+            final MessageFactory messageFactory,
+            final BiFunction<String, MessageFactory, Logger> loggerSupplier) {
+        // Check arguments
+        requireNonNull(name, "name");
+        requireNonNull(messageFactory, "messageFactory");
+        requireNonNull(loggerSupplier, "loggerSupplier");
+        // Skipping `expungeStaleEntries()`, it will be invoked by the 
`getLogger()` invocation below
+
+        // Read lock fast path: See if logger already exists
+        @Nullable Logger logger = getLogger(name, messageFactory);
+        if (logger != null) {
+            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 {
+            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<>());
+            }
+            final WeakReference<Logger> loggerRef = loggerRefByName.get(name);
+            if (loggerRef == null || (logger = loggerRef.get()) == null) {
+                loggerRefByName.put(name, new WeakReference<>(logger = 
newLogger, staleLoggerRefs));
+            }
+            return logger;
+        } finally {
+            writeLock.unlock();
+        }
+    }
+}

Reply via email to