Skip to content

Commit 29d7a22

Browse files
author
Alan Bateman
committed
8321270: Virtual Thread.yield consumes parking permit
Reviewed-by: sspitsyn
1 parent 0b0fa47 commit 29d7a22

File tree

4 files changed

+139
-79
lines changed

4 files changed

+139
-79
lines changed

src/hotspot/share/classfile/javaClasses.cpp

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1986,26 +1986,27 @@ int java_lang_VirtualThread::state(oop vthread) {
19861986
JavaThreadStatus java_lang_VirtualThread::map_state_to_thread_status(int state) {
19871987
JavaThreadStatus status = JavaThreadStatus::NEW;
19881988
switch (state & ~SUSPENDED) {
1989-
case NEW :
1989+
case NEW:
19901990
status = JavaThreadStatus::NEW;
19911991
break;
1992-
case STARTED :
1993-
case RUNNABLE :
1994-
case RUNNING :
1995-
case PARKING :
1992+
case STARTED:
1993+
case RUNNING:
1994+
case PARKING:
19961995
case TIMED_PARKING:
1997-
case YIELDING :
1996+
case UNPARKED:
1997+
case YIELDING:
1998+
case YIELDED:
19981999
status = JavaThreadStatus::RUNNABLE;
19992000
break;
2000-
case PARKED :
2001-
case PINNED :
2001+
case PARKED:
2002+
case PINNED:
20022003
status = JavaThreadStatus::PARKED;
20032004
break;
20042005
case TIMED_PARKED:
20052006
case TIMED_PINNED:
20062007
status = JavaThreadStatus::PARKED_TIMED;
20072008
break;
2008-
case TERMINATED :
2009+
case TERMINATED:
20092010
status = JavaThreadStatus::TERMINATED;
20102011
break;
20112012
default:

src/hotspot/share/classfile/javaClasses.hpp

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -523,15 +523,16 @@ class java_lang_VirtualThread : AllStatic {
523523
enum {
524524
NEW = 0,
525525
STARTED = 1,
526-
RUNNABLE = 2,
527-
RUNNING = 3,
528-
PARKING = 4,
529-
PARKED = 5,
530-
PINNED = 6,
531-
TIMED_PARKING = 7,
532-
TIMED_PARKED = 8,
533-
TIMED_PINNED = 9,
526+
RUNNING = 2,
527+
PARKING = 3,
528+
PARKED = 4,
529+
PINNED = 5,
530+
TIMED_PARKING = 6,
531+
TIMED_PARKED = 7,
532+
TIMED_PINNED = 8,
533+
UNPARKED = 9,
534534
YIELDING = 10,
535+
YIELDED = 11,
535536
TERMINATED = 99,
536537

537538
// additional state bits

src/java.base/share/classes/java/lang/VirtualThread.java

Lines changed: 89 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -96,37 +96,37 @@ final class VirtualThread extends BaseVirtualThread {
9696
* RUNNING -> PARKING // Thread parking with LockSupport.park
9797
* PARKING -> PARKED // cont.yield successful, parked indefinitely
9898
* PARKING -> PINNED // cont.yield failed, parked indefinitely on carrier
99-
* PARKED -> RUNNABLE // unparked, schedule to continue
99+
* PARKED -> UNPARKED // unparked, may be scheduled to continue
100100
* PINNED -> RUNNING // unparked, continue execution on same carrier
101+
* UNPARKED -> RUNNING // continue execution after park
101102
*
102103
* RUNNING -> TIMED_PARKING // Thread parking with LockSupport.parkNanos
103104
* TIMED_PARKING -> TIMED_PARKED // cont.yield successful, timed-parked
104105
* TIMED_PARKING -> TIMED_PINNED // cont.yield failed, timed-parked on carrier
105-
* TIMED_PARKED -> RUNNABLE // unparked, schedule to continue
106+
* TIMED_PARKED -> UNPARKED // unparked, may be scheduled to continue
106107
* TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier
107108
*
108-
* RUNNABLE -> RUNNING // continue execution
109-
*
110109
* RUNNING -> YIELDING // Thread.yield
111-
* YIELDING -> RUNNABLE // yield successful
112-
* YIELDING -> RUNNING // yield failed
110+
* YIELDING -> YIELDED // cont.yield successful, may be scheduled to continue
111+
* YIELDING -> RUNNING // cont.yield failed
112+
* YIELDED -> RUNNING // continue execution after Thread.yield
113113
*/
114114
private static final int NEW = 0;
115115
private static final int STARTED = 1;
116-
private static final int RUNNABLE = 2; // runnable-unmounted
117-
private static final int RUNNING = 3; // runnable-mounted
118-
119-
// untimed parking
120-
private static final int PARKING = 4;
121-
private static final int PARKED = 5; // unmounted
122-
private static final int PINNED = 6; // mounted
116+
private static final int RUNNING = 2; // runnable-mounted
123117

124-
// timed parking
125-
private static final int TIMED_PARKING = 7;
126-
private static final int TIMED_PARKED = 8;
127-
private static final int TIMED_PINNED = 9;
118+
// untimed and timed parking
119+
private static final int PARKING = 3;
120+
private static final int PARKED = 4; // unmounted
121+
private static final int PINNED = 5; // mounted
122+
private static final int TIMED_PARKING = 6;
123+
private static final int TIMED_PARKED = 7; // unmounted
124+
private static final int TIMED_PINNED = 8; // mounted
125+
private static final int UNPARKED = 9; // unmounted but runnable
128126

129-
private static final int YIELDING = 10; // Thread.yield
127+
// Thread.yield
128+
private static final int YIELDING = 10;
129+
private static final int YIELDED = 11; // unmounted but runnable
130130

131131
private static final int TERMINATED = 99; // final state
132132

@@ -218,11 +218,15 @@ private void runContinuation() {
218218

219219
// set state to RUNNING
220220
int initialState = state();
221-
if (initialState == STARTED && compareAndSetState(STARTED, RUNNING)) {
222-
// first run
223-
} else if (initialState == RUNNABLE && compareAndSetState(RUNNABLE, RUNNING)) {
224-
// consume parking permit
225-
setParkPermit(false);
221+
if (initialState == STARTED || initialState == UNPARKED || initialState == YIELDED) {
222+
// newly started or continue after parking/blocking/Thread.yield
223+
if (!compareAndSetState(initialState, RUNNING)) {
224+
return;
225+
}
226+
// consume parking permit when continuing after parking
227+
if (initialState == UNPARKED) {
228+
setParkPermit(false);
229+
}
226230
} else {
227231
// not runnable
228232
return;
@@ -244,8 +248,7 @@ private void runContinuation() {
244248
/**
245249
* Submits the runContinuation task to the scheduler. For the default scheduler,
246250
* and calling it on a worker thread, the task will be pushed to the local queue,
247-
* otherwise it will be pushed to a submission queue.
248-
*
251+
* otherwise it will be pushed to an external submission queue.
249252
* @throws RejectedExecutionException
250253
*/
251254
private void submitRunContinuation() {
@@ -258,7 +261,7 @@ private void submitRunContinuation() {
258261
}
259262

260263
/**
261-
* Submits the runContinuation task to the scheduler with a lazy submit.
264+
* Submits the runContinuation task to given scheduler with a lazy submit.
262265
* @throws RejectedExecutionException
263266
* @see ForkJoinPool#lazySubmit(ForkJoinTask)
264267
*/
@@ -272,7 +275,7 @@ private void lazySubmitRunContinuation(ForkJoinPool pool) {
272275
}
273276

274277
/**
275-
* Submits the runContinuation task to the scheduler as an external submit.
278+
* Submits the runContinuation task to the given scheduler as an external submit.
276279
* @throws RejectedExecutionException
277280
* @see ForkJoinPool#externalSubmit(ForkJoinTask)
278281
*/
@@ -457,7 +460,7 @@ private void afterYield() {
457460
setState(newState);
458461

459462
// may have been unparked while parking
460-
if (parkPermit && compareAndSetState(newState, RUNNABLE)) {
463+
if (parkPermit && compareAndSetState(newState, UNPARKED)) {
461464
// lazy submit to continue on the current thread as carrier if possible
462465
if (currentThread() instanceof CarrierThread ct) {
463466
lazySubmitRunContinuation(ct.getPool());
@@ -471,7 +474,7 @@ private void afterYield() {
471474

472475
// Thread.yield
473476
if (s == YIELDING) {
474-
setState(RUNNABLE);
477+
setState(YIELDED);
475478

476479
// external submit if there are no tasks in the local task queue
477480
if (currentThread() instanceof CarrierThread ct && ct.getQueuedTaskCount() == 0) {
@@ -618,7 +621,7 @@ void parkNanos(long nanos) {
618621
long startTime = System.nanoTime();
619622

620623
boolean yielded = false;
621-
Future<?> unparker = scheduleUnpark(this::unpark, nanos);
624+
Future<?> unparker = scheduleUnpark(nanos); // may throw OOME
622625
setState(TIMED_PARKING);
623626
try {
624627
yielded = yieldContinuation(); // may throw
@@ -683,14 +686,15 @@ private void parkOnCarrierThread(boolean timed, long nanos) {
683686
}
684687

685688
/**
686-
* Schedule an unpark task to run after a given delay.
689+
* Schedule this virtual thread to be unparked after a given delay.
687690
*/
688691
@ChangesCurrentThread
689-
private Future<?> scheduleUnpark(Runnable unparker, long nanos) {
692+
private Future<?> scheduleUnpark(long nanos) {
693+
assert Thread.currentThread() == this;
690694
// need to switch to current carrier thread to avoid nested parking
691695
switchToCarrierThread();
692696
try {
693-
return UNPARKER.schedule(unparker, nanos, NANOSECONDS);
697+
return UNPARKER.schedule(this::unpark, nanos, NANOSECONDS);
694698
} finally {
695699
switchToVirtualThread(this);
696700
}
@@ -726,7 +730,7 @@ void unpark() {
726730
if (!getAndSetParkPermit(true) && currentThread != this) {
727731
int s = state();
728732
boolean parked = (s == PARKED) || (s == TIMED_PARKED);
729-
if (parked && compareAndSetState(s, RUNNABLE)) {
733+
if (parked && compareAndSetState(s, UNPARKED)) {
730734
if (currentThread instanceof VirtualThread vthread) {
731735
vthread.switchToCarrierThread();
732736
try {
@@ -738,7 +742,7 @@ void unpark() {
738742
submitRunContinuation();
739743
}
740744
} else if ((s == PINNED) || (s == TIMED_PINNED)) {
741-
// unpark carrier thread when pinned.
745+
// unpark carrier thread when pinned
742746
synchronized (carrierThreadAccessLock()) {
743747
Thread carrier = carrierThread;
744748
if (carrier != null && ((s = state()) == PINNED || s == TIMED_PINNED)) {
@@ -889,7 +893,8 @@ Thread.State threadState() {
889893
} else {
890894
return Thread.State.RUNNABLE;
891895
}
892-
case RUNNABLE:
896+
case UNPARKED:
897+
case YIELDED:
893898
// runnable, not mounted
894899
return Thread.State.RUNNABLE;
895900
case RUNNING:
@@ -905,7 +910,7 @@ Thread.State threadState() {
905910
case PARKING:
906911
case TIMED_PARKING:
907912
case YIELDING:
908-
// runnable, mounted, not yet waiting
913+
// runnable, in transition
909914
return Thread.State.RUNNABLE;
910915
case PARKED:
911916
case PINNED:
@@ -947,35 +952,58 @@ StackTraceElement[] asyncGetStackTrace() {
947952

948953
/**
949954
* Returns the stack trace for this virtual thread if it is unmounted.
950-
* Returns null if the thread is in another state.
955+
* Returns null if the thread is mounted or in transition.
951956
*/
952957
private StackTraceElement[] tryGetStackTrace() {
953958
int initialState = state();
954-
return switch (initialState) {
955-
case RUNNABLE, PARKED, TIMED_PARKED -> {
956-
int suspendedState = initialState | SUSPENDED;
957-
if (compareAndSetState(initialState, suspendedState)) {
958-
try {
959-
yield cont.getStackTrace();
960-
} finally {
961-
assert state == suspendedState;
962-
setState(initialState);
963-
964-
// re-submit if runnable
965-
// re-submit if unparked while suspended
966-
if (initialState == RUNNABLE
967-
|| (parkPermit && compareAndSetState(initialState, RUNNABLE))) {
968-
try {
969-
submitRunContinuation();
970-
} catch (RejectedExecutionException ignore) { }
971-
}
972-
}
973-
}
974-
yield null;
959+
switch (initialState) {
960+
case NEW, STARTED, TERMINATED -> {
961+
return new StackTraceElement[0]; // unmounted, empty stack
962+
}
963+
case RUNNING, PINNED -> {
964+
return null; // mounted
965+
}
966+
case PARKED, TIMED_PARKED -> {
967+
// unmounted, not runnable
968+
}
969+
case UNPARKED, YIELDED -> {
970+
// unmounted, runnable
975971
}
976-
case NEW, STARTED, TERMINATED -> new StackTraceElement[0]; // empty stack
977-
default -> null;
972+
case PARKING, TIMED_PARKING, YIELDING -> {
973+
return null; // in transition
974+
}
975+
default -> throw new InternalError();
976+
}
977+
978+
// thread is unmounted, prevent it from continuing
979+
int suspendedState = initialState | SUSPENDED;
980+
if (!compareAndSetState(initialState, suspendedState)) {
981+
return null;
982+
}
983+
984+
// get stack trace and restore state
985+
StackTraceElement[] stack;
986+
try {
987+
stack = cont.getStackTrace();
988+
} finally {
989+
assert state == suspendedState;
990+
setState(initialState);
991+
}
992+
boolean resubmit = switch (initialState) {
993+
case UNPARKED, YIELDED -> {
994+
// resubmit as task may have run while suspended
995+
yield true;
996+
}
997+
case PARKED, TIMED_PARKED -> {
998+
// resubmit if unparked while suspended
999+
yield parkPermit && compareAndSetState(initialState, UNPARKED);
1000+
}
1001+
default -> throw new InternalError();
9781002
};
1003+
if (resubmit) {
1004+
submitRunContinuation();
1005+
}
1006+
return stack;
9791007
}
9801008

9811009
@Override

test/jdk/java/lang/Thread/virtual/ThreadAPI.java

Lines changed: 31 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323

2424
/*
2525
* @test id=default
26-
* @bug 8284161 8286788
26+
* @bug 8284161 8286788 8321270
2727
* @summary Test Thread API with virtual threads
2828
* @modules java.base/java.lang:+open
2929
* @library /test/lib
@@ -1191,6 +1191,36 @@ void testYield2() throws Exception {
11911191
assertEquals(List.of("A", "A", "B"), list);
11921192
}
11931193

1194+
/**
1195+
* Test that Thread.yield does not consume the thread's parking permit.
1196+
*/
1197+
@Test
1198+
void testYield3() throws Exception {
1199+
var thread = Thread.ofVirtual().start(() -> {
1200+
LockSupport.unpark(Thread.currentThread());
1201+
Thread.yield();
1202+
LockSupport.park(); // should not park
1203+
});
1204+
thread.join();
1205+
}
1206+
1207+
/**
1208+
* Test that Thread.yield does not make available the thread's parking permit.
1209+
*/
1210+
@Test
1211+
void testYield4() throws Exception {
1212+
var thread = Thread.ofVirtual().start(() -> {
1213+
Thread.yield();
1214+
LockSupport.park(); // should park
1215+
});
1216+
try {
1217+
await(thread, Thread.State.WAITING);
1218+
} finally {
1219+
LockSupport.unpark(thread);
1220+
thread.join();
1221+
}
1222+
}
1223+
11941224
/**
11951225
* Test Thread.onSpinWait.
11961226
*/

0 commit comments

Comments
 (0)