Skip to content

ikucuze/spring-framework-issues-31811

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

2 Commits
 
 
 
 
 
 

Repository files navigation

spring-framework-issues-31811

Exhibits a deadlock in AbstractApplicationContext that randomly occurs.

By registering multiple ShutdownHooks, the deadlock can be reproduced more easily.

spring-projects/spring-framework#31811

incriminating code

	private boolean isStartupShutdownThreadStuck() {
		Thread activeThread = this.startupShutdownThread;
		if (activeThread != null && activeThread.getState() == Thread.State.WAITING) {
			// Indefinitely waiting: might be Thread.join or the like, or System.exit
			activeThread.interrupt();
			try {
				// Leave just a little bit of time for the interruption to show effect
				Thread.sleep(1);
			}
			catch (InterruptedException ex) {
				Thread.currentThread().interrupt();
			}
			if (activeThread.getState() == Thread.State.WAITING) {
				// Interrupted but still waiting: very likely a System.exit call
				return true;
			}
		}
		return false;
	}

This tries to guess that the thread calling the context close org.springframework.context.support.AbstractApplicationContext has been called by JVM shutdown hook SpringApplicationShutdownHook.

This code assumes that the code in java.lang.ApplicationShutdownHooks.runHooks will have always finished spawning all the shutdown hooks before, and will be waiting on the join() instruction, and able to run to it again upon interrupting it.

For reference:

    for (Thread hook : threads) {
        hook.start();
    }
    for (Thread hook : threads) {
        while (true) {
            try {
                hook.join();
                break;
            } catch (InterruptedException ignored) {
            }
        }
    }

stack traces

main thread: (calling SpringApplication.run, PostConstruct, and System.exit)

"main" #1 prio=5 os_prio=0 cpu=1546.88ms elapsed=25.02s tid=0x000002007dc43850 nid=0x7c9c in Object.wait()  [0x00000039a4efd000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(java.base@17.0.12/Native Method)
        - waiting on <0x0000000711600080> (a java.lang.Thread)
        at java.lang.Thread.join(java.base@17.0.12/Thread.java:1313)
        - locked <0x0000000711600080> (a java.lang.Thread)
        at java.lang.Thread.join(java.base@17.0.12/Thread.java:1381)
        at java.lang.ApplicationShutdownHooks.runHooks(java.base@17.0.12/ApplicationShutdownHooks.java:107)
        at java.lang.ApplicationShutdownHooks$1.run(java.base@17.0.12/ApplicationShutdownHooks.java:46)
        at java.lang.Shutdown.runHooks(java.base@17.0.12/Shutdown.java:130)
        at java.lang.Shutdown.exit(java.base@17.0.12/Shutdown.java:173)
        - locked <0x0000000703e00378> (a java.lang.Class for java.lang.Shutdown)
        ...

SpringApplicationShutdownHook thread:

"SpringApplicationShutdownHook" #17 prio=5 os_prio=0 cpu=0.00ms elapsed=23.46s tid=0x0000020067af9420 nid=0x9210 waiting on condition  [0x00000039aa7ff000]
   java.lang.Thread.State: WAITING (parking)
        at jdk.internal.misc.Unsafe.park(java.base@17.0.12/Native Method)
        - parking to wait for  <0x0000000711630a60> (a java.util.concurrent.locks.ReentrantLock$NonfairSync)
        at java.util.concurrent.locks.LockSupport.park(java.base@17.0.12/LockSupport.java:211)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.12/AbstractQueuedSynchronizer.java:715)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.base@17.0.12/AbstractQueuedSynchronizer.java:938)
        at java.util.concurrent.locks.ReentrantLock$Sync.lock(java.base@17.0.12/ReentrantLock.java:153)
        at java.util.concurrent.locks.ReentrantLock.lock(java.base@17.0.12/ReentrantLock.java:322)
        at org.springframework.context.support.AbstractApplicationContext.close(AbstractApplicationContext.java:1136)
        at org.springframework.boot.SpringApplicationShutdownHook.closeAndWait(SpringApplicationShutdownHook.java:147)
        at org.springframework.boot.SpringApplicationShutdownHook$$Lambda$506/0x00000200221d0000.accept(Unknown Source)
        at java.lang.Iterable.forEach(java.base@17.0.12/Iterable.java:75)
        at org.springframework.boot.SpringApplicationShutdownHook.run(SpringApplicationShutdownHook.java:116)
        at java.lang.Thread.run(java.base@17.0.12/Thread.java:840)

This show that AbstractApplicationContext.java:1136 is called, thus isStartupShutdownThreadStuck() is returning false, not performing well its detection.

    public void close() {
        if (isStartupShutdownThreadStuck()) {
            this.active.set(false);
            return;
        }

        this.startupShutdownLock.lock(); // line 1136
        // redacted
    }

Proposed correction

By detecting JVM shutdown, as stated by the javadoc "through a {@code System.exit} call in a user component."

When ApplicationShutdownHooks is running the hooks, the list is nullified in runHooks() (hooks = null;).

This cannot be accessed directly, but can be exhibited by trying to register a new shutdown hook, that would lead to a throw new IllegalArgumentException("Hook already running").

    private boolean isStartupShutdownThreadStuck() {
        try {
            Thread thread = new Thread();
            Runtime.getRuntime().addShutdownHook(thread);
            // clean up immediately just in case it did not raise an exception
            Runtime.getRuntime().removeShutdownHook(thread);
        } catch (IllegalStateException ex) {
            // Shutdown in progress
            return true;
        }

        return false;
    }

Alternative: detected call from SpringApplicationShutdownHook

This would required ConfigurableApplicationContext from spring to expose a parameter for SpringApplicationShutdownHook from spring boot.

This sound akward to me...

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages