Skip to content

Commit 3ffaf76

Browse files
ppkarwaszvy
andauthored
Minimize lock usage in InternalLoggerRegistry (#3418)
Co-authored-by: Volkan Yazıcı <[email protected]>
1 parent a76a83f commit 3ffaf76

File tree

2 files changed

+65
-41
lines changed

2 files changed

+65
-41
lines changed

log4j-core/src/main/java/org/apache/logging/log4j/core/util/internal/InternalLoggerRegistry.java

+55-41
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,6 @@
2222
import java.util.Collection;
2323
import java.util.HashMap;
2424
import java.util.Map;
25-
import java.util.Optional;
2625
import java.util.WeakHashMap;
2726
import java.util.concurrent.locks.Lock;
2827
import java.util.concurrent.locks.ReadWriteLock;
@@ -31,17 +30,20 @@
3130
import java.util.stream.Collectors;
3231
import java.util.stream.Stream;
3332
import org.apache.logging.log4j.core.Logger;
33+
import org.apache.logging.log4j.core.LoggerContext;
3434
import org.apache.logging.log4j.message.MessageFactory;
3535
import org.apache.logging.log4j.status.StatusLogger;
3636
import org.jspecify.annotations.NullMarked;
3737
import org.jspecify.annotations.Nullable;
3838

3939
/**
40-
* Convenience class used by {@link org.apache.logging.log4j.core.LoggerContext}
40+
* A registry of {@link Logger}s namespaced by name and message factory.
41+
* This class is internally used by {@link LoggerContext}.
4142
* <p>
42-
* We don't use {@link org.apache.logging.log4j.spi.LoggerRegistry} from the Log4j API to keep Log4j Core independent
43-
* from the version of the Log4j API at runtime.
43+
* 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.
44+
* This also allows Log4j Core to evolve independently from Log4j API.
4445
* </p>
46+
*
4547
* @since 2.25.0
4648
*/
4749
@NullMarked
@@ -70,11 +72,15 @@ public InternalLoggerRegistry() {}
7072
requireNonNull(messageFactory, "messageFactory");
7173
readLock.lock();
7274
try {
73-
return Optional.of(loggerRefByNameByMessageFactory)
74-
.map(loggerRefByNameByMessageFactory -> loggerRefByNameByMessageFactory.get(messageFactory))
75-
.map(loggerRefByName -> loggerRefByName.get(name))
76-
.map(WeakReference::get)
77-
.orElse(null);
75+
final Map<String, WeakReference<Logger>> loggerRefByName =
76+
loggerRefByNameByMessageFactory.get(messageFactory);
77+
if (loggerRefByName != null) {
78+
final WeakReference<Logger> loggerRef = loggerRefByName.get(name);
79+
if (loggerRef != null) {
80+
return loggerRef.get();
81+
}
82+
}
83+
return null;
7884
} finally {
7985
readLock.unlock();
8086
}
@@ -147,43 +153,51 @@ public Logger computeIfAbsent(
147153
return logger;
148154
}
149155

156+
// Intentionally moving the logger creation outside the write lock, because:
157+
//
158+
// - Logger instantiation is expensive (causes contention on the write-lock)
159+
//
160+
// - User code might have circular code paths, though through different threads.
161+
// Consider `T1[ILR:computeIfAbsent] -> ... -> T1[Logger::new] -> ... -> T2[ILR::computeIfAbsent]`.
162+
// Hence, having logger instantiation while holding a write lock might cause deadlocks:
163+
// https://github.com/apache/logging-log4j2/issues/3252
164+
// https://github.com/apache/logging-log4j2/issues/3399
165+
//
166+
// - Creating loggers without a lock, allows multiple threads to create loggers in parallel, which also improves
167+
// performance.
168+
//
169+
// Since all loggers with the same parameters are equivalent, we can safely return the logger from the
170+
// thread that finishes first.
171+
Logger newLogger = loggerSupplier.apply(name, messageFactory);
172+
173+
// Report name and message factory mismatch if there are any
174+
final String loggerName = newLogger.getName();
175+
final MessageFactory loggerMessageFactory = newLogger.getMessageFactory();
176+
if (!loggerName.equals(name) || !loggerMessageFactory.equals(messageFactory)) {
177+
StatusLogger.getLogger()
178+
.error(
179+
"Newly registered logger with name `{}` and message factory `{}`, is requested to be associated with a different name `{}` or message factory `{}`.\n"
180+
+ "Effectively the message factory of the logger will be used and the other one will be ignored.\n"
181+
+ "This generally hints a problem at the logger context implementation.\n"
182+
+ "Please report this using the Log4j project issue tracker.",
183+
loggerName,
184+
loggerMessageFactory,
185+
name,
186+
messageFactory);
187+
}
188+
150189
// Write lock slow path: Insert the logger
151190
writeLock.lock();
152191
try {
153-
154-
// See if the logger is created by another thread in the meantime
155-
final Map<String, WeakReference<Logger>> loggerRefByName =
156-
loggerRefByNameByMessageFactory.computeIfAbsent(messageFactory, ignored -> new HashMap<>());
157-
WeakReference<Logger> loggerRef = loggerRefByName.get(name);
158-
if (loggerRef != null && (logger = loggerRef.get()) != null) {
159-
return logger;
192+
Map<String, WeakReference<Logger>> loggerRefByName = loggerRefByNameByMessageFactory.get(messageFactory);
193+
// noinspection Java8MapApi (avoid the allocation of lambda passed to `Map::computeIfAbsent`)
194+
if (loggerRefByName == null) {
195+
loggerRefByNameByMessageFactory.put(messageFactory, loggerRefByName = new HashMap<>());
160196
}
161-
162-
// Create the logger
163-
logger = loggerSupplier.apply(name, messageFactory);
164-
165-
// Report name and message factory mismatch if there are any
166-
final String loggerName = logger.getName();
167-
final MessageFactory loggerMessageFactory = logger.getMessageFactory();
168-
if (!loggerMessageFactory.equals(messageFactory)) {
169-
StatusLogger.getLogger()
170-
.error(
171-
"Newly registered logger with name `{}` and message factory `{}`, is requested to be associated with a different name `{}` or message factory `{}`.\n"
172-
+ "Effectively the message factory of the logger will be used and the other one will be ignored.\n"
173-
+ "This generally hints a problem at the logger context implementation.\n"
174-
+ "Please report this using the Log4j project issue tracker.",
175-
loggerName,
176-
loggerMessageFactory,
177-
name,
178-
messageFactory);
179-
// Register logger under alternative keys
180-
loggerRefByNameByMessageFactory
181-
.computeIfAbsent(loggerMessageFactory, ignored -> new HashMap<>())
182-
.putIfAbsent(loggerName, new WeakReference<>(logger));
197+
final WeakReference<Logger> loggerRef = loggerRefByName.get(name);
198+
if (loggerRef == null || (logger = loggerRef.get()) == null) {
199+
loggerRefByName.put(name, new WeakReference<>(logger = newLogger));
183200
}
184-
185-
// Insert the logger
186-
loggerRefByName.put(name, new WeakReference<>(logger));
187201
return logger;
188202
} finally {
189203
writeLock.unlock();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<entry xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xmlns="https://logging.apache.org/xml/ns"
4+
xsi:schemaLocation="https://logging.apache.org/xml/ns https://logging.apache.org/xml/ns/log4j-changelog-0.xsd"
5+
type="fixed">
6+
<issue id="3399" link="https://github.com/apache/logging-log4j2/issues/3399"/>
7+
<description format="asciidoc">
8+
Minimize lock usage in `InternalLoggerRegistry`.
9+
</description>
10+
</entry>

0 commit comments

Comments
 (0)