Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ReentrantLock instead of synchronized in DeserializerCache to avoid deadlock on pinning #4430

Merged
merged 4 commits into from
Mar 29, 2024

Conversation

oddbjornkvalsund
Copy link
Contributor

@oddbjornkvalsund oddbjornkvalsund commented Mar 13, 2024

DeserializerCache uses synchronized to protect the _incompleteDeserializers map from concurrent modification. This deadlocks when using virtual threads + IO within the synchronized block and you end up with a non-responsive VM and stacktraces looking like the one below. Note in particular the threads that have - waiting to lock <0x00000006104ab5e8> (a java.util.HashMap).

Replacing the synchronized keyword with a ReentrantLock fixes the problem. See relevant info here: https://www.infoworld.com/article/3713220/java-virtual-threads-hit-with-pinning-issue.html and here: https://inside.java/2024/02/21/quality-heads-up/.

Attaching to process ID 14488, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 21.0.1+12-29
Deadlock Detection:

No deadlocks found.

"main" #1 prio=5 tid=0x00007ef06c02b500 nid=14490 waiting on condition [0x00007ef073bfd000]
   java.lang.Thread.State: TIMED_WAITING (parking)
   JavaThread state: _thread_blocked
 - jdk.internal.misc.Unsafe.park(boolean, long) @bci=0 (Interpreted frame)
	- parking to wait for <0x00000006104a51c0> (a java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject)
 - java.util.concurrent.locks.LockSupport.parkNanos(java.lang.Object, long) @bci=34, line=269 (Interpreted frame)
 - java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.awaitNanos(long) @bci=124, line=1758 (Interpreted frame)
 - java.util.concurrent.ThreadPoolExecutor.awaitTermination(long, java.util.concurrent.TimeUnit) @bci=57, line=1475 (Interpreted frame)
 - java.util.concurrent.ExecutorService.close() @bci=28, line=417 (Interpreted frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.testDeadlock() @bci=108, line=60 (Interpreted frame)
 - java.lang.invoke.LambdaForm$DMH+0x00007eeff401cc00.invokeVirtual(java.lang.Object, java.lang.Object) @bci=10 (Interpreted frame)
 - java.lang.invoke.LambdaForm$MH+0x00007eeff401d800.invoke(java.lang.Object, java.lang.Object) @bci=31 (Interpreted frame)
 - java.lang.invoke.Invokers$Holder.invokeExact_MT(java.lang.Object, java.lang.Object, java.lang.Object) @bci=19 (Interpreted frame)
 - jdk.internal.reflect.DirectMethodHandleAccessor.invokeImpl(java.lang.Object, java.lang.Object[]) @bci=41, line=153 (Interpreted frame)
 - jdk.internal.reflect.DirectMethodHandleAccessor.invoke(java.lang.Object, java.lang.Object[]) @bci=23, line=103 (Interpreted frame)
 - java.lang.reflect.Method.invoke(java.lang.Object, java.lang.Object[]) @bci=102, line=580 (Interpreted frame)
 - junit.framework.TestCase.runTest() @bci=107, line=177 (Interpreted frame)
 - junit.framework.TestCase.runBare() @bci=7, line=142 (Interpreted frame)
 - junit.framework.TestResult$1.protect() @bci=4, line=122 (Interpreted frame)
 - junit.framework.TestResult.runProtected(junit.framework.Test, junit.framework.Protectable) @bci=1, line=142 (Interpreted frame)
 - junit.framework.TestResult.run(junit.framework.TestCase) @bci=18, line=125 (Interpreted frame)
 - junit.framework.TestCase.run(junit.framework.TestResult) @bci=2, line=130 (Interpreted frame)
 - junit.framework.TestSuite.runTest(junit.framework.Test, junit.framework.TestResult) @bci=2, line=241 (Interpreted frame)
 - junit.framework.TestSuite.run(junit.framework.TestResult) @bci=40, line=236 (Interpreted frame)
 - org.junit.internal.runners.JUnit38ClassRunner.run(org.junit.runner.notification.RunNotifier) @bci=22, line=90 (Interpreted frame)
 - org.junit.runner.JUnitCore.run(org.junit.runner.Runner) @bci=37, line=137 (Interpreted frame)
 - com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(java.lang.String[], java.lang.String, int, boolean) @bci=166, line=69 (Interpreted frame)
 - com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(boolean) @bci=17, line=38 (Interpreted frame)
 - com.intellij.rt.execution.junit.TestsRepeater.repeat(int, boolean, com.intellij.rt.execution.junit.TestsRepeater$TestRun) @bci=80, line=30 (Interpreted frame)
 - com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(com.intellij.rt.junit.IdeaTestRunner, java.lang.String[], java.util.ArrayList, java.lang.String, int, boolean) @bci=25, line=35 (Interpreted frame)
 - com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(java.lang.String[], java.lang.String, java.util.ArrayList, java.lang.String) @bci=117, line=232 (Interpreted frame)
 - com.intellij.rt.junit.JUnitStarter.main(java.lang.String[]) @bci=97, line=55 (Interpreted frame)


"Reference Handler" #9 daemon prio=10 tid=0x00007ef06c135b00 nid=14505 waiting on condition [0x00007ef0491fe000]
   java.lang.Thread.State: RUNNABLE
   JavaThread state: _thread_blocked
 - java.lang.ref.Reference.waitForReferencePendingList() @bci=0 (Interpreted frame)
 - java.lang.ref.Reference.processPendingReferences() @bci=0, line=246 (Interpreted frame)
 - java.lang.ref.Reference$ReferenceHandler.run() @bci=8, line=208 (Interpreted frame)


"Finalizer" #10 daemon prio=8 tid=0x00007ef06c137150 nid=14506 in Object.wait() [0x00007ef0490fe000]
   java.lang.Thread.State: WAITING (on object monitor)
   JavaThread state: _thread_blocked
 - java.lang.Object.wait0(long) @bci=0 (Interpreted frame)
 - java.lang.Object.wait(long) @bci=6, line=366 (Interpreted frame)
 - java.lang.Object.wait() @bci=2, line=339 (Interpreted frame)
 - java.lang.ref.NativeReferenceQueue.await() @bci=4, line=48 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove0() @bci=12, line=158 (Interpreted frame)
 - java.lang.ref.NativeReferenceQueue.remove() @bci=8, line=89 (Interpreted frame)
	- locked <0x00000006104a83d8> (a java.lang.ref.NativeReferenceQueue$Lock)
 - java.lang.ref.Finalizer$FinalizerThread.run() @bci=20, line=173 (Interpreted frame)


"Signal Dispatcher" #11 daemon prio=9 tid=0x00007ef06c138c30 nid=14507 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   JavaThread state: _thread_blocked


"Common-Cleaner" #18 daemon prio=8 tid=0x00007ef06c1603c0 nid=14514 waiting on condition [0x00007ef048a2e000]
   java.lang.Thread.State: TIMED_WAITING (parking)
   JavaThread state: _thread_blocked
 - jdk.internal.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
	- parking to wait for <0x00000006104a20f0> (a java/util/concurrent/locks/AbstractQueuedSynchronizer$ConditionObject)
 - java.util.concurrent.locks.LockSupport.parkNanos(java.lang.Object, long) @bci=34, line=269 (Interpreted frame)
 - java.util.concurrent.locks.AbstractQueuedSynchronizer$ConditionObject.await(long, java.util.concurrent.TimeUnit) @bci=134, line=1847 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.await(long) @bci=8, line=71 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove0(long) @bci=18, line=143 (Interpreted frame)
 - java.lang.ref.ReferenceQueue.remove(long) @bci=36, line=218 (Interpreted frame)
 - jdk.internal.ref.CleanerImpl.run() @bci=45, line=140 (Interpreted frame)
 - java.lang.Thread.runWith(java.lang.Object, java.lang.Runnable) @bci=5, line=1596 (Interpreted frame)
 - java.lang.Thread.run() @bci=19, line=1583 (Interpreted frame)
 - jdk.internal.misc.InnocuousThread.run() @bci=20, line=186 (Interpreted frame)


"Monitor Ctrl-Break" #19 daemon prio=5 tid=0x00007ef06c1d92d0 nid=14516 runnable [0x00007ef04892e000]
   java.lang.Thread.State: RUNNABLE
   JavaThread state: _thread_in_native
 - sun.nio.ch.SocketDispatcher.read0(java.io.FileDescriptor, long, int) @bci=0 (Interpreted frame)
 - sun.nio.ch.SocketDispatcher.read(java.io.FileDescriptor, long, int) @bci=4, line=47 (Interpreted frame)
 - sun.nio.ch.NioSocketImpl.tryRead(java.io.FileDescriptor, byte[], int, int) @bci=45, line=256 (Interpreted frame)
 - sun.nio.ch.NioSocketImpl.implRead(byte[], int, int) @bci=109, line=307 (Interpreted frame)
 - sun.nio.ch.NioSocketImpl.read(byte[], int, int) @bci=54, line=346 (Interpreted frame)
 - sun.nio.ch.NioSocketImpl$1.read(byte[], int, int) @bci=7, line=796 (Interpreted frame)
 - java.net.Socket$SocketInputStream.read(byte[], int, int) @bci=7, line=1099 (Interpreted frame)
 - sun.nio.cs.StreamDecoder.readBytes() @bci=117, line=329 (Interpreted frame)
 - sun.nio.cs.StreamDecoder.implRead(char[], int, int) @bci=112, line=372 (Interpreted frame)
 - sun.nio.cs.StreamDecoder.lockedRead(char[], int, int) @bci=158, line=215 (Interpreted frame)
 - sun.nio.cs.StreamDecoder.read(char[], int, int) @bci=30, line=169 (Interpreted frame)
 - java.io.InputStreamReader.read(char[], int, int) @bci=7, line=188 (Interpreted frame)
 - java.io.BufferedReader.fill() @bci=145, line=160 (Interpreted frame)
 - java.io.BufferedReader.implReadLine(boolean, boolean[]) @bci=44, line=370 (Interpreted frame)
 - java.io.BufferedReader.readLine(boolean, boolean[]) @bci=26, line=347 (Interpreted frame)
 - java.io.BufferedReader.readLine() @bci=3, line=436 (Interpreted frame)
 - com.intellij.rt.execution.application.AppMainV2$1.run() @bci=36, line=53 (Interpreted frame)


"ForkJoinPool-1-worker-1" #22 daemon prio=5 tid=0x00007ef06c533ec0 nid=14520 waiting for monitor entry [0x00007ef04862e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-2" #31 daemon prio=5 tid=0x00007eefb0001040 nid=14521 waiting for monitor entry [0x00007ef04852e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-3" #47 daemon prio=5 tid=0x00007eefb4001040 nid=14522 waiting for monitor entry [0x00007ef04842e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-4" #65 daemon prio=5 tid=0x00007eefa8001040 nid=14523 waiting for monitor entry [0x00007ef04832e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-5" #70 daemon prio=5 tid=0x00007eefb0003380 nid=14524 waiting for monitor entry [0x00007ef04822e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-6" #74 daemon prio=5 tid=0x00007eefac001040 nid=14525 waiting for monitor entry [0x00007ef04812e000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-7" #77 daemon prio=5 tid=0x00007eefb0004840 nid=14526 waiting for monitor entry [0x00007eeff2ffe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-8" #82 daemon prio=5 tid=0x00007eefa0001040 nid=14527 waiting for monitor entry [0x00007eeff2efe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-9" #92 daemon prio=5 tid=0x00007eefa4001040 nid=14528 waiting for monitor entry [0x00007eeff2dfe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-10" #97 daemon prio=5 tid=0x00007eefb0005aa0 nid=14529 waiting on condition [0x00007eeff2cfe000]
   java.lang.Thread.State: WAITING (parking)
   JavaThread state: _thread_blocked
 - jdk.internal.misc.Unsafe.park(boolean, long) @bci=0 (Compiled frame; information may be imprecise)
 - java.lang.VirtualThread.parkOnCarrierThread(boolean, long) @bci=66, line=663 (Interpreted frame)
 - java.lang.VirtualThread.park() @bci=200, line=593 (Compiled frame)
 - java.lang.System$2.parkVirtualThread() @bci=17, line=2639 (Compiled frame)
 - jdk.internal.misc.VirtualThreads.park() @bci=3, line=54 (Compiled frame)
 - java.util.concurrent.locks.LockSupport.park(java.lang.Object) @bci=16, line=219 (Compiled frame)
 - java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(java.util.concurrent.locks.AbstractQueuedSynchronizer$Node, int, boolean, boolean, boolean, long) @bci=361, line=754 (Compiled frame)
 - java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(int) @bci=15, line=990 (Compiled frame)
 - java.util.concurrent.locks.ReentrantLock$Sync.lock() @bci=9, line=153 (Compiled frame [deoptimized])
 - java.util.concurrent.locks.ReentrantLock.lock() @bci=4, line=322 (Compiled frame [deoptimized])
 - jdk.internal.misc.InternalLock.lock() @bci=4, line=74 (Compiled frame [deoptimized])
 - java.io.PrintStream.writeln(java.lang.String) @bci=11, line=824 (Compiled frame [deoptimized])
 - java.io.PrintStream.println(java.lang.String) @bci=14, line=1168 (Compiled frame [deoptimized])
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=13, line=259 (Interpreted frame)
	- locked <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-11" #101 daemon prio=5 tid=0x00007eef98001040 nid=14530 waiting for monitor entry [0x00007eeff2bfe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"ForkJoinPool-1-worker-12" #102 daemon prio=5 tid=0x00007ef06c535080 nid=14531 waiting for monitor entry [0x00007eeff2afe000]
   java.lang.Thread.State: BLOCKED (on object monitor)
   JavaThread state: _thread_blocked
 - com.fasterxml.jackson.databind.deser.DeserializerCache._createAndCacheValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=8, line=259 (Interpreted frame)
	- waiting to lock <0x00000006104ab5e8> (a java.util.HashMap)
 - com.fasterxml.jackson.databind.deser.DeserializerCache.findValueDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.deser.DeserializerFactory, com.fasterxml.jackson.databind.JavaType) @bci=16, line=170 (Interpreted frame)
 - com.fasterxml.jackson.databind.DeserializationContext.findRootValueDeserializer(com.fasterxml.jackson.databind.JavaType) @bci=10, line=669 (Interpreted frame)
 - com.fasterxml.jackson.databind.ObjectMapper._findRootDeserializer(com.fasterxml.jackson.databind.DeserializationContext, com.fasterxml.jackson.databind.JavaType) @bci=20, line=5036 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.JavaType) @bci=80, line=4906 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, com.fasterxml.jackson.databind.JavaType) @bci=18, line=3848 (Compiled frame)
 - com.fasterxml.jackson.databind.ObjectMapper.readValue(java.lang.String, java.lang.Class) @bci=18, line=3816 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest.lambda$testDeadlock$0(java.util.concurrent.atomic.AtomicLong, com.fasterxml.jackson.databind.json.JsonMapper, java.lang.Class, java.util.concurrent.CountDownLatch) @bci=11, line=51 (Compiled frame)
 - com.fasterxml.jackson.databind.jdk21.DeserializerCacheTest$$Lambda+0x00007eeff40ae0d0.run() @bci=16 (Compiled frame)
 - java.util.concurrent.Executors$RunnableAdapter.call() @bci=4, line=572 (Compiled frame)
 - java.util.concurrent.FutureTask.run() @bci=39, line=317 (Compiled frame)
 - java.util.concurrent.ThreadPoolExecutor.runWorker(java.util.concurrent.ThreadPoolExecutor$Worker) @bci=92, line=1144 (Interpreted frame)

