Spring Boot 和 H2 关闭死锁

Springboot and H2 shutdown deadlock

目前我可以在结合使用 h2 和 spring 引导时创建死锁。那里有很多问题,看起来有点相似,但显然它们已经解决了,我不完全确定是 Spring 还是 h2 做了一些奇怪的事情。

首先关闭死锁的 visualvm 跟踪(滚动到锁的底部):


2019-07-08 12:34:47
Full thread dump OpenJDK 64-Bit Server VM (25.212-b03 mixed mode):

"RMI TCP Connection(2)-127.0.0.1" #25 daemon prio=9 os_prio=0 tid=0x00007fc19c0f6000 nid=0x2f44 runnable [0x00007fc1a2444000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
        - locked <0x00000000f5c5bdb0> (a java.io.BufferedInputStream)
        at java.io.FilterInputStream.read(FilterInputStream.java:83)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:555)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run[=10=](TCPTransport.java:688)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda7/422104739.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - <0x00000000f58d4990> (a java.util.concurrent.ThreadPoolExecutor$Worker)

"JMX server connection timeout 24" #24 daemon prio=9 os_prio=0 tid=0x00007fc19804c000 nid=0x2f42 in Object.wait() [0x00007fc1a2746000]
   java.lang.Thread.State: TIMED_WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000f59cafe0> (a [I)
        at com.sun.jmx.remote.internal.ServerCommunicatorAdmin$Timeout.run(ServerCommunicatorAdmin.java:168)
        - locked <0x00000000f59cafe0> (a [I)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"RMI Scheduler(0)" #23 daemon prio=9 os_prio=0 tid=0x00007fc198039800 nid=0x2f41 waiting on condition [0x00007fc1a32fd000]
   java.lang.Thread.State: TIMED_WAITING (parking)
        at sun.misc.Unsafe.park(Native Method)
        - parking to wait for  <0x00000000f5586878> (a java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject)
        at java.util.concurrent.locks.LockSupport.parkNanos(LockSupport.java:215)
        at java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(AbstractQueuedSynchronizer.java:2078)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:1093)
        at java.util.concurrent.ScheduledThreadPoolExecutor$DelayedWorkQueue.take(ScheduledThreadPoolExecutor.java:809)
        at java.util.concurrent.ThreadPoolExecutor.getTask(ThreadPoolExecutor.java:1074)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1134)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"RMI TCP Connection(1)-127.0.0.1" #22 daemon prio=9 os_prio=0 tid=0x00007fc19c57d800 nid=0x2f40 runnable [0x00007fc1a3dfd000]
   java.lang.Thread.State: RUNNABLE
        at java.net.SocketInputStream.socketRead0(Native Method)
        at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
        at java.net.SocketInputStream.read(SocketInputStream.java:171)
        at java.net.SocketInputStream.read(SocketInputStream.java:141)
        at java.io.BufferedInputStream.fill(BufferedInputStream.java:246)
        at java.io.BufferedInputStream.read(BufferedInputStream.java:265)
        - locked <0x00000000f598e720> (a java.io.BufferedInputStream)
        at java.io.FilterInputStream.read(FilterInputStream.java:83)
        at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:555)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:834)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.lambda$run[=10=](TCPTransport.java:688)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda7/422104739.run(Unknown Source)
        at java.security.AccessController.doPrivileged(Native Method)
        at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:687)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - <0x00000000f58d4360> (a java.util.concurrent.ThreadPoolExecutor$Worker)

"RMI TCP Accept-0" #21 daemon prio=9 os_prio=0 tid=0x00007fc1ad3a9800 nid=0x2f3e runnable [0x00007fc1b47fb000]
   java.lang.Thread.State: RUNNABLE
        at java.net.PlainSocketImpl.socketAccept(Native Method)
        at java.net.AbstractPlainSocketImpl.accept(AbstractPlainSocketImpl.java:409)
        at java.net.ServerSocket.implAccept(ServerSocket.java:545)
        at java.net.ServerSocket.accept(ServerSocket.java:513)
        at sun.management.jmxremote.LocalRMIServerSocketFactory.accept(LocalRMIServerSocketFactory.java:52)
        at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.executeAcceptLoop(TCPTransport.java:405)
        at sun.rmi.transport.tcp.TCPTransport$AcceptLoop.run(TCPTransport.java:377)
        at java.lang.Thread.run(Thread.java:748)

   Locked ownable synchronizers:
        - None

