@@ -205,9 +205,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
205205 /** Flag that indicates whether this context has been closed already. */
206206 private final AtomicBoolean closed = new AtomicBoolean ();
207207
208- /** Synchronization lock for the "refresh" and "destroy ". */
208+ /** Synchronization lock for "refresh" and "close ". */
209209 private final Lock startupShutdownLock = new ReentrantLock ();
210210
211+ /** Currently active startup/shutdown thread. */
212+ @ Nullable
213+ private volatile Thread startupShutdownThread ;
214+
211215 /** Reference to the JVM shutdown hook, if registered. */
212216 @ Nullable
213217 private Thread shutdownHook ;
@@ -580,6 +584,8 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
580584 public void refresh () throws BeansException , IllegalStateException {
581585 this .startupShutdownLock .lock ();
582586 try {
587+ this .startupShutdownThread = Thread .currentThread ();
588+
583589 StartupStep contextRefresh = this .applicationStartup .start ("spring.context.refresh" );
584590
585591 // Prepare this context for refreshing.
@@ -643,6 +649,7 @@ public void refresh() throws BeansException, IllegalStateException {
643649 }
644650 }
645651 finally {
652+ this .startupShutdownThread = null ;
646653 this .startupShutdownLock .unlock ();
647654 }
648655 }
@@ -1022,20 +1029,47 @@ public void registerShutdownHook() {
10221029 this .shutdownHook = new Thread (SHUTDOWN_HOOK_THREAD_NAME ) {
10231030 @ Override
10241031 public void run () {
1025- if (startupShutdownLock .tryLock ()) {
1026- try {
1027- doClose ();
1028- }
1029- finally {
1030- startupShutdownLock .unlock ();
1031- }
1032+ if (isStartupShutdownThreadStuck ()) {
1033+ active .set (false );
1034+ return ;
1035+ }
1036+ startupShutdownLock .lock ();
1037+ try {
1038+ doClose ();
1039+ }
1040+ finally {
1041+ startupShutdownLock .unlock ();
10321042 }
10331043 }
10341044 };
10351045 Runtime .getRuntime ().addShutdownHook (this .shutdownHook );
10361046 }
10371047 }
10381048
1049+ /**
1050+ * Determine whether an active startup/shutdown thread is currently stuck,
1051+ * e.g. through a {@code System.exit} call in a user component.
1052+ */
1053+ private boolean isStartupShutdownThreadStuck () {
1054+ Thread activeThread = this .startupShutdownThread ;
1055+ if (activeThread != null && activeThread .getState () == Thread .State .WAITING ) {
1056+ // Indefinitely waiting: might be Thread.join or the like, or System.exit
1057+ activeThread .interrupt ();
1058+ try {
1059+ // Leave just a little bit of time for the interruption to show effect
1060+ Thread .sleep (1 );
1061+ }
1062+ catch (InterruptedException ex ) {
1063+ Thread .currentThread ().interrupt ();
1064+ }
1065+ if (activeThread .getState () == Thread .State .WAITING ) {
1066+ // Interrupted but still waiting: very likely a System.exit call
1067+ return true ;
1068+ }
1069+ }
1070+ return false ;
1071+ }
1072+
10391073 /**
10401074 * Close this application context, destroying all beans in its bean factory.
10411075 * <p>Delegates to {@code doClose()} for the actual closing procedure.
@@ -1045,23 +1079,31 @@ public void run() {
10451079 */
10461080 @ Override
10471081 public void close () {
1048- if (this .startupShutdownLock .tryLock ()) {
1049- try {
1050- doClose ();
1051- // If we registered a JVM shutdown hook, we don't need it anymore now:
1052- // We've already explicitly closed the context.
1053- if (this .shutdownHook != null ) {
1054- try {
1055- Runtime .getRuntime ().removeShutdownHook (this .shutdownHook );
1056- }
1057- catch (IllegalStateException ex ) {
1058- // ignore - VM is already shutting down
1059- }
1082+ if (isStartupShutdownThreadStuck ()) {
1083+ this .active .set (false );
1084+ return ;
1085+ }
1086+
1087+ this .startupShutdownLock .lock ();
1088+ try {
1089+ this .startupShutdownThread = Thread .currentThread ();
1090+
1091+ doClose ();
1092+
1093+ // If we registered a JVM shutdown hook, we don't need it anymore now:
1094+ // We've already explicitly closed the context.
1095+ if (this .shutdownHook != null ) {
1096+ try {
1097+ Runtime .getRuntime ().removeShutdownHook (this .shutdownHook );
1098+ }
1099+ catch (IllegalStateException ex ) {
1100+ // ignore - VM is already shutting down
10601101 }
10611102 }
1062- finally {
1063- this .startupShutdownLock .unlock ();
1064- }
1103+ }
1104+ finally {
1105+ this .startupShutdownThread = null ;
1106+ this .startupShutdownLock .unlock ();
10651107 }
10661108 }
10671109
0 commit comments