Commit 992e90f4 authored by Andy Wilkinson's avatar Andy Wilkinson

Ensure that background preinit has completed before refresh returns

This commit is a continuation of the changes made in b85b6082. It
addresses an additional problem when testing applications where two
contexts are refreshed in quick succession. In this scenario, it
was possible, theoretically at least, for the first context’s background preinitialization to still be in progress and creating loggers when the
second is refreshed and resets the logger context.

This commit updates BackgroundPreinitializer so that, upon receipt of
a ContextRefreshedEvent, it waits for preinitialization to have
completed. In the scenario described above, this ensures that
preinitialization has completed before the call to refresh() for the
first context returns, thereby preventing it from running in parallel
with the refresh of the second context.

Closes gh-4871
parent 179467bd
...@@ -16,16 +16,15 @@ ...@@ -16,16 +16,15 @@
package org.springframework.boot.autoconfigure; package org.springframework.boot.autoconfigure;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.validation.Validation; 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.ApplicationStartedEvent;
import org.springframework.boot.logging.LoggingApplicationListener; import org.springframework.boot.logging.LoggingApplicationListener;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.Order; import org.springframework.core.annotation.Order;
import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter; import org.springframework.http.converter.support.AllEncompassingFormHttpMessageConverter;
...@@ -38,50 +37,60 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage ...@@ -38,50 +37,60 @@ import org.springframework.http.converter.support.AllEncompassingFormHttpMessage
* @since 1.3.0 * @since 1.3.0
*/ */
@Order(LoggingApplicationListener.DEFAULT_ORDER + 1) @Order(LoggingApplicationListener.DEFAULT_ORDER + 1)
public class BackgroundPreinitializer public class BackgroundPreinitializer implements ApplicationListener<ApplicationEvent> {
implements ApplicationListener<ApplicationEnvironmentPreparedEvent> {
private volatile Thread initializationThread;
@Override @Override
public void onApplicationEvent(ApplicationEnvironmentPreparedEvent event) { public void onApplicationEvent(ApplicationEvent event) {
try { if (event instanceof ApplicationStartedEvent) {
ExecutorService executor = Executors.newSingleThreadExecutor(); performInitialization();
submit(executor, new MessageConverterInitializer());
submit(executor, new MBeanFactoryInitializer());
submit(executor, new ValidationInitializer());
executor.shutdown();
} }
catch (Exception ex) { else if (event instanceof ContextRefreshedEvent) {
// This will fail on GAE where creating threads is prohibited. We can safely awaitInitialization();
// continue but startup will be slightly slower as the initialization will now
// happen on the main thread.
} }
} }
private void submit(ExecutorService executor, Runnable runnable) { private void performInitialization() {
executor.submit(new FailSafeRunnable(runnable)); try {
} this.initializationThread = new Thread(new Runnable() {
/**
* Wrapper to ignore any thrown exceptions.
*/
private static class FailSafeRunnable implements Runnable {
private final Runnable delegate;
FailSafeRunnable(Runnable delegate) {
this.delegate = delegate;
}
@Override @Override
public void run() { public void run() {
runSafely(new MessageConverterInitializer());
runSafely(new MBeanFactoryInitializer());
runSafely(new ValidationInitializer());
}
public void runSafely(Runnable runnable) {
try { try {
this.delegate.run(); runnable.run();
} }
catch (Throwable ex) { catch (Throwable ex) {
// Ignore // Ignore
} }
} }
}, "background-preinit");
this.initializationThread.start();
}
catch (Exception ex) {
// 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
// happen on the main thread.
}
}
private void awaitInitialization() {
try {
this.initializationThread.join();
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
finally {
this.initializationThread = null;
}
} }
/** /**
......
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