Skip to content

Commit 2a3d5c4

Browse files
committed
Tolerate parallel init of SLF4J
Closes gh-37477
1 parent 5ec3580 commit 2a3d5c4

File tree

3 files changed

+94
-1
lines changed

3 files changed

+94
-1
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/logging/logback/LogbackLoggingSystem.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@
4343
import org.slf4j.LoggerFactory;
4444
import org.slf4j.Marker;
4545
import org.slf4j.bridge.SLF4JBridgeHandler;
46+
import org.slf4j.helpers.SubstituteLoggerFactory;
4647

4748
import org.springframework.aot.AotDetector;
4849
import org.springframework.beans.factory.aot.BeanFactoryInitializationAotContribution;
@@ -384,7 +385,7 @@ private ch.qos.logback.classic.Logger getLogger(String name) {
384385
}
385386

386387
private LoggerContext getLoggerContext() {
387-
ILoggerFactory factory = LoggerFactory.getILoggerFactory();
388+
ILoggerFactory factory = getLoggerFactory();
388389
Assert.isInstanceOf(LoggerContext.class, factory,
389390
() -> String.format(
390391
"LoggerFactory is not a Logback LoggerContext but Logback is on "
@@ -396,6 +397,21 @@ private LoggerContext getLoggerContext() {
396397
return (LoggerContext) factory;
397398
}
398399

400+
private ILoggerFactory getLoggerFactory() {
401+
ILoggerFactory factory = LoggerFactory.getILoggerFactory();
402+
while (factory instanceof SubstituteLoggerFactory) {
403+
try {
404+
Thread.sleep(50);
405+
}
406+
catch (InterruptedException ex) {
407+
Thread.currentThread().interrupt();
408+
throw new IllegalStateException("Interrupted while waiting for non-subtitute logger factory", ex);
409+
}
410+
factory = LoggerFactory.getILoggerFactory();
411+
}
412+
return factory;
413+
}
414+
399415
private Object getLocation(ILoggerFactory factory) {
400416
try {
401417
ProtectionDomain protectionDomain = factory.getClass().getProtectionDomain();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
/*
2+
* Copyright 2012-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.logging.logback;
18+
19+
import java.util.ArrayList;
20+
import java.util.List;
21+
import java.util.concurrent.CopyOnWriteArrayList;
22+
23+
import ch.qos.logback.classic.LoggerContext;
24+
import org.junit.jupiter.api.AfterEach;
25+
import org.junit.jupiter.api.Test;
26+
import org.slf4j.LoggerFactory;
27+
28+
import org.springframework.boot.logging.LoggingSystem;
29+
import org.springframework.boot.testsupport.classpath.ForkedClassPath;
30+
31+
import static org.assertj.core.api.Assertions.assertThat;
32+
33+
/**
34+
* Tests for parallel initialization of {@link LogbackLoggingSystem} that are separate
35+
* from {@link LogbackLoggingSystemTests}. This isolation allows them to have complete
36+
* control over how and when the logging system is initialized.
37+
*
38+
* @author Andy Wilkinson
39+
*/
40+
class LogbackLoggingSystemParallelInitializationTests {
41+
42+
private final LoggingSystem loggingSystem = LoggingSystem
43+
.get(LogbackLoggingSystemParallelInitializationTests.class.getClassLoader());
44+
45+
@AfterEach
46+
void cleanUp() {
47+
this.loggingSystem.cleanUp();
48+
((LoggerContext) LoggerFactory.getILoggerFactory()).stop();
49+
}
50+
51+
@Test
52+
@ForkedClassPath
53+
void noExceptionsAreThrownWhenBeforeInitializeIsCalledInParallel() {
54+
List<Thread> threads = new ArrayList<>();
55+
List<Throwable> exceptions = new CopyOnWriteArrayList<>();
56+
for (int i = 0; i < 10; i++) {
57+
Thread thread = new Thread(() -> this.loggingSystem.beforeInitialize());
58+
thread.setUncaughtExceptionHandler((t, ex) -> exceptions.add(ex));
59+
threads.add(thread);
60+
}
61+
threads.forEach(Thread::start);
62+
threads.forEach(this::join);
63+
assertThat(exceptions).isEmpty();
64+
}
65+
66+
private void join(Thread thread) {
67+
try {
68+
thread.join();
69+
}
70+
catch (InterruptedException ex) {
71+
Thread.currentThread().interrupt();
72+
throw new RuntimeException(ex);
73+
}
74+
}
75+
76+
}

src/checkstyle/checkstyle-suppressions.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
<suppress files="LogbackInitializer\.java" checks="IllegalImport" />
1111
<suppress files="LogbackLoggingSystem\.java" checks="IllegalImport" />
1212
<suppress files="LogbackLoggingSystemTests\.java" checks="IllegalImport" />
13+
<suppress files="LogbackLoggingSystemParallelInitializationTests\.java" checks="IllegalImport" />
1314
<suppress files="LogbackConfigurationAotContributionTests\.java" checks="IllegalImport" />
1415
<suppress files="MetricsAutoConfigurationMeterRegistryPostProcessorIntegrationTests\.java" checks="IllegalImport" message="LoggerFactory"/>
1516
<suppress files="SpringApplicationTests\.java" checks="FinalClass" />

0 commit comments

Comments
 (0)