"Attach Listener" #20 daemon prio=9 os_prio=0 tid=0x00007fc1c8001000 nid=0x2f3c waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Thread-8" #19 prio=5 os_prio=0 tid=0x00007fc210870800 nid=0x2f06 waiting for monitor entry [0x00007fc1b4ffc000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.h2.command.dml.TransactionCommand.update(TransactionCommand.java:98)
        - waiting to lock <0x00000000ff487080> (a org.h2.engine.Session)
        at org.h2.command.CommandContainer.update(CommandContainer.java:133)
        at org.h2.command.Command.executeUpdate(Command.java:267)
        - locked <0x00000000ff5dc0d0> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:233)
        - locked <0x00000000ff5dc0d0> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:205)
        at org.springframework.jdbc.datasource.embedded.AbstractEmbeddedDatabaseConfigurer.shutdown(AbstractEmbeddedDatabaseConfigurer.java:47)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory.shutdownDatabase(EmbeddedDatabaseFactory.java:228)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory$EmbeddedDataSourceProxy.shutdown(EmbeddedDatabaseFactory.java:303)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:337)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:271)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:571)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:543)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1034)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:504)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1027)
        at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1057)
        at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1026)
        at org.springframework.context.support.AbstractApplicationContext.run(AbstractApplicationContext.java:945)
        - locked <0x00000000ff2b55d0> (a java.lang.Object)

   Locked ownable synchronizers:
        - None

"Thread-6" #17 prio=5 os_prio=0 tid=0x00007fc2107e7000 nid=0x2f05 waiting for monitor entry [0x00007fc1b4efb000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at org.h2.command.dml.TransactionCommand.update(TransactionCommand.java:98)
        - waiting to lock <0x00000000ff5dc0d0> (a org.h2.engine.Session)
        at org.h2.command.CommandContainer.update(CommandContainer.java:133)
        at org.h2.command.Command.executeUpdate(Command.java:267)
        - locked <0x00000000ff487080> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:233)
        - locked <0x00000000ff487080> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:205)
        at org.springframework.jdbc.datasource.embedded.AbstractEmbeddedDatabaseConfigurer.shutdown(AbstractEmbeddedDatabaseConfigurer.java:47)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory.shutdownDatabase(EmbeddedDatabaseFactory.java:228)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory$EmbeddedDataSourceProxy.shutdown(EmbeddedDatabaseFactory.java:303)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:337)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:271)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:571)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:543)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1034)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:504)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1027)
        at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1057)
        at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1026)
        at org.springframework.context.support.AbstractApplicationContext.run(AbstractApplicationContext.java:945)
        - locked <0x00000000e002b8d0> (a java.lang.Object)

   Locked ownable synchronizers:
        - None

"Service Thread" #9 daemon prio=9 os_prio=0 tid=0x00007fc210169800 nid=0x2ef5 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C1 CompilerThread3" #8 daemon prio=9 os_prio=0 tid=0x00007fc210156000 nid=0x2ef4 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread2" #7 daemon prio=9 os_prio=0 tid=0x00007fc210154000 nid=0x2ef3 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread1" #6 daemon prio=9 os_prio=0 tid=0x00007fc210152000 nid=0x2ef2 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"C2 CompilerThread0" #5 daemon prio=9 os_prio=0 tid=0x00007fc210150000 nid=0x2ef1 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Signal Dispatcher" #4 daemon prio=9 os_prio=0 tid=0x00007fc21014d800 nid=0x2ef0 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

   Locked ownable synchronizers:
        - None

"Finalizer" #3 daemon prio=8 os_prio=0 tid=0x00007fc21008c800 nid=0x2eef in Object.wait() [0x00007fc1e160c000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000e0016c68> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x00000000e0016c68> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

   Locked ownable synchronizers:
        - None

"Reference Handler" #2 daemon prio=10 os_prio=0 tid=0x00007fc21008a000 nid=0x2eee in Object.wait() [0x00007fc1e170d000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000e0016e38> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x00000000e0016e38> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

   Locked ownable synchronizers:
        - None

"main" #1 prio=5 os_prio=0 tid=0x00007fc21000b000 nid=0x2ee4 in Object.wait() [0x00007fc214f53000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x00000000ff24cda8> (a org.springframework.context.support.AbstractApplicationContext)
        at java.lang.Thread.join(Thread.java:1252)
        - locked <0x00000000ff24cda8> (a org.springframework.context.support.AbstractApplicationContext)
        at java.lang.Thread.join(Thread.java:1326)
        at java.lang.ApplicationShutdownHooks.runHooks(ApplicationShutdownHooks.java:107)
        at java.lang.ApplicationShutdownHooks.run(ApplicationShutdownHooks.java:46)
        at java.lang.Shutdown.runHooks(Shutdown.java:123)
        at java.lang.Shutdown.sequence(Shutdown.java:167)
        at java.lang.Shutdown.exit(Shutdown.java:212)
        - locked <0x00000000e039fcc8> (a java.lang.Class for java.lang.Shutdown)
        at java.lang.Runtime.exit(Runtime.java:109)
        at java.lang.System.exit(System.java:971)
        at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:68)

   Locked ownable synchronizers:
        - None