Error occurred during stack walking:
sun.jvm.hotspot.utilities.AssertionFailure: must have non-zero frame size
	at jdk.hotspot.agent/sun.jvm.hotspot.utilities.Assert.that(Assert.java:32)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.senderForCompiledFrame(X86Frame.java:383)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.x86.X86Frame.sender(X86Frame.java:292)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.sender(Frame.java:207)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.Frame.realSender(Frame.java:212)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.sender(VFrame.java:119)
	at jdk.hotspot.agent/sun.jvm.hotspot.runtime.VFrame.javaSender(VFrame.java:148)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:81)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.StackTrace.run(StackTrace.java:45)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.run(JStack.java:67)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.startInternal(Tool.java:278)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.start(Tool.java:241)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.Tool.execute(Tool.java:134)
	at jdk.hotspot.agent/sun.jvm.hotspot.tools.JStack.runWithArgs(JStack.java:90)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.runJSTACK(SALauncher.java:302)
	at jdk.hotspot.agent/sun.jvm.hotspot.SALauncher.main(SALauncher.java:500)


"Attach Listener" #142 daemon prio=9 tid=0x00007eefdc000ff0 nid=14553 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE
   JavaThread state: _thread_blocked


@oddbjornkvalsund
Copy link
Contributor Author

The detailed thread stacktrace was produced by jhsdb jstack --pid 14488.

