There is a minimal example of the bug occurring here https://github.com/WillHolbrook/example-springboot-bug
This is using spring boot 2.7.16 as currently I still need to use JMS/RMI
There is a workaround by updating the method that calls System.exit() to be aware of the spring lifecycle by looking at a property on application listener of the closed event and only calling exit when not in the already closing state my example is something like
@Bean
public ApplicationListener<ContextClosedEvent> closedApplicationListener(GuiMain guiMain) {
return new ApplicationListener<ContextClosedEvent>() {
@Override
public void onApplicationEvent(ContextClosedEvent event) {
guiMain.setSpringApplicationStopping(true);
}
};
}
The bug occurs when in a main JFrame it calls System.exit() (In this case it calls it by the default close method being exit on close) and then it has a deletion hook that also calls System.exit() and when it gets called as part of the context shut down process it causes the program to hang and can't close the GUI without a kill -9.
In the exampe I have given the bean self registers for destruction by having a close() method sadly in my case I can't control the fact it has a close method as the dependency I require has the close method implemented
Hope this helps
Comment From: WillHolbrook
Part of this behavior comes from the method inferDestroyMethodIfNecessary() in the class org/springframework/beans/factory/support/DisposableBeanAdapter.java
Comment From: wilkinsona
Spring Boot 2.7.x is no longer supported. Please upgrade to Spring Boot 3.2.x or later as soon as possible.
I've upgrade the sample to 3.2.x and the problem still occurs. Unfortunately, I don't think there's anything that we can do about it in Spring Boot or Spring Framework as the locks that are involved are out of our control. When running with 2.7.x, they are:
"AWT-EventQueue-0" #30 prio=6 os_prio=31 cpu=295.87ms elapsed=12.08s tid=0x00007f815f15c400 nid=0x12407 in Object.wait() [0x0000700011d85000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(java.base@17.0.11/Native Method)
- waiting on <0x000000061dcd9410> (a java.lang.Thread)
at java.lang.Thread.join(java.base@17.0.11/Thread.java:1313)
- locked <0x000000061dcd9410> (a java.lang.Thread)
at java.lang.Thread.join(java.base@17.0.11/Thread.java:1381)
at java.lang.ApplicationShutdownHooks.runHooks(java.base@17.0.11/ApplicationShutdownHooks.java:107)
at java.lang.ApplicationShutdownHooks$1.run(java.base@17.0.11/ApplicationShutdownHooks.java:46)
at java.lang.Shutdown.runHooks(java.base@17.0.11/Shutdown.java:130)
at java.lang.Shutdown.exit(java.base@17.0.11/Shutdown.java:173)
- locked <0x00000007ffb020b8> (a java.lang.Class for java.lang.Shutdown)
at java.lang.Runtime.exit(java.base@17.0.11/Runtime.java:115)
at java.lang.System.exit(java.base@17.0.11/System.java:1864)
at org.example.SampleConfiguration$2.windowClosing(SampleConfiguration.java:29)
at java.awt.AWTEventMulticaster.windowClosing(java.desktop@17.0.11/AWTEventMulticaster.java:357)
at java.awt.Window.processWindowEvent(java.desktop@17.0.11/Window.java:2085)
at javax.swing.JFrame.processWindowEvent(java.desktop@17.0.11/JFrame.java:298)
at java.awt.Window.processEvent(java.desktop@17.0.11/Window.java:2044)
at java.awt.Component.dispatchEventImpl(java.desktop@17.0.11/Component.java:5001)
at java.awt.Container.dispatchEventImpl(java.desktop@17.0.11/Container.java:2324)
at java.awt.Window.dispatchEventImpl(java.desktop@17.0.11/Window.java:2780)
at java.awt.Component.dispatchEvent(java.desktop@17.0.11/Component.java:4833)
at java.awt.EventQueue.dispatchEventImpl(java.desktop@17.0.11/EventQueue.java:775)
at java.awt.EventQueue$4.run(java.desktop@17.0.11/EventQueue.java:720)
at java.awt.EventQueue$4.run(java.desktop@17.0.11/EventQueue.java:714)
at java.security.AccessController.executePrivileged(java.base@17.0.11/AccessController.java:776)
at java.security.AccessController.doPrivileged(java.base@17.0.11/AccessController.java:399)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(java.base@17.0.11/ProtectionDomain.java:86)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(java.base@17.0.11/ProtectionDomain.java:97)
at java.awt.EventQueue$5.run(java.desktop@17.0.11/EventQueue.java:747)
at java.awt.EventQueue$5.run(java.desktop@17.0.11/EventQueue.java:745)
at java.security.AccessController.executePrivileged(java.base@17.0.11/AccessController.java:776)
at java.security.AccessController.doPrivileged(java.base@17.0.11/AccessController.java:399)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(java.base@17.0.11/ProtectionDomain.java:86)
at java.awt.EventQueue.dispatchEvent(java.desktop@17.0.11/EventQueue.java:744)
at java.awt.EventDispatchThread.pumpOneEventForFilters(java.desktop@17.0.11/EventDispatchThread.java:203)
at java.awt.EventDispatchThread.pumpEventsForFilter(java.desktop@17.0.11/EventDispatchThread.java:124)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(java.desktop@17.0.11/EventDispatchThread.java:113)
at java.awt.EventDispatchThread.pumpEvents(java.desktop@17.0.11/EventDispatchThread.java:109)
at java.awt.EventDispatchThread.pumpEvents(java.desktop@17.0.11/EventDispatchThread.java:101)
at java.awt.EventDispatchThread.run(java.desktop@17.0.11/EventDispatchThread.java:90)
"SpringApplicationShutdownHook" #24 prio=5 os_prio=31 cpu=3.04ms elapsed=10.32s tid=0x00007f815f9c8800 nid=0x1e75f waiting for monitor entry [0x0000700011e88000]
java.lang.Thread.State: BLOCKED (on object monitor)
at java.lang.Shutdown.exit(java.base@17.0.11/Shutdown.java:172)
- waiting to lock <0x00000007ffb020b8> (a java.lang.Class for java.lang.Shutdown)
at java.lang.Runtime.exit(java.base@17.0.11/Runtime.java:115)
at java.lang.System.exit(java.base@17.0.11/System.java:1864)
at org.example.SampleConfiguration$2.windowClosing(SampleConfiguration.java:29)
at java.awt.AWTEventMulticaster.windowClosing(java.desktop@17.0.11/AWTEventMulticaster.java:357)
at java.awt.Window.processWindowEvent(java.desktop@17.0.11/Window.java:2085)
at javax.swing.JFrame.processWindowEvent(java.desktop@17.0.11/JFrame.java:298)
at java.awt.Window.processEvent(java.desktop@17.0.11/Window.java:2044)
at org.example.SampleConfiguration$1.close(SampleConfiguration.java:19)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(java.base@17.0.11/Native Method)
at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(java.base@17.0.11/NativeMethodAccessorImpl.java:77)
at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(java.base@17.0.11/DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(java.base@17.0.11/Method.java:568)
at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:325)
at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:259)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:587)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:559)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1163)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:520)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1156)
at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1120)
at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1086)
at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:1032)
- locked <0x0000000600228dc0> (a java.lang.Object)
at org.springframework.boot.SpringApplicationShutdownHook.closeAndWait(SpringApplicationShutdownHook.java:145)
at org.springframework.boot.SpringApplicationShutdownHook$$Lambda$461/0x000000013c258ab0.accept(Unknown Source)
at java.lang.Iterable.forEach(java.base@17.0.11/Iterable.java:75)
at org.springframework.boot.SpringApplicationShutdownHook.run(SpringApplicationShutdownHook.java:114)
at java.lang.Thread.run(java.base@17.0.11/Thread.java:840)
The picture's similar with 3.2.x.
In AWT-EventQueue-0, Runtime.exit has locked 0x00000007ffb020b8 and is then waiting for all shutdown hook threads to have completed. In SpringApplicationShutdownHook, a shutdown hook thread, Runtime.exit is waiting to lock 0x00000007ffb020b8. It will never be able to do so as it's a shutdown hook thread and the lock won't be freed until all shutdown hook threads have been completed.
The shutdown hook thread is calling Runtime.exit as it's closing the application context. mainFrame is exposed as a bean so its close() method is called as part of closing the application context. It then calls System.exit despite exit processing already being underway. There's then a deadlock in the JVM as the two threads compete with each other.
In addition to the workaround that you have already described, two further alternatives are:
- Configure your
SpringApplicationnot to register its shutdown hook (app.setRegisterShutdownHook(false)). You'd then probably want to close the context when the application's main window is closed - Configure your
mainFramebean so that itsclose()method isn't used as a destroy method:@Bean(destroyMethod="")
Comment From: WillHolbrook
@wilkinsona Thank you I really appreciate the feedback! I think setting the destroy method to "" is a much better solution sorry for the hassle