|
|
|
@ -27,6 +27,7 @@ import java.util.Locale; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Map; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.Set; |
|
|
|
import java.util.concurrent.Executor; |
|
|
|
import java.util.concurrent.Executor; |
|
|
|
|
|
|
|
import java.util.concurrent.TimeUnit; |
|
|
|
import java.util.concurrent.atomic.AtomicBoolean; |
|
|
|
import java.util.concurrent.atomic.AtomicBoolean; |
|
|
|
import java.util.concurrent.locks.Lock; |
|
|
|
import java.util.concurrent.locks.Lock; |
|
|
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
|
import java.util.concurrent.locks.ReentrantLock; |
|
|
|
@ -1070,11 +1071,9 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader |
|
|
|
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) { |
|
|
|
this.shutdownHook = new Thread(SHUTDOWN_HOOK_THREAD_NAME) { |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void run() { |
|
|
|
public void run() { |
|
|
|
if (isStartupShutdownThreadStuck()) { |
|
|
|
if (!tryLockForShutdown()) { |
|
|
|
active.set(false); |
|
|
|
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
startupShutdownLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
doClose(); |
|
|
|
doClose(); |
|
|
|
} |
|
|
|
} |
|
|
|
@ -1087,6 +1086,30 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
|
|
|
|
* Try to acquire the common startup/shutdown lock, backing out if |
|
|
|
|
|
|
|
* the main startup/shutdown thread is stuck or on interruption. |
|
|
|
|
|
|
|
* @see #isStartupShutdownThreadStuck() |
|
|
|
|
|
|
|
*/ |
|
|
|
|
|
|
|
private boolean tryLockForShutdown() { |
|
|
|
|
|
|
|
try { |
|
|
|
|
|
|
|
while (!this.startupShutdownLock.tryLock(100, TimeUnit.MILLISECONDS)) { |
|
|
|
|
|
|
|
if (!this.active.get() || this.closed.get()) { |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
if (isStartupShutdownThreadStuck()) { |
|
|
|
|
|
|
|
this.active.set(false); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
return true; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
catch (InterruptedException ex) { |
|
|
|
|
|
|
|
Thread.currentThread().interrupt(); |
|
|
|
|
|
|
|
return false; |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/** |
|
|
|
/** |
|
|
|
* Determine whether an active startup/shutdown thread is currently stuck, |
|
|
|
* Determine whether an active startup/shutdown thread is currently stuck, |
|
|
|
* for example, through a {@code System.exit} call in a user component. |
|
|
|
* for example, through a {@code System.exit} call in a user component. |
|
|
|
@ -1098,7 +1121,7 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader |
|
|
|
activeThread.interrupt(); |
|
|
|
activeThread.interrupt(); |
|
|
|
try { |
|
|
|
try { |
|
|
|
// Leave just a little bit of time for the interruption to show effect
|
|
|
|
// Leave just a little bit of time for the interruption to show effect
|
|
|
|
Thread.sleep(1); |
|
|
|
Thread.sleep(10); |
|
|
|
} |
|
|
|
} |
|
|
|
catch (InterruptedException ex) { |
|
|
|
catch (InterruptedException ex) { |
|
|
|
Thread.currentThread().interrupt(); |
|
|
|
Thread.currentThread().interrupt(); |
|
|
|
@ -1120,12 +1143,10 @@ public abstract class AbstractApplicationContext extends DefaultResourceLoader |
|
|
|
*/ |
|
|
|
*/ |
|
|
|
@Override |
|
|
|
@Override |
|
|
|
public void close() { |
|
|
|
public void close() { |
|
|
|
if (isStartupShutdownThreadStuck()) { |
|
|
|
if (!tryLockForShutdown()) { |
|
|
|
this.active.set(false); |
|
|
|
|
|
|
|
return; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
this.startupShutdownLock.lock(); |
|
|
|
|
|
|
|
try { |
|
|
|
try { |
|
|
|
this.startupShutdownThread = Thread.currentThread(); |
|
|
|
this.startupShutdownThread = Thread.currentThread(); |
|
|
|
|
|
|
|
|
|
|
|
|