The following unit test illustrates the underlying problem. If you set both boolean flag to true the test deadlocks more or less immediately. If you set one of them to false it runs fine.

@SuppressWarnings("ConstantValue")
public void test() {

    // If BOTH are true - it deadlocks
    final var doSynchronized = true;
    final var doPrintOutSide = true;

    // System.setProperty("jdk.tracePinnedThreads", "full");

    final var objectLock = new Object();
    final var reentrantLock = new ReentrantLock();

    try (var executor = Executors.newFixedThreadPool(100, Thread.ofVirtual().factory())) {
        for (int i = 0; i < 1000; i++) {
            final var ci = i;
            executor.submit(() -> {
                if (doSynchronized) {
                    printWithSynchronized(objectLock, ci);
                } else {
                    printWithReentrantLock(reentrantLock, ci);
                }

                if (doPrintOutSide) {
                    System.out.println("HELLO " + ci);
                }
            });
        }
    }
}

private static void printWithReentrantLock(ReentrantLock reentrantLock, int i) {
    try {
        reentrantLock.lock();
        System.out.println("HELLO " + i);
    } finally {
        reentrantLock.unlock();
    }
}

private static void printWithSynchronized(Object objectLock, int i) {
    synchronized (objectLock) {
        System.out.println("HELLO " + i);
    }
}

