Skip to content

Commit 3a4a37d

Browse files
committed
Minimize lock usage in InternalLoggerRegistry
It has been reported that holding a lock on `InternalLoggerRegistry` during the creation of a logger can cause performance loss and deadlocks. The logger constructor can trigger property lookups and other pluggable operations, we don't entirely control. The fix to #3252 only covered one of these cases. This change moves the instantiation of new `Logger`s outside the write lock. While in some cases, this will cause multiple instantiations of loggers with the same parameters, all such loggers are functionally equivalent. On the other hand, the change allows the creation of different loggers in parallel. Closes #3399
1 parent a76a83f commit 3a4a37d

File tree

2 files changed

+44
-34
lines changed

2 files changed

+44
-34
lines changed

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

+34-34
Original file line numberDiff line numberDiff line change
@@ -147,44 +147,44 @@ public Logger computeIfAbsent(
147147
return logger;
148148
}
149149

150+
// Creating a logger is expensive and might cause lookups and locks, possibly deadlocks:
151+
// https://github.com/apache/logging-log4j2/issues/3252
152+
// https://github.com/apache/logging-log4j2/issues/3399
153+
//
154+
// Creating loggers without a lock, allows multiple threads to create loggers in parallel, which also
155+
// improves performance.
156+
// Since all loggers with the same parameters are equivalent, we can safely return the logger from the
157+
// thread that finishes first.
158+
Logger newLogger = loggerSupplier.apply(name, messageFactory);
159+
160+
// Report name and message factory mismatch if there are any
161+
final String loggerName = newLogger.getName();
162+
final MessageFactory loggerMessageFactory = newLogger.getMessageFactory();
163+
if (!loggerName.equals(name) || !loggerMessageFactory.equals(messageFactory)) {
164+
StatusLogger.getLogger()
165+
.error(
166+
"Newly registered logger with name `{}` and message factory `{}`, is requested to be associated with a different name `{}` or message factory `{}`.\n"
167+
+ "Effectively the message factory of the logger will be used and the other one will be ignored.\n"
168+
+ "This generally hints a problem at the logger context implementation.\n"
169+
+ "Please report this using the Log4j project issue tracker.",
170+
loggerName,
171+
loggerMessageFactory,
172+
name,
173+
messageFactory);
174+
}
175+
150176
// Write lock slow path: Insert the logger
151177
writeLock.lock();
152178
try {
153179

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;
160-
}
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));
183-
}
184-
185-
// Insert the logger
186-
loggerRefByName.put(name, new WeakReference<>(logger));
187-
return logger;
180+
Logger currentLogger = loggerRefByNameByMessageFactory
181+
.computeIfAbsent(messageFactory, ignored -> new HashMap<>())
182+
.computeIfAbsent(name, ignored -> new WeakReference<>(newLogger))
183+
.get();
184+
// Null check, in the rare case a GC occurs between:
185+
// * new WeakReference (last reference to `newLogger`)
186+
// * WeakReference.get()
187+
return currentLogger == null ? newLogger : currentLogger;
188188
} finally {
189189
writeLock.unlock();
190190
}
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)