Skip to content

Commit db39dc0

Browse files
committed
Acquire the interrupt lock on the carrier thread to avoid stuck threads.
1 parent 26bcaa8 commit db39dc0

File tree

4 files changed

+64
-17
lines changed

4 files changed

+64
-17
lines changed

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/JavaThreads.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -862,10 +862,9 @@ static void initializeNewThread(
862862
}
863863

864864
/** Interruptibly park the current thread. */
865-
static void platformPark() {
865+
static void platformOrCarrierPark() {
866866
VMOperationControl.guaranteeOkayToBlock("[JavaThreads.park(): Should not park when it is not okay to block.]");
867-
Thread thread = Thread.currentThread();
868-
assert !isVirtual(thread);
867+
Thread thread = platformThread.get();
869868
if (isInterrupted(thread)) { // avoid state changes and synchronization
870869
return;
871870
}
@@ -887,10 +886,9 @@ static void platformPark() {
887886
}
888887

889888
/** Interruptibly park the current thread for the given number of nanoseconds. */
890-
static void platformPark(long delayNanos) {
889+
static void platformOrCarrierPark(long delayNanos) {
891890
VMOperationControl.guaranteeOkayToBlock("[JavaThreads.park(long): Should not park when it is not okay to block.]");
892-
Thread thread = Thread.currentThread();
893-
assert !isVirtual(thread);
891+
Thread thread = platformThread.get();
894892
if (isInterrupted(thread)) { // avoid state changes and synchronization
895893
return;
896894
}
@@ -913,8 +911,8 @@ static void platformPark(long delayNanos) {
913911
/**
914912
* Unpark a Thread.
915913
*
916-
* @see #platformPark()
917-
* @see #platformPark(long)
914+
* @see #platformOrCarrierPark()
915+
* @see #platformOrCarrierPark(long)
918916
*/
919917
static void platformUnpark(Thread thread) {
920918
assert !isVirtual(thread);

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/SubstrateVirtualThread.java

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@
4242
import com.oracle.svm.core.annotate.TargetClass;
4343
import com.oracle.svm.core.jdk.ContinuationsSupported;
4444
import com.oracle.svm.core.jdk.NotLoomJDK;
45+
import com.oracle.svm.core.monitor.MonitorSupport;
4546
import com.oracle.svm.core.stack.JavaFrameAnchor;
4647
import com.oracle.svm.core.stack.JavaFrameAnchors;
4748
import com.oracle.svm.core.util.VMError;
@@ -168,10 +169,13 @@ private void mount() {
168169
if (JavaThreads.isInterrupted(this)) {
169170
JavaThreads.platformSetInterrupt(carrier);
170171
} else if (JavaThreads.isInterrupted(carrier)) {
171-
synchronized (interruptLock()) {
172+
Object token = switchToCarrierAndAcquireInterruptLock();
173+
try {
172174
if (!JavaThreads.isInterrupted(this)) {
173175
JavaThreads.platformGetAndClearInterrupt(carrier);
174176
}
177+
} finally {
178+
releaseInterruptLockAndSwitchBack(token);
175179
}
176180
}
177181

@@ -374,11 +378,14 @@ void unpark() {
374378
submitRunContinuation();
375379
}
376380
} else if (s == PINNED) {
377-
synchronized (interruptLock()) {
381+
Object token = switchToCarrierAndAcquireInterruptLock();
382+
try {
378383
Thread carrier = carrierThread;
379384
if (carrier != null && state() == PINNED) {
380385
U.unpark(carrier);
381386
}
387+
} finally {
388+
releaseInterruptLockAndSwitchBack(token);
382389
}
383390
}
384391
}
@@ -428,10 +435,41 @@ private Object interruptLock() {
428435
return JavaThreads.toTarget(this).blockerLock;
429436
}
430437

438+
/** @see #releaseInterruptLockAndSwitchBack */
439+
private Object switchToCarrierAndAcquireInterruptLock() {
440+
Object token = null;
441+
Thread current = Thread.currentThread();
442+
if (current instanceof SubstrateVirtualThread) {
443+
SubstrateVirtualThread vthread = (SubstrateVirtualThread) current;
444+
/*
445+
* If we block on the interrupt lock in the virtual thread, we yield, for which we first
446+
* unmount. Unmounting also tries to acquire the interrupt lock and we might block
447+
* again, this time on the carrier thread. Then, the virtual thread cannot continue to
448+
* yield and execute on another thread later, and the carrier thread might not get
449+
* unparked, and both threads are stuck.
450+
*/
451+
Thread carrier = vthread.carrierThread;
452+
JavaThreads.setCurrentThread(carrier, carrier);
453+
token = vthread;
454+
}
455+
MonitorSupport.singleton().monitorEnter(interruptLock());
456+
return token;
457+
}
458+
459+
/** @see #switchToCarrierAndAcquireInterruptLock */
460+
private void releaseInterruptLockAndSwitchBack(Object token) {
461+
MonitorSupport.singleton().monitorExit(interruptLock());
462+
if (token != null) {
463+
SubstrateVirtualThread vthread = (SubstrateVirtualThread) token;
464+
JavaThreads.setCurrentThread(vthread.carrierThread, vthread);
465+
}
466+
}
467+
431468
@Override
432469
public void interrupt() {
433470
if (Thread.currentThread() != this) {
434-
synchronized (interruptLock()) {
471+
Object token = switchToCarrierAndAcquireInterruptLock();
472+
try {
435473
JavaThreads.getAndWriteInterruptedFlag(this, true);
436474
Target_sun_nio_ch_Interruptible b = JavaThreads.toTarget(this).blocker;
437475
if (b != null) {
@@ -441,6 +479,8 @@ public void interrupt() {
441479
if (carrier != null) {
442480
JavaThreads.platformSetInterrupt(carrier);
443481
}
482+
} finally {
483+
releaseInterruptLockAndSwitchBack(token);
444484
}
445485
} else {
446486
JavaThreads.getAndWriteInterruptedFlag(this, true);
@@ -451,8 +491,11 @@ public void interrupt() {
451491

452492
boolean getAndClearCarrierInterrupt() {
453493
assert Thread.currentThread() == this;
454-
synchronized (interruptLock()) {
494+
Object token = switchToCarrierAndAcquireInterruptLock();
495+
try {
455496
return JavaThreads.platformGetAndClearInterrupt(carrierThread);
497+
} finally {
498+
releaseInterruptLockAndSwitchBack(token);
456499
}
457500
}
458501

@@ -496,12 +539,15 @@ public Thread.State getState() {
496539
return Thread.State.RUNNABLE;
497540
case RUNNING:
498541
// if mounted then return state of carrier thread
499-
synchronized (interruptLock()) {
542+
Object token = switchToCarrierAndAcquireInterruptLock();
543+
try {
500544
@SuppressWarnings("hiding")
501545
Thread carrierThread = this.carrierThread;
502546
if (carrierThread != null) {
503547
return JavaThreads.getThreadState(carrierThread);
504548
}
549+
} finally {
550+
releaseInterruptLockAndSwitchBack(token);
505551
}
506552
// runnable, mounted
507553
return Thread.State.RUNNABLE;
@@ -533,14 +579,17 @@ public String toString() {
533579
Thread carrier = carrierThread;
534580
if (carrier != null) {
535581
// include the carrier thread state and name when mounted
536-
synchronized (interruptLock()) {
582+
Object token = switchToCarrierAndAcquireInterruptLock();
583+
try {
537584
carrier = carrierThread;
538585
if (carrier != null) {
539586
String stateAsString = JavaThreads.getThreadState(carrier).toString();
540587
sb.append(stateAsString.toLowerCase(Locale.ROOT));
541588
sb.append('@');
542589
sb.append(carrier.getName());
543590
}
591+
} finally {
592+
releaseInterruptLockAndSwitchBack(token);
544593
}
545594
}
546595
// include virtual thread state when not mounted

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_java_lang_Thread.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -382,7 +382,7 @@ public static boolean interrupted() {
382382
/**
383383
* Marks the thread as interrupted and wakes it up.
384384
*
385-
* See {@link JavaThreads#platformPark()}, {@link JavaThreads#platformUnpark} and
385+
* See {@link JavaThreads#platformOrCarrierPark()}, {@link JavaThreads#platformUnpark} and
386386
* {@link JavaThreads#sleep} for vital aspects of the underlying mechanisms.
387387
*/
388388
@Substitute

substratevm/src/com.oracle.svm.core/src/com/oracle/svm/core/thread/Target_jdk_internal_misc_Unsafe_JavaThreads.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,11 @@ void park(boolean isAbsolute, long time) {
4545
/* Decide what kind of park I am doing. */
4646
if (!isAbsolute && time == 0L) {
4747
/* Park without deadline. */
48-
JavaThreads.platformPark();
48+
JavaThreads.platformOrCarrierPark();
4949
} else {
5050
/* Park with deadline. */
5151
final long delayNanos = TimeUtils.delayNanos(isAbsolute, time);
52-
JavaThreads.platformPark(delayNanos);
52+
JavaThreads.platformOrCarrierPark(delayNanos);
5353
}
5454
/*
5555
* Unsafe.park does not distinguish between timing out, being unparked, and being

0 commit comments

Comments
 (0)