@pjfanning
Copy link
Member

The InfoWorld article makes this sound like a Java bug that Oracle are seeking to fix. While Jackson will probably replace synchronized blocks with explicit locks, this is probably not going to happen in a patch release.

@pjfanning
Copy link
Member

@cowtowncoder From my testing, Reentrant locks are slower than synchronized blocks. This change could affect performance for the majority of users. One option is to make the behaviour configurable. One code path uses synchronized blocks and the other ReentrantLocks.

@oddbjornkvalsund
Copy link
Contributor Author

@pjfanning I agree that the "proper" fix would be to fix the JVMs problematic behavior observed when synchronized threads park (more specifically "threads holding an object monitor") and this indeed being worked on by the JVM team, but I'm a bit worried that this will take months (if not years) to complete.

For info there is currently 18 usages of synchronized in the jackson-databind module:

image

@pjfanning
Copy link
Member

@oddbjornkvalsund You are free to fork jackson-databind and make whatever changes suit you. Unfortunately, I need to worry about all other jackson-databind users. I don't feel like hacking away at jackson-databind because of JDK bugs is something that should be rushed into.

@oddbjornkvalsund
Copy link
Contributor Author

oddbjornkvalsund commented Mar 15, 2024

@pjfanning Sure, I have already forked and submitted this change for the one specific part of the code that caused problems for me. I'll be happy to dig into the remaining, hang on...

