Ensure that background preinit has completed before refresh returns

This commit is a continuation of the changes made in b85b608. 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
This commit is contained in:
Andy Wilkinson 2016-01-20 21:56:35 +00:00
parent 179467bdd9
commit 992e90f43d
1 changed files with 44 additions and 35 deletions

View File

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