|
22 | 22 | import java.util.Collection; |
23 | 23 | import java.util.HashMap; |
24 | 24 | import java.util.Map; |
25 | | -import java.util.Optional; |
26 | 25 | import java.util.WeakHashMap; |
27 | 26 | import java.util.concurrent.locks.Lock; |
28 | 27 | import java.util.concurrent.locks.ReadWriteLock; |
|
31 | 30 | import java.util.stream.Collectors; |
32 | 31 | import java.util.stream.Stream; |
33 | 32 | import org.apache.logging.log4j.core.Logger; |
| 33 | +import org.apache.logging.log4j.core.LoggerContext; |
34 | 34 | import org.apache.logging.log4j.message.MessageFactory; |
35 | 35 | import org.apache.logging.log4j.status.StatusLogger; |
36 | 36 | import org.jspecify.annotations.NullMarked; |
37 | 37 | import org.jspecify.annotations.Nullable; |
38 | 38 |
|
39 | 39 | /** |
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}. |
41 | 42 | * <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. |
44 | 45 | * </p> |
| 46 | + * |
45 | 47 | * @since 2.25.0 |
46 | 48 | */ |
47 | 49 | @NullMarked |
@@ -70,11 +72,15 @@ public InternalLoggerRegistry() {} |
70 | 72 | requireNonNull(messageFactory, "messageFactory"); |
71 | 73 | readLock.lock(); |
72 | 74 | 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; |
78 | 84 | } finally { |
79 | 85 | readLock.unlock(); |
80 | 86 | } |
@@ -147,43 +153,51 @@ public Logger computeIfAbsent( |
147 | 153 | return logger; |
148 | 154 | } |
149 | 155 |
|
| 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 | + |
150 | 189 | // Write lock slow path: Insert the logger |
151 | 190 | writeLock.lock(); |
152 | 191 | 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<>()); |
160 | 196 | } |
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)); |
183 | 200 | } |
184 | | - |
185 | | - // Insert the logger |
186 | | - loggerRefByName.put(name, new WeakReference<>(logger)); |
187 | 201 | return logger; |
188 | 202 | } finally { |
189 | 203 | writeLock.unlock(); |
|
0 commit comments