@oddbjornkvalsund
Copy link
Contributor Author

oddbjornkvalsund commented Mar 15, 2024

@pjfanning There, I've now changed the remaining usages of synchronized (except one) in this module to use ReentrantLock as per the JEP 444 suggestion. All tests pass. I opted to change almost all since it looked to me like most of these classes take user-supplied dependencies that we must assume can do I/O and cause thread parking while holding a monitor and thus ultimately deadlock (see this for more background).

I realize that there is quite possibly a ton of synchronized in other Jackson modules, but this is at least a start. I'll be happy to contribute if I come across other deadlocks in the Jackson code.

As to the more fundamental question of

Should the Jackson code be change to not use synchronized/object monitors or should we wait for the JVM to be fixed?,

...I have no strong opinions, but I do feel that if you choose to wait then you should give a big warning to users that Jackson cannot be used with JDK 21 or newer until this issue is fixed. The deadlock issue I observed is very real and hits very hard.

@iProdigy
Copy link
Contributor

Jackson cannot be used with JDK 21 or newer

is discouraged on virtual threads*

@pjfanning
Copy link
Member

pjfanning commented Mar 15, 2024

Not every Java 21/22 user is using virtual threads. Let's also be straight - this a major bug in Java. It is not a Jackson bug.

@oddbjornkvalsund if you want to cast stones - cast them in the right direction.

