SpringApplicationShutdownHookTests Class — spring-boot Architecture
Architecture documentation for the SpringApplicationShutdownHookTests class in SpringApplicationShutdownHookTests.java from the spring-boot codebase.
Entity Profile
Source Code
core/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java lines 54–344
class SpringApplicationShutdownHookTests {
@Test
void shutdownHookIsNotAddedUntilContextIsRegistered() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
shutdownHook.enableShutdownHookAddition();
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
ConfigurableApplicationContext context = new GenericApplicationContext();
shutdownHook.registerApplicationContext(context);
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isTrue();
}
@Test
void shutdownHookIsNotAddedUntilHandlerIsRegistered() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
shutdownHook.enableShutdownHookAddition();
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
shutdownHook.getHandlers().add(() -> {
});
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isTrue();
}
@Test
void shutdownHookIsNotAddedUntilAdditionIsEnabled() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
shutdownHook.getHandlers().add(() -> {
});
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isFalse();
shutdownHook.enableShutdownHookAddition();
shutdownHook.getHandlers().add(() -> {
});
assertThat(shutdownHook.isRuntimeShutdownHookAdded()).isTrue();
}
@Test
void runClosesContextsBeforeRunningHandlerActions() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
List<Object> finished = new CopyOnWriteArrayList<>();
ConfigurableApplicationContext context = new TestApplicationContext(finished);
shutdownHook.registerApplicationContext(context);
context.refresh();
Runnable handlerAction = new TestHandlerAction(finished);
shutdownHook.getHandlers().add(handlerAction);
shutdownHook.run();
assertThat(finished).containsExactly(context, handlerAction);
}
@Test
void runWhenContextIsBeingClosedInAnotherThreadWaitsUntilContextIsInactive() throws InterruptedException {
// This situation occurs in the Spring Tools IDE. It triggers a context close via
// JMX and then stops the JVM. The two actions happen almost simultaneously
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
List<Object> finished = new CopyOnWriteArrayList<>();
CountDownLatch closing = new CountDownLatch(1);
CountDownLatch proceedWithClose = new CountDownLatch(1);
ConfigurableApplicationContext context = new TestApplicationContext(finished, closing, proceedWithClose);
shutdownHook.registerApplicationContext(context);
context.refresh();
Runnable handlerAction = new TestHandlerAction(finished);
shutdownHook.getHandlers().add(handlerAction);
Thread contextThread = new Thread(context::close);
contextThread.start();
// Wait for context thread to begin closing the context
closing.await();
Thread shutdownThread = new Thread(shutdownHook);
shutdownThread.start();
// Shutdown thread should start waiting for context to become inactive
Awaitility.await().atMost(Duration.ofSeconds(30)).until(shutdownThread::getState, State.TIMED_WAITING::equals);
// Allow context thread to proceed, unblocking shutdown thread
proceedWithClose.countDown();
contextThread.join();
shutdownThread.join();
// Context should have been closed before handler action was run
assertThat(finished).containsExactly(context, handlerAction);
}
@Test
void runDueToExitDuringRefreshWhenContextHasBeenClosedDoesNotDeadlock() {
GenericApplicationContext context = new GenericApplicationContext();
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
shutdownHook.registerApplicationContext(context);
context.registerBean(CloseContextAndExit.class, context, shutdownHook);
context.refresh();
}
@Test
void runWhenContextIsClosedDirectlyRunsHandlerActions() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
List<Object> finished = new CopyOnWriteArrayList<>();
ConfigurableApplicationContext context = new TestApplicationContext(finished);
shutdownHook.registerApplicationContext(context);
context.refresh();
context.close();
Runnable handlerAction1 = new TestHandlerAction(finished);
Runnable handlerAction2 = new TestHandlerAction(finished);
shutdownHook.getHandlers().add(handlerAction1);
shutdownHook.getHandlers().add(handlerAction2);
shutdownHook.run();
assertThat(finished).contains(handlerAction1, handlerAction2);
}
@Test
@SuppressWarnings("NullAway") // Test null check
void addHandlerActionWhenNullThrowsException() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
assertThatIllegalArgumentException().isThrownBy(() -> shutdownHook.getHandlers().add(null))
.withMessage("'action' must not be null");
}
@Test
void addHandlerActionWhenShuttingDownThrowsException() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
shutdownHook.run();
Runnable handlerAction = new TestHandlerAction(new ArrayList<>());
assertThatIllegalStateException().isThrownBy(() -> shutdownHook.getHandlers().add(handlerAction))
.withMessage("Shutdown in progress");
}
@Test
@SuppressWarnings("NullAway") // Test null check
void removeHandlerActionWhenNullThrowsException() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
assertThatIllegalArgumentException().isThrownBy(() -> shutdownHook.getHandlers().remove(null))
.withMessage("'action' must not be null");
}
@Test
void removeHandlerActionWhenShuttingDownThrowsException() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
Runnable handlerAction = new TestHandlerAction(new ArrayList<>());
shutdownHook.getHandlers().add(handlerAction);
shutdownHook.run();
assertThatIllegalStateException().isThrownBy(() -> shutdownHook.getHandlers().remove(handlerAction))
.withMessage("Shutdown in progress");
}
@Test
void failsWhenDeregisterActiveContext() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
ConfigurableApplicationContext context = new GenericApplicationContext();
shutdownHook.registerApplicationContext(context);
context.refresh();
assertThatIllegalStateException().isThrownBy(() -> shutdownHook.deregisterFailedApplicationContext(context));
assertThat(shutdownHook.isApplicationContextRegistered(context)).isTrue();
}
@Test
void deregistersFailedContext() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
GenericApplicationContext context = new GenericApplicationContext();
shutdownHook.registerApplicationContext(context);
context.registerBean(FailingBean.class);
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(context::refresh);
assertThat(shutdownHook.isApplicationContextRegistered(context)).isTrue();
shutdownHook.deregisterFailedApplicationContext(context);
assertThat(shutdownHook.isApplicationContextRegistered(context)).isFalse();
}
@Test
void handlersRunInDeterministicOrderFromLastRegisteredToFirst() {
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
Runnable r1 = mock(Runnable.class);
Runnable r2 = mock(Runnable.class);
Runnable r3 = mock(Runnable.class);
shutdownHook.getHandlers().add(r2);
shutdownHook.getHandlers().add(r1);
shutdownHook.getHandlers().add(r3);
shutdownHook.run();
InOrder ordered = inOrder(r1, r2, r3);
ordered.verify(r3).run();
ordered.verify(r1).run();
ordered.verify(r2).run();
ordered.verifyNoMoreInteractions();
}
static class TestSpringApplicationShutdownHook extends SpringApplicationShutdownHook {
private boolean runtimeShutdownHookAdded;
@Override
protected void addRuntimeShutdownHook() {
this.runtimeShutdownHookAdded = true;
}
boolean isRuntimeShutdownHookAdded() {
return this.runtimeShutdownHookAdded;
}
}
static class TestApplicationContext extends AbstractApplicationContext {
private final ConfigurableListableBeanFactory beanFactory = new DefaultListableBeanFactory();
private final List<Object> finished;
private final @Nullable CountDownLatch closing;
private final @Nullable CountDownLatch proceedWithClose;
TestApplicationContext(List<Object> finished) {
this(finished, null, null);
}
TestApplicationContext(List<Object> finished, @Nullable CountDownLatch closing,
@Nullable CountDownLatch proceedWithClose) {
this.finished = finished;
this.closing = closing;
this.proceedWithClose = proceedWithClose;
}
@Override
protected void refreshBeanFactory() {
}
@Override
protected void closeBeanFactory() {
}
@Override
protected void onClose() {
if (this.closing != null) {
this.closing.countDown();
}
if (this.proceedWithClose != null) {
try {
this.proceedWithClose.await(1, TimeUnit.MINUTES);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
}
this.finished.add(this);
}
@Override
public ConfigurableListableBeanFactory getBeanFactory() {
return this.beanFactory;
}
}
static class TestHandlerAction implements Runnable {
private final List<Object> finished;
TestHandlerAction(List<Object> finished) {
this.finished = finished;
}
@Override
public void run() {
this.finished.add(this);
}
}
static class CloseContextAndExit implements InitializingBean {
private final ConfigurableApplicationContext context;
private final Runnable shutdownHook;
CloseContextAndExit(ConfigurableApplicationContext context, SpringApplicationShutdownHook shutdownHook) {
this.context = context;
this.shutdownHook = shutdownHook;
}
@Override
public void afterPropertiesSet() throws Exception {
this.context.close();
// Simulate System.exit by running the hook on a separate thread and waiting
// for it to complete
Thread thread = new Thread(this.shutdownHook);
thread.start();
thread.join(15000);
assertThat(thread.isAlive()).isFalse();
}
}
static class FailingBean implements InitializingBean {
@Override
public void afterPropertiesSet() throws Exception {
throw new IllegalArgumentException("test failure");
}
}
}
Source
Analyze Your Own Codebase
Get architecture documentation, dependency graphs, and domain analysis for your codebase in minutes.
Try Supermodel Free