commit
a824b23cd7
|
@ -17,7 +17,6 @@
|
||||||
package org.springframework.boot;
|
package org.springframework.boot;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.IdentityHashMap;
|
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
|
@ -104,16 +103,16 @@ class SpringApplicationShutdownHook implements Runnable {
|
||||||
public void run() {
|
public void run() {
|
||||||
Set<ConfigurableApplicationContext> contexts;
|
Set<ConfigurableApplicationContext> contexts;
|
||||||
Set<ConfigurableApplicationContext> closedContexts;
|
Set<ConfigurableApplicationContext> closedContexts;
|
||||||
Set<Runnable> actions;
|
Set<Handler> handlers;
|
||||||
synchronized (SpringApplicationShutdownHook.class) {
|
synchronized (SpringApplicationShutdownHook.class) {
|
||||||
this.inProgress = true;
|
this.inProgress = true;
|
||||||
contexts = new LinkedHashSet<>(this.contexts);
|
contexts = new LinkedHashSet<>(this.contexts);
|
||||||
closedContexts = new LinkedHashSet<>(this.closedContexts);
|
closedContexts = new LinkedHashSet<>(this.closedContexts);
|
||||||
actions = new LinkedHashSet<>(this.handlers.getActions());
|
handlers = new LinkedHashSet<>(this.handlers.getActions());
|
||||||
}
|
}
|
||||||
contexts.forEach(this::closeAndWait);
|
contexts.forEach(this::closeAndWait);
|
||||||
closedContexts.forEach(this::closeAndWait);
|
closedContexts.forEach(this::closeAndWait);
|
||||||
actions.forEach(Runnable::run);
|
handlers.forEach(Handler::run);
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean isApplicationContextRegistered(ConfigurableApplicationContext context) {
|
boolean isApplicationContextRegistered(ConfigurableApplicationContext context) {
|
||||||
|
@ -171,7 +170,7 @@ class SpringApplicationShutdownHook implements Runnable {
|
||||||
*/
|
*/
|
||||||
private final class Handlers implements SpringApplicationShutdownHandlers, Runnable {
|
private final class Handlers implements SpringApplicationShutdownHandlers, Runnable {
|
||||||
|
|
||||||
private final Set<Runnable> actions = Collections.newSetFromMap(new IdentityHashMap<>());
|
private final Set<Handler> actions = new LinkedHashSet<>();
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void add(Runnable action) {
|
public void add(Runnable action) {
|
||||||
|
@ -179,7 +178,7 @@ class SpringApplicationShutdownHook implements Runnable {
|
||||||
addRuntimeShutdownHookIfNecessary();
|
addRuntimeShutdownHookIfNecessary();
|
||||||
synchronized (SpringApplicationShutdownHook.class) {
|
synchronized (SpringApplicationShutdownHook.class) {
|
||||||
assertNotInProgress();
|
assertNotInProgress();
|
||||||
this.actions.add(action);
|
this.actions.add(new Handler(action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -188,11 +187,11 @@ class SpringApplicationShutdownHook implements Runnable {
|
||||||
Assert.notNull(action, "Action must not be null");
|
Assert.notNull(action, "Action must not be null");
|
||||||
synchronized (SpringApplicationShutdownHook.class) {
|
synchronized (SpringApplicationShutdownHook.class) {
|
||||||
assertNotInProgress();
|
assertNotInProgress();
|
||||||
this.actions.remove(action);
|
this.actions.remove(new Handler(action));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<Runnable> getActions() {
|
Set<Handler> getActions() {
|
||||||
return this.actions;
|
return this.actions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -204,6 +203,36 @@ class SpringApplicationShutdownHook implements Runnable {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A single handler that uses object identity for {@link #equals(Object)} and
|
||||||
|
* {@link #hashCode()}.
|
||||||
|
*
|
||||||
|
* @param runnable the handler runner
|
||||||
|
*/
|
||||||
|
record Handler(Runnable runnable) {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return System.identityHashCode(this.runnable);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return this.runnable == ((Handler) obj).runnable;
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() {
|
||||||
|
this.runnable.run();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@link ApplicationListener} to track closed contexts.
|
* {@link ApplicationListener} to track closed contexts.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2012-2023 the original author or authors.
|
* Copyright 2012-2024 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,6 +26,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import org.awaitility.Awaitility;
|
import org.awaitility.Awaitility;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.mockito.InOrder;
|
||||||
|
|
||||||
import org.springframework.beans.factory.BeanCreationException;
|
import org.springframework.beans.factory.BeanCreationException;
|
||||||
import org.springframework.beans.factory.InitializingBean;
|
import org.springframework.beans.factory.InitializingBean;
|
||||||
|
@ -39,6 +40,8 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
|
||||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
|
||||||
|
import static org.mockito.Mockito.inOrder;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link SpringApplicationShutdownHook}.
|
* Tests for {@link SpringApplicationShutdownHook}.
|
||||||
|
@ -203,6 +206,23 @@ class SpringApplicationShutdownHookTests {
|
||||||
assertThat(shutdownHook.isApplicationContextRegistered(context)).isFalse();
|
assertThat(shutdownHook.isApplicationContextRegistered(context)).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void handlersRunInDeterministicOrder() {
|
||||||
|
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(r2).run();
|
||||||
|
ordered.verify(r1).run();
|
||||||
|
ordered.verify(r3).run();
|
||||||
|
ordered.verifyNoMoreInteractions();
|
||||||
|
}
|
||||||
|
|
||||||
static class TestSpringApplicationShutdownHook extends SpringApplicationShutdownHook {
|
static class TestSpringApplicationShutdownHook extends SpringApplicationShutdownHook {
|
||||||
|
|
||||||
private boolean runtimeShutdownHookAdded;
|
private boolean runtimeShutdownHookAdded;
|
||||||
|
|
Loading…
Reference in New Issue