Exhibits a deadlock in AbstractApplicationContext that randomly occurs.
By registering multiple ShutdownHooks, the deadlock can be reproduced more easily.
spring-projects/spring-framework#31811
private boolean isStartupShutdownThreadStuck() {
Thread activeThread = this.startupShutdownThread;
if (activeThread != null && activeThread.getState() == Thread.State.WAITING) {
// Indefinitely waiting: might be Thread.join or the like, or System.exit
activeThread.interrupt();
try {
// Leave just a little bit of time for the interruption to show effect
Thread.sleep(1);
}
catch (InterruptedException ex) {
Thread.currentThread().interrupt();
}
if (activeThread.getState() == Thread.State.WAITING) {
// Interrupted but still waiting: very likely a System.exit call
return true;
}
}
return false;
}This tries to guess that the thread calling the context close org.springframework.context.support.AbstractApplicationContext has been called by JVM shutdown hook SpringApplicationShutdownHook.
This code assumes that the code in java.lang.ApplicationShutdownHooks.runHooks will have always finished spawning all the shutdown hooks before, and will be waiting on the join() instruction, and able to run to it again upon interrupting it.
For reference:
for (Thread hook : threads) {
hook.start();
}
for (Thread hook : threads) {
while (true) {
try {
hook.join();
break;
} catch (InterruptedException ignored) {
}
}
}main thread: (calling SpringApplication.run, PostConstruct, and System.exit)
"main" #1 prio=5 os_prio=0 cpu=1546.88ms elapsed=25.02s tid=0x000002007dc43850 nid=0x7c9c in Object.wait() [0x00000039a4efd000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@17.0.12/Native Method)
- waiting on <0x0000000711600080> (a java.lang.Thread)
at java.lang.Thread.join(java.base@17.0.12/Thread.java:1313)
- locked <0x0000000711600080> (a java.lang.Thread)
at java.lang.Thread.join(java.base@17.0.12/Thread.java:1381)
at java.lang.ApplicationShutdownHooks.runHooks(java.base@17.0.12/ApplicationShutdownHooks.java:107)
at java.lang.ApplicationShutdownHooks$1.run(java.base@17.0.12/ApplicationShutdownHooks.java:46)
at java.lang.Shutdown.runHooks(java.base@17.0.12/Shutdown.java:130)
at java.lang.Shutdown.exit(java.base@17.0.12/Shutdown.java:173)
- locked <0x0000000703e00378> (a java.lang.Class for java.lang.Shutdown)
...
SpringApplicationShutdownHook thread:
"SpringApplicationShutdownHook" #17 prio=5 os_prio=0 cpu=0.00ms elapsed=23.46s tid=0x0000020067af9420 nid=0x9210 waiting on condition [0x00000039aa7ff000]
java.lang.Thread.State: WAITING (parking)
at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
- parking to wait for <0x0000000711630a60> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:211)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.12/AbstractQueuedSynchronizer.java:715)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.12/AbstractQueuedSynchronizer.java:938)
at java.util.concurrent.locks.ReentrantLock$Sync.lock(java.base@17.0.12/ReentrantLock.java:153)
at java.util.concurrent.locks.ReentrantLock.lock(java.base@17.0.12/ReentrantLock.java:322)
at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:1136)
at org.springframework.boot.SpringApplicationShutdownHook.closeAndWait(SpringApplicationShutdownHook.java:147)
at org.springframework.boot.SpringApplicationShutdownHook$$Lambda$506/0x00000200221d0000.accept(Unknown Source)
at java.lang.Iterable.forEach(java.base@17.0.12/Iterable.java:75)
at org.springframework.boot.SpringApplicationShutdownHook.run(SpringApplicationShutdownHook.java:116)
at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)
This show that AbstractApplicationContext.java:1136 is called, thus isStartupShutdownThreadStuck() is returning false, not performing well its detection.
public void close() {
if (isStartupShutdownThreadStuck()) {
this.active.set(false);
return;
}
this.startupShutdownLock.lock(); // line 1136
// redacted
}By detecting JVM shutdown, as stated by the javadoc "through a {@code System.exit} call in a user component."
When ApplicationShutdownHooks is running the hooks, the list is nullified in runHooks() (hooks = null;).
This cannot be accessed directly, but can be exhibited by trying to register a new shutdown hook, that would lead to a throw new IllegalArgumentException("Hook already running").
private boolean isStartupShutdownThreadStuck() {
try {
Thread thread = new Thread();
Runtime.getRuntime().addShutdownHook(thread);
// clean up immediately just in case it did not raise an exception
Runtime.getRuntime().removeShutdownHook(thread);
} catch (IllegalStateException ex) {
// Shutdown in progress
return true;
}
return false;
}This would required ConfigurableApplicationContext from spring to expose a parameter for SpringApplicationShutdownHook from spring boot.
This sound akward to me...