Copy link
Member

@pjfanning pjfanning left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1 from me - but I guess it's up to @cowtowncoder

My preference is make the locking configurable so that users can choose to use synchronized blocks or ReentrantLocks. If we can agree on where to put the configuration (maybe JsonFactory) and what to call it, I can do a POC that uses the config and refactor the DeserializerCache to support both lock types. If the POC looks good, we can roll it out elsewhere in jackson-databind and beyond that into other Jackson modules.

@pjfanning pjfanning added the 2.18 label Mar 15, 2024
@pjfanning
Copy link
Member

@mariofusco Making you aware of this issue since you are the biggest driver of the existing Jackson changes to avoid ThreadLocal caching.

@@ -69,6 +70,8 @@ protected abstract static class DateBasedDeserializer<T>
*/
protected final DateFormat _customFormat;

private final ReentrantLock _customFormatLock = new ReentrantLock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code is not 100% correct - If you have 2 deserializer instances with the same _customFormat instance then synchronizing it will prevent concurrent use of that _customFormat instance - your lock would not work properly in that case.

It is sort of annoying that Java never made DateFormat thread-safe. We could look into finding thread-safe alternatives - or adding a thread-safe class of our own that wraps DataFormat but that uses a locking mechanism to control access.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't researched this but I suspect we could find some java.time API to do date/time formatting that is thread safe - and therefore not need locking here at all.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree, the existing DateFormat should be replaced with a java.time.format.DateTimeFormatter which is immutable and thread-safe and at that point this synchronization could be of course totally removed. However I guess that this could be addressed with a different pull request, immediately after this will be merged.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure replacement would work, unfortunately, since we are talking about support for java.util.Date / java.util.Calendar; and if I remember correctly, format Strings used with SimpleDateFormat are different from those used with Java 8 date/time (and Joda).

