Skip to content

Commit a824b23

Browse files
committed
Merge branch '3.3.x'
Closes gh-43536
2 parents 66f1b3a + 21203f0 commit a824b23

File tree

2 files changed

+58
-9
lines changed

2 files changed

+58
-9
lines changed

Diff for: spring-boot-project/spring-boot/src/main/java/org/springframework/boot/SpringApplicationShutdownHook.java

+37-8
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot;
1818

1919
import java.util.Collections;
20-
import java.util.IdentityHashMap;
2120
import java.util.LinkedHashSet;
2221
import java.util.Set;
2322
import java.util.WeakHashMap;
@@ -104,16 +103,16 @@ void deregisterFailedApplicationContext(ConfigurableApplicationContext applicati
104103
public void run() {
105104
Set<ConfigurableApplicationContext> contexts;
106105
Set<ConfigurableApplicationContext> closedContexts;
107-
Set<Runnable> actions;
106+
Set<Handler> handlers;
108107
synchronized (SpringApplicationShutdownHook.class) {
109108
this.inProgress = true;
110109
contexts = new LinkedHashSet<>(this.contexts);
111110
closedContexts = new LinkedHashSet<>(this.closedContexts);
112-
actions = new LinkedHashSet<>(this.handlers.getActions());
111+
handlers = new LinkedHashSet<>(this.handlers.getActions());
113112
}
114113
contexts.forEach(this::closeAndWait);
115114
closedContexts.forEach(this::closeAndWait);
116-
actions.forEach(Runnable::run);
115+
handlers.forEach(Handler::run);
117116
}
118117

119118
boolean isApplicationContextRegistered(ConfigurableApplicationContext context) {
@@ -171,15 +170,15 @@ private void assertNotInProgress() {
171170
*/
172171
private final class Handlers implements SpringApplicationShutdownHandlers, Runnable {
173172

174-
private final Set<Runnable> actions = Collections.newSetFromMap(new IdentityHashMap<>());
173+
private final Set<Handler> actions = new LinkedHashSet<>();
175174

176175
@Override
177176
public void add(Runnable action) {
178177
Assert.notNull(action, "Action must not be null");
179178
addRuntimeShutdownHookIfNecessary();
180179
synchronized (SpringApplicationShutdownHook.class) {
181180
assertNotInProgress();
182-
this.actions.add(action);
181+
this.actions.add(new Handler(action));
183182
}
184183
}
185184

@@ -188,11 +187,11 @@ public void remove(Runnable action) {
188187
Assert.notNull(action, "Action must not be null");
189188
synchronized (SpringApplicationShutdownHook.class) {
190189
assertNotInProgress();
191-
this.actions.remove(action);
190+
this.actions.remove(new Handler(action));
192191
}
193192
}
194193

195-
Set<Runnable> getActions() {
194+
Set<Handler> getActions() {
196195
return this.actions;
197196
}
198197

@@ -204,6 +203,36 @@ public void run() {
204203

205204
}
206205

206+
/**
207+
* A single handler that uses object identity for {@link #equals(Object)} and
208+
* {@link #hashCode()}.
209+
*
210+
* @param runnable the handler runner
211+
*/
212+
record Handler(Runnable runnable) {
213+
214+
@Override
215+
public int hashCode() {
216+
return System.identityHashCode(this.runnable);
217+
}
218+
219+
@Override
220+
public boolean equals(Object obj) {
221+
if (this == obj) {
222+
return true;
223+
}
224+
if (obj == null || getClass() != obj.getClass()) {
225+
return false;
226+
}
227+
return this.runnable == ((Handler) obj).runnable;
228+
}
229+
230+
void run() {
231+
this.runnable.run();
232+
}
233+
234+
}
235+
207236
/**
208237
* {@link ApplicationListener} to track closed contexts.
209238
*/

Diff for: spring-boot-project/spring-boot/src/test/java/org/springframework/boot/SpringApplicationShutdownHookTests.java

+21-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2023 the original author or authors.
2+
* Copyright 2012-2024 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -26,6 +26,7 @@
2626

2727
import org.awaitility.Awaitility;
2828
import org.junit.jupiter.api.Test;
29+
import org.mockito.InOrder;
2930

3031
import org.springframework.beans.factory.BeanCreationException;
3132
import org.springframework.beans.factory.InitializingBean;
@@ -39,6 +40,8 @@
3940
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
4041
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
4142
import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
43+
import static org.mockito.Mockito.inOrder;
44+
import static org.mockito.Mockito.mock;
4245

4346
/**
4447
* Tests for {@link SpringApplicationShutdownHook}.
@@ -203,6 +206,23 @@ void deregistersFailedContext() {
203206
assertThat(shutdownHook.isApplicationContextRegistered(context)).isFalse();
204207
}
205208

209+
@Test
210+
void handlersRunInDeterministicOrder() {
211+
TestSpringApplicationShutdownHook shutdownHook = new TestSpringApplicationShutdownHook();
212+
Runnable r1 = mock(Runnable.class);
213+
Runnable r2 = mock(Runnable.class);
214+
Runnable r3 = mock(Runnable.class);
215+
shutdownHook.getHandlers().add(r2);
216+
shutdownHook.getHandlers().add(r1);
217+
shutdownHook.getHandlers().add(r3);
218+
shutdownHook.run();
219+
InOrder ordered = inOrder(r1, r2, r3);
220+
ordered.verify(r2).run();
221+
ordered.verify(r1).run();
222+
ordered.verify(r3).run();
223+
ordered.verifyNoMoreInteractions();
224+
}
225+
206226
static class TestSpringApplicationShutdownHook extends SpringApplicationShutdownHook {
207227

208228
private boolean runtimeShutdownHookAdded;

0 commit comments

Comments
 (0)