@@ -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