So the better fix for users is to upgrade from java.util types to java.time.

So while I agree with all the ugliness of java.util world, I am not sure spending time on better support there is worth the effort.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this Date code is not on critical path - users can avoid it using java.time classes instead of java.util Data/Calendar

so this could be left alone - and maybe tackled later

@@ -42,6 +43,8 @@ public abstract class TypeDeserializerBase
*/
protected final JavaType _defaultImpl;

protected final ReentrantLock _defaultImplLock = new ReentrantLock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same problem as I described for DateDeserializers - what if JavaType _defaultImpl instance is used by more than 1 TypeDeserializerBase instance. This set of refactors seem really dangerous to me. I hate the idea of hacking like this.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree that this is wrong. In my opinion the ReentrantLock should be moved inside the JavaType.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think JavaType would be the place: locking here is specifically to try to avoid duplicate lookups for deserializer used for "default type" (of given polymorphic base type).

However. Since this is not done for correctness but as (possibly misguided?) performance optimization, maybe even use of basic AtomicReference would work (accepting possible race condition).

@oddbjornkvalsund
Copy link
Contributor Author

oddbjornkvalsund commented Mar 15, 2024

@pjfanning I have not intended to cast any stones or blame anyone and I'm sorry if I came across as blunt or pointing fingers. I am pragmatic about this and only focused on making things work. 🙏

The complexities with synchronized and virtual threads has been communicated since November 2021 and virtual threads have been in preview status since JDK 19, generally available in September 2022. Spring Boot uses Jackson and has supported virtual threads since November 2023. I haven't checked if Spring Boot is vulnerable to the problem I have been seeing, but I wouldn't be surprised if it is. To be clear: I am not saying this to blame anyone or to point fingers, I am just saying this to shed light on the implications of the issue.

Thanks for identifying the bugs in my code! I will address those if there proves to be any interest in merging this PR. 👍

@mariofusco
Copy link
Contributor

@cowtowncoder From my testing, Reentrant locks are slower than synchronized blocks. This change could affect performance for the majority of users. One option is to make the behaviour configurable. One code path uses synchronized blocks and the other ReentrantLocks.

With the removal of biased locking I believe that this is no longer so true in modern JVMs. Just to give you one more datapoint we replaced all synchronized blocks with locks in Quarkus almost one year ago and didn't notice any performance implication. In my opinion making the locking mechanism pluggable and/or configurable will only add unnecessary complexity without any real advantage.

@mariofusco
Copy link
Contributor

@cowtowncoder @pjfanning @oddbjornkvalsund I gave a second look at this pull request and in particular tried to replace the legacy DateFormat with a DateTimeFormatter as suggest here. In my opinion this is a long overdue work that has to be done regardless of this deadlock issue (for which is not relevant), but my efforts in this direction have been blocked by the presence of the custom made StdDateFormat.

Regarding the problem at hand I believe that it is not necessary to replace all the synchronized blocks (and in particular it is not needed for the block parsing the date) but only for the blocks that perform an I/O operation inside them. In fact pinning is a problem only when the virtual thread performs an I/O operation and then it should be unmounted from its carrier but it can't since it is pinned.

In essence my suggestion is:

  1. close this PR
  2. file another PR replacing the synchronized blocks with ReentrantLock only where it internally performs an I/O operation
  3. independently fix the legacy date management using a DateTimeFormatter

@oddbjornkvalsund
Copy link
Contributor Author

@mariofusco That sounds like a good approach. Feel free to close this PR!

For me, avoiding setting the "jdk.tracePinnedThreads" system property made the problem go away for now, but since this is the approach suggested by the JDK team for identifying pinning issues, it's not great that I can't use it.

@mariofusco
Copy link
Contributor

For me, avoiding setting the "jdk.tracePinnedThreads" system property made the problem go away for now.

