Currently I'm able to create a deadlock when using the combination of h2 and spring boot. There's a lot of issues out there, that looks kind of similar, but apparently they have been solved, and I'm not entirely sure if it's Spring or h2 that does something strange.
First off the visualvm trace for the deadlock (scroll to the bottom for the lock):
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$0(TCPTransport.java:688)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$607/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$0(TCPTransport.java:688)
at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler$$Lambda$607/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$1.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$1.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$1.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$1)
at java.lang.Thread.join(Thread.java:1252)
- locked <0x00000000ff24cda8> (a org.springframework.context.support.AbstractApplicationContext$1)
at java.lang.Thread.join(Thread.java:1326)
at java.lang.ApplicationShutdownHooks.runHooks(ApplicationShutdownHooks.java:107)
at java.lang.ApplicationShutdownHooks$1.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$1.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$1.run(AbstractApplicationContext.java:945)
- locked <0x00000000e002b8d0> (a java.lang.Object)
Found 1 deadlock.
When tracing into the files in the lock trace it seems quite clear that it locks during Shutdown. To reproduce the following code was set up:
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()
}
}
To reproduce the error, the follow combination of tests seems to be the minimum usable:
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)
}
}
For the sake of completeness this is the build.gradle in question:
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"
}
And at least on my machine it's easily reproducible by running ./gradlew clean assemble
followed by doing a looped ./gradlew test
.
For some reason it does not work if the two tests are identical, and (not surprisingly) I haven't been able to reproduce with only one test.
From what I can see it could look like that there are two shutdown hooks that end up competing about closing the issue. However, I am not entirely certain.
And shortly after asking, I of course find this: https://github.com/h2database/h2database/issues/1841
It does look like the exact same issue. I've added @DirtiesContext
to the two tests now, and currently they've been run more than 15 times without issue