Deadlocks in virtual threads - was RFR: 8285196: Deadlock reporting prints carrier thread when virtual thread is in deadlock cycle
Dr Heinz M. Kabutz
heinz at javaspecialists.eu
Mon Jun 20 16:16:28 UTC 2022
Hi Ron and Alan,
I was pondering today what would happen if two virtual threads
deadlocked, either on synchronized or on ReentrantLock. My intuition was
that if it was on a ReentrantLock, then those two virtual threads would
be effectively decommissioned, and that they would not use up any
carrier threads, but that if they were deadlocking on synchronized, that
the carrier threads would be permanently blocked. This appears to be the
case, however, due to RFR 8285196 I'm struggling to see how we could
discover something like this. We'd just notice (or not) that there are
less carrier threads available. Of course we could dig in further and
dump all the virtual threads, hoping to discover the deadlock, but the
tool of the ThreadMXBean does not seem to be available anymore.
Here is an example:
import java.lang.management.*;
import java.util.*;
import java.util.concurrent.*;
import java.util.concurrent.locks.*;
public class DeadlockInVirtualThreads {
private static boolean MONITOR_LOCKS = true;
public static void main(String... args) throws InterruptedException {
// var builder = Thread.ofPlatform().name("platform-", 1);
var builder = Thread.ofVirtual().name("virtual-", 1);
for (int i = 0; i < Runtime.getRuntime().availableProcessors() / 2;
i++) {
deadlock(builder);
Thread.sleep(50);
}
Thread.sleep(1000);
System.out.println("Started all threads");
ThreadMXBean tmb = ManagementFactory.getThreadMXBean();
long[] threads = tmb.findDeadlockedThreads();
if (threads == null) System.out.println("No deadlocks found");
else System.out.println(Arrays.toString(threads));
System.out.println();
Thread testThread = Thread.startVirtualThread(
() -> System.out.println("Virtual threads still work")
);
testThread.join(50);
if (testThread.isAlive())
System.out.println("Virtual threads appear to be broken");
}
private static void deadlock(Thread.Builder builder) throws
InterruptedException {
var lock1 = new ReentrantLock();
var lock2 = new ReentrantLock();
var coop = new Phaser(2);
var thread1 = lock(builder, coop, lock2, lock1);
var thread2 = lock(builder, coop, lock1, lock2);
}
private static Thread lock(Thread.Builder builder,
Phaser coop,
Lock lock1, Lock lock2) {
return builder.start(() -> {
if (MONITOR_LOCKS) {
synchronized (lock1) {
coop.arriveAndAwaitAdvance();
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "
locked");
}
}
} else {
lock1.lock();
try {
coop.arriveAndAwaitAdvance();
lock2.lock();
try {
System.out.println(Thread.currentThread().getName() + "
locked");
} finally {
lock2.unlock();
}
} finally {
lock1.unlock();
}
}
});
}
}
With MONITOR_LOCKS = true, we get the following output:
Started all threads
No deadlocks found
Virtual threads appear to be broken
With MONITOR_LOCKS = false, we get the following output:
Started all threads
No deadlocks found
Virtual threads still work
When instead of virtual threads we use platform threads, we see:
With MONITOR_LOCKS = true and false, we get the same output:
Started all threads
[30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45]
Virtual threads still work
(Process does not terminate)
What would you consider the best practices for discovering a deadlock
between virtual threads?
And between a virtual and a platform thread? It appears that in that
case ThreadMXBean is also silent and does not help diagnose who is
holding the other lock. For example:
import java.lang.management.*;
import java.util.concurrent.*;
public class Deadlock {
public static void main(String... args) throws InterruptedException {
record Monitor(String name) {}
var monitor1 = new Monitor("monitor1");
var monitor2 = new Monitor("monitor2");
var coop = new Phaser(2);
Thread.ofPlatform().name("platform").start(() -> {
synchronized (monitor1) {
coop.arriveAndAwaitAdvance();
synchronized (monitor2) {
System.out.println("All's well");
}
}
});
Thread.ofVirtual().name("virtual").start(() -> {
synchronized (monitor2) {
coop.arriveAndAwaitAdvance();
synchronized (monitor1) {
System.out.println("All's well too");
}
}
});
Thread.sleep(100);
ThreadMXBean tmb = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = tmb.findDeadlockedThreads();
System.out.println("deadlockedThreads = " + deadlockedThreads);
}
}
Output is:
deadlockedThreads = null
And jstack outputs:
"platform" #30 [26371] prio=5 os_prio=31 cpu=2.80ms elapsed=28.75s
tid=0x00007fda6b81ea00 nid=26371 waiting for monitor entry
[0x00007000065f6000]
java.lang.Thread.State: BLOCKED (on object monitor)
at
eu.javaspecialists.course.concurrency.ch10_avoiding_liveness_hazards.Deadlock.lambda$main$0(Deadlock.java:16)
- waiting to lock <0x000000043fd11c88> (a
eu.javaspecialists.course.concurrency.ch10_avoiding_liveness_hazards.Deadlock$1Monitor)
- locked <0x000000043fd11c48> (a
eu.javaspecialists.course.concurrency.ch10_avoiding_liveness_hazards.Deadlock$1Monitor)
at
eu.javaspecialists.course.concurrency.ch10_avoiding_liveness_hazards.Deadlock$$Lambda$14/0x0000000801003be8.run(Unknown
Source)
at java.lang.Thread.run(java.base at 19-ea/Thread.java:1596)
"ForkJoinPool-1-worker-1" #32 [39683] daemon prio=5 os_prio=31
cpu=1.38ms elapsed=28.74s tid=0x00007fda6b930a00 [0x00007000066f9000]
Carrying virtual thread #31
at
jdk.internal.vm.Continuation.run(java.base at 19-ea/Continuation.java:257)
at
java.lang.VirtualThread.runContinuation(java.base at 19-ea/VirtualThread.java:213)
at
java.lang.VirtualThread$$Lambda$22/0x000000080104e1b0.run(java.base at 19-ea/Unknown
Source)
at
java.util.concurrent.ForkJoinTask$RunnableExecuteAction.exec(java.base at 19-ea/ForkJoinTask.java:1423)
at
java.util.concurrent.ForkJoinTask.doExec(java.base at 19-ea/ForkJoinTask.java:387)
at
java.util.concurrent.ForkJoinPool$WorkQueue.topLevelExec(java.base at 19-ea/ForkJoinPool.java:1311)
at
java.util.concurrent.ForkJoinPool.scan(java.base at 19-ea/ForkJoinPool.java:1840)
at
java.util.concurrent.ForkJoinPool.runWorker(java.base at 19-ea/ForkJoinPool.java:1806)
at
java.util.concurrent.ForkJoinWorkerThread.run(java.base at 19-ea/ForkJoinWorkerThread.java:177)
Would love to hear your thoughts on this.
Regards
Heinz
--
Dr Heinz M. Kabutz (PhD CompSci)
Author of "The Java™ Specialists' Newsletter" - www.javaspecialists.eu
Java Champion - www.javachampions.org
JavaOne Rock Star Speaker
Tel: +30 69 75 595 262
Skype: kabutz
On 2022/04/27 18:24, Ron Pressler wrote:
> Do not detect deadlock cycles, be it for monitors only or j.u.c `OwnableSynchronizer`s, when virtual threads are involved in the cycle.
>
> -------------
>
> Commit messages:
> - Skip carriers in deadlock detection
>
> Changes: https://git.openjdk.java.net/loom/pull/173/files
> Webrev: https://webrevs.openjdk.java.net/?repo=loom&pr=173&range=00
> Issue: https://bugs.openjdk.java.net/browse/JDK-8285196
> Stats: 28 lines in 2 files changed: 9 ins; 14 del; 5 mod
> Patch: https://git.openjdk.java.net/loom/pull/173.diff
> Fetch: git fetch https://git.openjdk.java.net/loom pull/173/head:pull/173
>
> PR: https://git.openjdk.java.net/loom/pull/173
More information about the loom-dev
mailing list