Used in that way that system property doesn't tell the whole story. As I tried to explain a pinned virtual thread is not necessarily a problem: pinned means that in the current state the virtual thread cannot be unmounted from its carrier, but if it doesn't perform any blocking I/O operation while pinned (e.g. while in the synchronized block) it won't be unmounted in all cases, so why bother?

@@ -107,29 +110,41 @@ public synchronized int size() {
*/
public JsonSerializer<Object> untypedValueSerializer(Class<?> type)
{
synchronized (this) {
try {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems this should never cause issues? There's no I/O operations within Map (LRUMap), TypeKey is stateless.

In fact... I don't think synchronization should be needed at all here: LRUMap states:

Since Jackson 2.14, the implementation
...
Is thread-safe and does NOT require external synchronization

so could it be this is legacy, pre-2.14 locking, that was just left for no good reason...

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After reading through the code, this may not be as simple as I thought (see #4442): I think synchronized actually has secondary purpose to act as monitor for resolution of cyclic dependencies for POJO serializers (BeanSerializer). And if so it cannot really be safely removed (but is also a big potential performance concern at same time).

@cowtowncoder
Copy link
Member

Ok, with a quick (but I hope not too quick) look, I agree that a minimal patch (PR) would be preferable -- just tackling observed case of _incompleteDeserializers. Further it seems that:

  1. SerializerCache locking should actually be removed (!) -- LRUMap should already handle syncing, and it's guarding simple lookup anyway. But removal should be done for 2.18 (assuming I did not miss something obvious). Either way I am not sure ReentrantLock would be needed here, there's no i/o involved
  2. "Old" java.util.Date/Calendar support via formatters: just leave these as-is. They are not used with newer Java 8 date/time or Joda, nor does it look like these would necessarily be problematic. However, as with everything, if there are proven problems I have no issue in addressing those

@pjfanning
Copy link
Member

Could we go back to having this PR being about the one lock in DeserializerCache ? This locking code needs due respect and every change needs to be analysed on its own merits. We will look at each case - we just don't need to try to handle every case in 1 PR.

@cowtowncoder
Copy link
Member

@pjfanning Right, that's what I tried to say (but with too many words) :)

@cowtowncoder
Copy link
Member

@pjfanning Could you come up with minimal PR for DeserializerCache? Since that's a real observed problem, we can tackle that -- in 2.17(.1) I think.

@pjfanning
Copy link
Member

@oddbjornkvalsund could you change this PR to just keep the DeserializerCache change? We can deal with the other classes separately.

@pjfanning
Copy link
Member

I created a speculative PR - #4456

@oddbjornkvalsund
Copy link
Contributor Author

@oddbjornkvalsund could you change this PR to just keep the DeserializerCache change? We can deal with the other classes separately.

Done!

@cowtowncoder cowtowncoder changed the title Use ReentrantLock instead of synchronized to avoid deadlock on pinning Use ReentrantLock instead of synchronized in DeserializerCache to avoid deadlock on pinning Mar 29, 2024
/**
* We hold an explicit lock while creating deserializers to avoid creating duplicates.
*/
private final ReentrantLock _incompleteDeserializersLock = new ReentrantLock();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cowtowncoder can we keep this private as it is in the PR? I want to do follow up PRs and would like to not have to keep this name. I think it too specific. I also think that protected code is just as bad as public code in forcing us to maintain compatibility across versions. Private code can be refactored without worrying about users who write their own subclasses.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, it's fine to be private; no benefit/need for protected/package access here.

@cowtowncoder cowtowncoder merged commit d512683 into FasterXML:2.17 Mar 29, 2024
6 checks passed
@cowtowncoder cowtowncoder added 2.17 Issues planned at earliest for 2.17 and removed 2.18 labels Mar 29, 2024
@cowtowncoder cowtowncoder added this to the 2.17.1 milestone Mar 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
2.17 Issues planned at earliest for 2.17
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants