@@ -205,9 +205,13 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader
205
205
/** Flag that indicates whether this context has been closed already. */
206
206
private final AtomicBoolean closed = new AtomicBoolean ();
207
207
208
- /** Synchronization lock for the "refresh" and "destroy ". */
208
+ /** Synchronization lock for "refresh" and "close ". */
209
209
private final Lock startupShutdownLock = new ReentrantLock ();
210
210
211
+ /** Currently active startup/shutdown thread. */
212
+ @ Nullable
213
+ private volatile Thread startupShutdownThread ;
214
+
211
215
/** Reference to the JVM shutdown hook, if registered. */
212
216
@ Nullable
213
217
private Thread shutdownHook ;
@@ -580,6 +584,8 @@ public Collection<ApplicationListener<?>> getApplicationListeners() {
580
584
public void refresh () throws BeansException , IllegalStateException {
581
585
this .startupShutdownLock .lock ();
582
586
try {
587
+ this .startupShutdownThread = Thread .currentThread ();
588
+
583
589
StartupStep contextRefresh = this .applicationStartup .start ("spring.context.refresh" );
584
590
585
591
// Prepare this context for refreshing.
@@ -643,6 +649,7 @@ public void refresh() throws BeansException, IllegalStateException {
643
649
}
644
650
}
645
651
finally {
652
+ this .startupShutdownThread = null ;
646
653
this .startupShutdownLock .unlock ();
647
654
}
648
655
}
@@ -1022,20 +1029,47 @@ public void registerShutdownHook() {
1022
1029
this .shutdownHook = new Thread (SHUTDOWN_HOOK_THREAD_NAME ) {
1023
1030
@ Override
1024
1031
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 ();
1032
1042
}
1033
1043
}
1034
1044
};
1035
1045
Runtime .getRuntime ().addShutdownHook (this .shutdownHook );
1036
1046
}
1037
1047
}
1038
1048
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
+
1039
1073
/**
1040
1074
* Close this application context, destroying all beans in its bean factory.
1041
1075
* <p>Delegates to {@code doClose()} for the actual closing procedure.
@@ -1045,23 +1079,31 @@ public void run() {
1045
1079
*/
1046
1080
@ Override
1047
1081
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
1060
1101
}
1061
1102
}
1062
- finally {
1063
- this .startupShutdownLock .unlock ();
1064
- }
1103
+ }
1104
+ finally {
1105
+ this .startupShutdownThread = null ;
1106
+ this .startupShutdownLock .unlock ();
1065
1107
}
1066
1108
}
1067
1109
0 commit comments