Commit d62c26c9 authored by Andy Wilkinson's avatar Andy Wilkinson

Ensure that preinitialization has completed before run returns

Previously, background preinitialization was started in response to
an ApplicationEnvironmentPreparedEvent and would complete at an
undetermined time later. This opened a window where SpringApplication
run could return and background preinitialization could still be
in progress. If, within this window, something attempted to configure
the logging system, an IO failure could occur as logging on the
background preinitialization thread would attempt to use resources
that had been closed.

This commit updates BackgroundPreinitializer so that it waits for
preinitialization to have completed when it receives an application
ready or application failed event. This prevents SpringApplication
run from returning while preinitialization is still in progress,
closing the window described above.

With info level logging enabled it appears that background
preinitialization consistently completes before the application ready
event is published. As a result, waiting should have no adverse effect
on performance in normal circumstances. With logging configured such
that background preinitialization outputs a large volume of log
messages (enabling trace logging for the root logger, for example), it
will be slowed down sufficiently for waiting to be necessary.

Closes gh-5669
parent 42eec50e
...@@ -16,6 +16,7 @@ ...@@ -16,6 +16,7 @@
package org.springframework.boot.autoconfigure; package org.springframework.boot.autoconfigure;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import javax.validation.Validation; import javax.validation.Validation;
...@@ -23,6 +24,9 @@ import javax.validation.Validation; ...@@ -23,6 +24,9 @@ import javax.validation.Validation;
import org.apache.catalina.mbeans.MBeanFactory; import org.apache.catalina.mbeans.MBeanFactory;
import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent; import org.springframework.boot.context.event.ApplicationEnvironmentPreparedEvent;
import org.springframework.boot.context.event.ApplicationFailedEvent;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.event.SpringApplicationEvent;
import org.springframework.boot.logging.LoggingApplicationListener; import org.springframework.boot.logging.LoggingApplicationListener;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
...@@ -40,14 +44,27 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage ...@@ -40,14 +44,27 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
*/ */
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1) @Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer public class BackgroundPreinitializer
implements ApplicationListener<ApplicationEnvironmentPreparedEvent> { implements ApplicationListener<SpringApplicationEvent> {
private static final AtomicBoolean preinitalizationStarted = new AtomicBoolean(false); private static final AtomicBoolean preinitalizationStarted = new AtomicBoolean(false);
private static final CountDownLatch preinitializationComplete = new CountDownLatch(1);
@Override @Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { public void onApplicationEvent(SpringApplicationEvent event) {
if (preinitalizationStarted.compareAndSet(false, true)) { if (event instanceof ApplicationEnvironmentPreparedEvent) {
performPreinitialization(); if (preinitalizationStarted.compareAndSet(false, true)) {
performPreinitialization();
}
}
if (event instanceof ApplicationReadyEvent
|| event instanceof ApplicationFailedEvent) {
try {
preinitializationComplete.await();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
} }
} }
...@@ -62,6 +79,7 @@ public class BackgroundPreinitializer ...@@ -62,6 +79,7 @@ public class BackgroundPreinitializer
runSafely(new ValidationInitializer()); runSafely(new ValidationInitializer());
runSafely(new JacksonInitializer()); runSafely(new JacksonInitializer());
runSafely(new ConversionServiceInitializer()); runSafely(new ConversionServiceInitializer());
preinitializationComplete.countDown();
} }
public void runSafely(Runnable runnable) { public void runSafely(Runnable runnable) {
...@@ -80,6 +98,7 @@ public class BackgroundPreinitializer ...@@ -80,6 +98,7 @@ public class BackgroundPreinitializer
// This will fail on GAE where creating threads is prohibited. We can safely // This will fail on GAE where creating threads is prohibited. We can safely
// continue but startup will be slightly slower as the initialization will now // continue but startup will be slightly slower as the initialization will now
// happen on the main thread. // happen on the main thread.
preinitializationComplete.countDown();
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment