-
Bug
-
Resolution: Unresolved
-
P4
-
19, 20, 21
-
generic
-
generic
ADDITIONAL SYSTEM INFORMATION :
java version "20" 2023-03-21
Java(TM) SE Runtime Environment (build 20+36-2344)
Java HotSpot(TM) 64-Bit Server VM (build 20+36-2344, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
Virtual threads are scheduled by an ForkJoinPool which provides carrier threads. If the number of carrier threads has reached the parallelism of the ForkJoinPool, submitting tasks will not create any more carrier thread.
And also using synchronized on some object will pin the carrier thread.
So in the situation:
1. all the carrier threads are pinned by synchronized on some object, but that object's monitor is owned by some virtual thread
2. That virtual thread has owned object's monitor but there is no carrier thread for it to run
So this could cause the deadlock. And the deadlock is not happening if change the virtual thread pool the normal thread pool
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
run the source code below
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
expect this message in sysout out:
Main thread lock
Main thread unlock
First thread lock
First thread unlock
Second thread lock
Second thread unlock
Suppose to be here!!
ACTUAL -
actual message in sysout out:
Main thread lock
Main thread unlock
---------- BEGIN SOURCE ----------
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
public class VirtualThreadDeadlock {
public static void main(String[] args) throws Exception {
// use jdk.virtualThreadScheduler.parallelism=1 to reproduce the issue easily
System.setProperty("jdk.virtualThreadScheduler.parallelism", "1");
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
CountDownLatch latch = new CountDownLatch(2);
ReentrantLock lock = new ReentrantLock();
Object objLock = new Object();
lock.lock(); // main thread locked
System.out.println("Main thread lock");
executor.execute(() -> {
lock.lock(); // try to lock, continuation will yield
try {
System.out.println("First thread lock");
} finally {
lock.unlock();
System.out.println("First thread unlock");
}
latch.countDown(); // first thread end
});
Thread.sleep(1000);
executor.execute(() -> {
synchronized (objLock) {
lock.lock(); // carrierThread will be pinned
try {
System.out.println("Second thread lock");
} finally {
lock.unlock();
System.out.println("Second thread unlock");
}
}
latch.countDown(); // second thread end
});
Thread.sleep(1000);
lock.unlock(); // main thread unlocked, the first thread is supposed to continue,
// but there is no available carrierThread to execute
System.out.println("Main thread unlock");
latch.await();
System.out.println("Suppose to be here!!");
}
}
---------- END SOURCE ----------
FREQUENCY : always
java version "20" 2023-03-21
Java(TM) SE Runtime Environment (build 20+36-2344)
Java HotSpot(TM) 64-Bit Server VM (build 20+36-2344, mixed mode, sharing)
A DESCRIPTION OF THE PROBLEM :
Virtual threads are scheduled by an ForkJoinPool which provides carrier threads. If the number of carrier threads has reached the parallelism of the ForkJoinPool, submitting tasks will not create any more carrier thread.
And also using synchronized on some object will pin the carrier thread.
So in the situation:
1. all the carrier threads are pinned by synchronized on some object, but that object's monitor is owned by some virtual thread
2. That virtual thread has owned object's monitor but there is no carrier thread for it to run
So this could cause the deadlock. And the deadlock is not happening if change the virtual thread pool the normal thread pool
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
run the source code below
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
expect this message in sysout out:
Main thread lock
Main thread unlock
First thread lock
First thread unlock
Second thread lock
Second thread unlock
Suppose to be here!!
ACTUAL -
actual message in sysout out:
Main thread lock
Main thread unlock
---------- BEGIN SOURCE ----------
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.ReentrantLock;
public class VirtualThreadDeadlock {
public static void main(String[] args) throws Exception {
// use jdk.virtualThreadScheduler.parallelism=1 to reproduce the issue easily
System.setProperty("jdk.virtualThreadScheduler.parallelism", "1");
ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
CountDownLatch latch = new CountDownLatch(2);
ReentrantLock lock = new ReentrantLock();
Object objLock = new Object();
lock.lock(); // main thread locked
System.out.println("Main thread lock");
executor.execute(() -> {
lock.lock(); // try to lock, continuation will yield
try {
System.out.println("First thread lock");
} finally {
lock.unlock();
System.out.println("First thread unlock");
}
latch.countDown(); // first thread end
});
Thread.sleep(1000);
executor.execute(() -> {
synchronized (objLock) {
lock.lock(); // carrierThread will be pinned
try {
System.out.println("Second thread lock");
} finally {
lock.unlock();
System.out.println("Second thread unlock");
}
}
latch.countDown(); // second thread end
});
Thread.sleep(1000);
lock.unlock(); // main thread unlocked, the first thread is supposed to continue,
// but there is no available carrierThread to execute
System.out.println("Main thread unlock");
latch.await();
System.out.println("Suppose to be here!!");
}
}
---------- END SOURCE ----------
FREQUENCY : always