Check startup/shutdown thread state for close bypass in shutdown hook · spring-projects/spring-framework@a612518 (original) (raw)

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

`+

`

``

1051

`+

`

``

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

` *

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

``