"VM Thread" os_prio=0 tid=0x00007fc210080000 nid=0x2eed runnable 

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00007fc210020000 nid=0x2ee5 runnable 

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00007fc210021800 nid=0x2ee6 runnable 

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00007fc210023800 nid=0x2ee7 runnable 

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00007fc210025000 nid=0x2ee8 runnable 

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00007fc210027000 nid=0x2ee9 runnable 

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00007fc210028800 nid=0x2eea runnable 

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00007fc21002a800 nid=0x2eeb runnable 

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00007fc21002c000 nid=0x2eec runnable 

"VM Periodic Task Thread" os_prio=0 tid=0x00007fc21016c000 nid=0x2ef6 waiting on condition 

JNI global references: 1324


Found one Java-level deadlock:
=============================
"Thread-8":
  waiting to lock monitor 0x00007fc1ad3792c8 (object 0x00000000ff487080, a org.h2.engine.Session),
  which is held by "Thread-6"
"Thread-6":
  waiting to lock monitor 0x00007fc1ad377cc8 (object 0x00000000ff5dc0d0, a org.h2.engine.Session),
  which is held by "Thread-8"

Java stack information for the threads listed above:
===================================================
"Thread-8":
        at org.h2.command.dml.TransactionCommand.update(TransactionCommand.java:98)
        - waiting to lock <0x00000000ff487080> (a org.h2.engine.Session)
        at org.h2.command.CommandContainer.update(CommandContainer.java:133)
        at org.h2.command.Command.executeUpdate(Command.java:267)
        - locked <0x00000000ff5dc0d0> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:233)
        - locked <0x00000000ff5dc0d0> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:205)
        at org.springframework.jdbc.datasource.embedded.AbstractEmbeddedDatabaseConfigurer.shutdown(AbstractEmbeddedDatabaseConfigurer.java:47)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory.shutdownDatabase(EmbeddedDatabaseFactory.java:228)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory$EmbeddedDataSourceProxy.shutdown(EmbeddedDatabaseFactory.java:303)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:337)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:271)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:571)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:543)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1034)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:504)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1027)
        at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1057)
        at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1026)
        at org.springframework.context.support.AbstractApplicationContext.run(AbstractApplicationContext.java:945)
        - locked <0x00000000ff2b55d0> (a java.lang.Object)
"Thread-6":
        at org.h2.command.dml.TransactionCommand.update(TransactionCommand.java:98)
        - waiting to lock <0x00000000ff5dc0d0> (a org.h2.engine.Session)
        at org.h2.command.CommandContainer.update(CommandContainer.java:133)
        at org.h2.command.Command.executeUpdate(Command.java:267)
        - locked <0x00000000ff487080> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.executeInternal(JdbcStatement.java:233)
        - locked <0x00000000ff487080> (a org.h2.engine.Session)
        at org.h2.jdbc.JdbcStatement.execute(JdbcStatement.java:205)
        at org.springframework.jdbc.datasource.embedded.AbstractEmbeddedDatabaseConfigurer.shutdown(AbstractEmbeddedDatabaseConfigurer.java:47)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory.shutdownDatabase(EmbeddedDatabaseFactory.java:228)
        at org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseFactory$EmbeddedDataSourceProxy.shutdown(EmbeddedDatabaseFactory.java:303)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
        at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.invokeCustomDestroyMethod(DisposableBeanAdapter.java:337)
        at org.springframework.beans.factory.support.DisposableBeanAdapter.destroy(DisposableBeanAdapter.java:271)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroyBean(DefaultSingletonBeanRegistry.java:571)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingleton(DefaultSingletonBeanRegistry.java:543)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingleton(DefaultListableBeanFactory.java:1034)
        at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.destroySingletons(DefaultSingletonBeanRegistry.java:504)
        at org.springframework.beans.factory.support.DefaultListableBeanFactory.destroySingletons(DefaultListableBeanFactory.java:1027)
        at org.springframework.context.support.AbstractApplicationContext.destroyBeans(AbstractApplicationContext.java:1057)
        at org.springframework.context.support.AbstractApplicationContext.doClose(AbstractApplicationContext.java:1026)
        at org.springframework.context.support.AbstractApplicationContext.run(AbstractApplicationContext.java:945)
        - locked <0x00000000e002b8d0> (a java.lang.Object)

Found 1 deadlock.

跟踪锁定跟踪中的文件时,很明显它在关机期间锁定。为了重现,设置了以下代码:

Application.kt

package com.example.deadlock

import org.springframework.boot.SpringApplication
import org.springframework.boot.autoconfigure.SpringBootApplication
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.jdbc.core.JdbcTemplate

@SpringBootApplication
@Configuration
open class Application {
    @Bean
    open fun repository(jdbcTemplate: JdbcTemplate): Repository {
        return Repository(jdbcTemplate)
    }

    companion object {
        @JvmStatic
        fun main(args: Array<String>) {
            SpringApplication.run(Application::class.java, *args)
        }
    }
}

Repository.kt

package com.example.deadlock

import org.springframework.jdbc.core.JdbcTemplate

class Repository constructor(private val template: JdbcTemplate) {
    fun findAll(): List<String> =
        template.queryForList("SELECT TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_NAME='jfdksaiufd'", String::class.java)
}

Controller.kt

package com.example.deadlock

import org.springframework.http.MediaType
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController

@RestController
@RequestMapping(path = ["/foo"],
        produces = [MediaType.APPLICATION_JSON_UTF8_VALUE])
class Controller(private val repository: Repository) {
    @GetMapping
    fun banks(): List<String> {
        val banks = repository.findAll()
        return emptyList()
    }
}

要重现错误,以下测试组合似乎是最低限度的可用:

EndpointTest.kt

package com.example.deadlock

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc
import org.springframework.boot.test.context.SpringBootTest
import org.springframework.http.MediaType
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.content
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status

@SpringBootTest(classes = [Application::class])
@AutoConfigureMockMvc
class EndpointTest @Autowired constructor(private val mockMvc: MockMvc) {
    @Test
    fun empty() {
        val mvcRequest = MockMvcRequestBuilders.get("/foo").accept(MediaType.APPLICATION_JSON_UTF8)
        mockMvc.perform(mvcRequest)
                .andExpect(status().isOk)
                .andExpect(content().string("[]"))
    }
}

RepositoryTest.kt

package com.example.deadlock

import org.junit.jupiter.api.Test
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.boot.test.context.SpringBootTest
import kotlin.test.assertEquals

@SpringBootTest(classes = [Application::class])
class RepositoryTest @Autowired constructor(private val repository: Repository) {
    @Test
    fun initialCount() {
        assertEquals(0, repository.findAll().size)
    }
}

为了完整起见,这是有问题的 build.gradle:

buildscript {
    ext {
        kotlinVersion = "1.3.31"
        springVersion = "2.1.5.RELEASE"
    }
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.springframework.boot:spring-boot-gradle-plugin:${springVersion}"
    }
}

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.3.31"
    id "org.springframework.boot" version "2.1.5.RELEASE"

    id "idea"
}

apply plugin: "org.springframework.boot"
apply plugin: "io.spring.dependency-management"

group = "com.example.deadlock"
version = "0.0.1-SNAPSHOT"
description = "Deadlock with Spring and h2"
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
compileJava.options.encoding = "UTF-8"

tasks.withType(Test) {
    useJUnitPlatform()
    outputs.upToDateWhen {false}
}

repositories {
    mavenCentral()
}

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.jetbrains.kotlin:kotlin-reflect"
    testImplementation "org.jetbrains.kotlin:kotlin-test"
    testImplementation "org.jetbrains.kotlin:kotlin-test-junit"

    compile "org.springframework.boot:spring-boot-starter-web"
    compile "org.springframework:spring-jdbc"

    compile "com.h2database:h2"

    // Test setups
    testCompile "org.junit.jupiter:junit-jupiter-api"
    testRuntimeOnly "org.junit.jupiter:junit-jupiter-engine"

    testCompile "org.springframework.boot:spring-boot-starter-test"
}

而且至少在我的机器上它很容易被 运行 ./gradlew clean assemble 重现,然后循环 ./gradlew test.

出于某种原因,如果两个测试完全相同,它就不起作用,而且(不足为奇)我无法仅通过一个测试重现。

据我所知,似乎有两个关闭挂钩最终会竞争关闭问题。但是,我并不完全确定。

在询问后不久,我当然发现了这个:https://github.com/h2database/h2database/issues/1841

看起来确实是同一个问题。我现在已经将 @DirtiesContext 添加到两个测试中,目前它们已经 运行 超过 15 次没有问题