8338383: Implement JEP 491: Synchronize Virtual Threads without Pinning

Co-authored-by: Patricio Chilano Mateo <pchilanomate@openjdk.org>
Co-authored-by: Alan Bateman <alanb@openjdk.org>
Co-authored-by: Andrew Haley <aph@openjdk.org>
Co-authored-by: Fei Yang <fyang@openjdk.org>
Co-authored-by: Coleen Phillimore <coleenp@openjdk.org>
Co-authored-by: Richard Reingruber <rrich@openjdk.org>
Co-authored-by: Martin Doerr <mdoerr@openjdk.org>
Reviewed-by: aboldtch, dholmes, coleenp, fbredberg, dlong, sspitsyn
This commit is contained in:
Patricio Chilano Mateo 2024-11-12 15:23:48 +00:00
parent 8a2a75e56d
commit 78b80150e0
246 changed files with 8295 additions and 2755 deletions

View file

@ -159,16 +159,8 @@ public class ByteArrayOutputStream extends OutputStream {
* @throws NullPointerException if {@code out} is {@code null}.
* @throws IOException if an I/O error occurs.
*/
public void writeTo(OutputStream out) throws IOException {
if (Thread.currentThread().isVirtual()) {
byte[] bytes;
synchronized (this) {
bytes = Arrays.copyOf(buf, count);
}
out.write(bytes);
} else synchronized (this) {
out.write(buf, 0, count);
}
public synchronized void writeTo(OutputStream out) throws IOException {
out.write(buf, 0, count);
}
/**

View file

@ -25,7 +25,6 @@
package java.lang;
import jdk.internal.misc.Blocker;
import jdk.internal.vm.annotation.IntrinsicCandidate;
/**
@ -374,21 +373,20 @@ public class Object {
* @see #wait(long, int)
*/
public final void wait(long timeoutMillis) throws InterruptedException {
if (!Thread.currentThread().isVirtual()) {
wait0(timeoutMillis);
return;
if (timeoutMillis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
// virtual thread waiting
boolean attempted = Blocker.begin();
try {
if (Thread.currentThread() instanceof VirtualThread vthread) {
try {
wait0(timeoutMillis);
} catch (InterruptedException e) {
// virtual thread's interrupt status needs to be cleared
vthread.getAndClearInterrupt();
throw e;
}
} else {
wait0(timeoutMillis);
} catch (InterruptedException e) {
// virtual thread's interrupt status needs to be cleared
Thread.currentThread().getAndClearInterrupt();
throw e;
} finally {
Blocker.end(attempted);
}
}

View file

@ -1,151 +0,0 @@
/*
* Copyright (c) 2020, 2023, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang;
import java.io.PrintStream;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import static java.lang.StackWalker.Option.*;
import jdk.internal.access.JavaIOPrintStreamAccess;
import jdk.internal.access.SharedSecrets;
import jdk.internal.misc.InternalLock;
import jdk.internal.vm.Continuation;
/**
* Helper class to print the virtual thread stack trace when pinned.
*
* The class maintains a ClassValue with the hashes of stack traces that are pinned by
* code in that Class. This is used to avoid printing the same stack trace many times.
*/
class PinnedThreadPrinter {
private static final JavaIOPrintStreamAccess JIOPSA = SharedSecrets.getJavaIOPrintStreamAccess();
private static final StackWalker STACK_WALKER;
static {
var options = Set.of(SHOW_REFLECT_FRAMES, RETAIN_CLASS_REFERENCE);
PrivilegedAction<StackWalker> pa = () ->
LiveStackFrame.getStackWalker(options, VirtualThread.continuationScope());
@SuppressWarnings("removal")
var stackWalker = AccessController.doPrivileged(pa);
STACK_WALKER = stackWalker;
}
private static final ClassValue<Hashes> HASHES = new ClassValue<>() {
@Override
protected Hashes computeValue(Class<?> type) {
return new Hashes();
}
};
@SuppressWarnings("serial")
private static class Hashes extends LinkedHashMap<Integer, Boolean> {
boolean add(int hash) {
return (putIfAbsent(hash, Boolean.TRUE) == null);
}
@Override
protected boolean removeEldestEntry(Map.Entry<Integer, Boolean> oldest) {
// limit number of hashes
return size() > 8;
}
}
/**
* Returns a hash of the given stack trace. The hash is based on the class,
* method and bytecode index.
*/
private static int hash(List<LiveStackFrame> stack) {
int hash = 0;
for (LiveStackFrame frame : stack) {
hash = (31 * hash) + Objects.hash(frame.getDeclaringClass(),
frame.getMethodName(),
frame.getByteCodeIndex());
}
return hash;
}
/**
* Returns true if the frame is native, a class initializer, or holds monitors.
*/
private static boolean isInterestingFrame(LiveStackFrame f) {
return f.isNativeMethod()
|| "<clinit>".equals(f.getMethodName())
|| (f.getMonitors().length > 0);
}
/**
* Prints the current thread's stack trace.
*
* @param printAll true to print all stack frames, false to only print the
* frames that are native or holding a monitor
*/
static void printStackTrace(PrintStream out, Continuation.Pinned reason, boolean printAll) {
List<LiveStackFrame> stack = STACK_WALKER.walk(s ->
s.map(f -> (LiveStackFrame) f)
.filter(f -> f.getDeclaringClass() != PinnedThreadPrinter.class)
.collect(Collectors.toList())
);
Object lockObj = JIOPSA.lock(out);
if (lockObj instanceof InternalLock lock && lock.tryLock()) {
try {
// find the closest frame that is causing the thread to be pinned
stack.stream()
.filter(f -> isInterestingFrame(f))
.map(LiveStackFrame::getDeclaringClass)
.findFirst()
.ifPresentOrElse(klass -> {
// print the stack trace if not already seen
int hash = hash(stack);
if (HASHES.get(klass).add(hash)) {
printStackTrace(out, reason, stack, printAll);
}
}, () -> printStackTrace(out, reason, stack, true)); // not found
} finally {
lock.unlock();
}
}
}
private static void printStackTrace(PrintStream out,
Continuation.Pinned reason,
List<LiveStackFrame> stack,
boolean printAll) {
out.format("%s reason:%s%n", Thread.currentThread(), reason);
for (LiveStackFrame frame : stack) {
var ste = frame.toStackTraceElement();
int monitorCount = frame.getMonitors().length;
if (monitorCount > 0) {
out.format(" %s <== monitors:%d%n", ste, monitorCount);
} else if (printAll || isInterestingFrame(frame)) {
out.format(" %s%n", ste);
}
}
}
}

View file

@ -636,12 +636,17 @@ public class Thread implements Runnable {
*/
static final int NO_INHERIT_THREAD_LOCALS = 1 << 2;
/**
* Thread identifier assigned to the primordial thread.
*/
static final long PRIMORDIAL_TID = 3;
/**
* Helper class to generate thread identifiers. The identifiers start at
* 2 as this class cannot be used during early startup to generate the
* identifier for the primordial thread. The counter is off-heap and
* shared with the VM to allow it assign thread identifiers to non-Java
* threads.
* {@link Thread#PRIMORDIAL_TID}&nbsp;+1 as this class cannot be used during
* early startup to generate the identifier for the primordial thread. The
* counter is off-heap and shared with the VM to allow it to assign thread
* identifiers to non-Java threads.
* See Thread initialization.
*/
private static class ThreadIdentifiers {
@ -722,10 +727,11 @@ public class Thread implements Runnable {
}
if (attached && VM.initLevel() < 1) {
this.tid = 1; // primordial thread
this.tid = PRIMORDIAL_TID; // primordial thread
} else {
this.tid = ThreadIdentifiers.next();
}
this.name = (name != null) ? name : genThreadName();
if (acc != null) {

View file

@ -41,7 +41,6 @@ import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import jdk.internal.event.VirtualThreadEndEvent;
import jdk.internal.event.VirtualThreadPinnedEvent;
import jdk.internal.event.VirtualThreadStartEvent;
import jdk.internal.event.VirtualThreadSubmitFailedEvent;
import jdk.internal.misc.CarrierThread;
@ -70,12 +69,12 @@ final class VirtualThread extends BaseVirtualThread {
private static final ContinuationScope VTHREAD_SCOPE = new ContinuationScope("VirtualThreads");
private static final ForkJoinPool DEFAULT_SCHEDULER = createDefaultScheduler();
private static final ScheduledExecutorService[] DELAYED_TASK_SCHEDULERS = createDelayedTaskSchedulers();
private static final int TRACE_PINNING_MODE = tracePinningMode();
private static final long STATE = U.objectFieldOffset(VirtualThread.class, "state");
private static final long PARK_PERMIT = U.objectFieldOffset(VirtualThread.class, "parkPermit");
private static final long CARRIER_THREAD = U.objectFieldOffset(VirtualThread.class, "carrierThread");
private static final long TERMINATION = U.objectFieldOffset(VirtualThread.class, "termination");
private static final long ON_WAITING_LIST = U.objectFieldOffset(VirtualThread.class, "onWaitingList");
// scheduler and continuation
private final Executor scheduler;
@ -106,6 +105,21 @@ final class VirtualThread extends BaseVirtualThread {
* TIMED_PARKED -> UNPARKED // unparked, may be scheduled to continue
* TIMED_PINNED -> RUNNING // unparked, continue execution on same carrier
*
* RUNNING -> BLOCKING // blocking on monitor enter
* BLOCKING -> BLOCKED // blocked on monitor enter
* BLOCKED -> UNBLOCKED // unblocked, may be scheduled to continue
* UNBLOCKED -> RUNNING // continue execution after blocked on monitor enter
*
* RUNNING -> WAITING // transitional state during wait on monitor
* WAITING -> WAITED // waiting on monitor
* WAITED -> BLOCKED // notified, waiting to be unblocked by monitor owner
* WAITED -> UNBLOCKED // timed-out/interrupted
*
* RUNNING -> TIMED_WAITING // transition state during timed-waiting on monitor
* TIMED_WAITING -> TIMED_WAITED // timed-waiting on monitor
* TIMED_WAITED -> BLOCKED // notified, waiting to be unblocked by monitor owner
* TIMED_WAITED -> UNBLOCKED // timed-out/interrupted
*
* RUNNING -> YIELDING // Thread.yield
* YIELDING -> YIELDED // cont.yield successful, may be scheduled to continue
* YIELDING -> RUNNING // cont.yield failed
@ -128,18 +142,44 @@ final class VirtualThread extends BaseVirtualThread {
private static final int YIELDING = 10;
private static final int YIELDED = 11; // unmounted but runnable
// monitor enter
private static final int BLOCKING = 12;
private static final int BLOCKED = 13; // unmounted
private static final int UNBLOCKED = 14; // unmounted but runnable
// monitor wait/timed-wait
private static final int WAITING = 15;
private static final int WAIT = 16; // waiting in Object.wait
private static final int TIMED_WAITING = 17;
private static final int TIMED_WAIT = 18; // waiting in timed-Object.wait
private static final int TERMINATED = 99; // final state
// can be suspended from scheduling when unmounted
private static final int SUSPENDED = 1 << 8;
// parking permit
// parking permit made available by LockSupport.unpark
private volatile boolean parkPermit;
// timeout for timed-park, in nanoseconds, only accessed on current/carrier thread
private long parkTimeout;
// blocking permit made available by unblocker thread when another thread exits monitor
private volatile boolean blockPermit;
// timer task for timed-park, only accessed on current/carrier thread
// true when on the list of virtual threads waiting to be unblocked
private volatile boolean onWaitingList;
// next virtual thread on the list of virtual threads waiting to be unblocked
private volatile VirtualThread next;
// notified by Object.notify/notifyAll while waiting in Object.wait
private volatile boolean notified;
// timed-wait support
private byte timedWaitSeqNo;
// timeout for timed-park and timed-wait, only accessed on current/carrier thread
private long timeout;
// timer task for timed-park and timed-wait, only accessed on current/carrier thread
private Future<?> timeoutTask;
// carrier thread when mounted, accessed by VM
@ -202,18 +242,6 @@ final class VirtualThread extends BaseVirtualThread {
}
@Override
protected void onPinned(Continuation.Pinned reason) {
if (TRACE_PINNING_MODE > 0) {
boolean printAll = (TRACE_PINNING_MODE == 1);
VirtualThread vthread = (VirtualThread) Thread.currentThread();
int oldState = vthread.state();
try {
// avoid printing when in transition states
vthread.setState(RUNNING);
PinnedThreadPrinter.printStackTrace(System.out, reason, printAll);
} finally {
vthread.setState(oldState);
}
}
}
private static Runnable wrap(VirtualThread vthread, Runnable task) {
return new Runnable() {
@ -245,16 +273,20 @@ final class VirtualThread extends BaseVirtualThread {
// set state to RUNNING
int initialState = state();
if (initialState == STARTED || initialState == UNPARKED || initialState == YIELDED) {
if (initialState == STARTED || initialState == UNPARKED
|| initialState == UNBLOCKED || initialState == YIELDED) {
// newly started or continue after parking/blocking/Thread.yield
if (!compareAndSetState(initialState, RUNNING)) {
return;
}
// consume permit when continuing after parking. If continuing after a
// timed-park then the timeout task is cancelled.
// consume permit when continuing after parking or blocking. If continue
// after a timed-park or timed-wait then the timeout task is cancelled.
if (initialState == UNPARKED) {
cancelTimeoutTask();
setParkPermit(false);
} else if (initialState == UNBLOCKED) {
cancelTimeoutTask();
blockPermit = false;
}
} else {
// not runnable
@ -275,8 +307,8 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Cancel timeout task when continuing after a timed-park. The
* timeout task may be executing, or may have already completed.
* Cancel timeout task when continuing after timed-park or timed-wait.
* The timeout task may be executing, or may have already completed.
*/
private void cancelTimeoutTask() {
if (timeoutTask != null) {
@ -511,7 +543,7 @@ final class VirtualThread extends BaseVirtualThread {
/**
* Invoked in the context of the carrier thread after the Continuation yields when
* parking or Thread.yield.
* parking, blocking on monitor enter, Object.wait, or Thread.yield.
*/
private void afterYield() {
assert carrierThread == null;
@ -530,8 +562,8 @@ final class VirtualThread extends BaseVirtualThread {
setState(newState = PARKED);
} else {
// schedule unpark
assert parkTimeout > 0;
timeoutTask = schedule(this::unpark, parkTimeout, NANOSECONDS);
assert timeout > 0;
timeoutTask = schedule(this::unpark, timeout, NANOSECONDS);
setState(newState = TIMED_PARKED);
}
@ -556,6 +588,56 @@ final class VirtualThread extends BaseVirtualThread {
return;
}
// blocking on monitorenter
if (s == BLOCKING) {
setState(BLOCKED);
// may have been unblocked while blocking
if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) {
// lazy submit if local queue is empty
lazySubmitRunContinuation();
}
return;
}
// Object.wait
if (s == WAITING || s == TIMED_WAITING) {
int newState;
if (s == WAITING) {
setState(newState = WAIT);
} else {
// For timed-wait, a timeout task is scheduled to execute. The timeout
// task will change the thread state to UNBLOCKED and submit the thread
// to the scheduler. A sequence number is used to ensure that the timeout
// task only unblocks the thread for this timed-wait. We synchronize with
// the timeout task to coordinate access to the sequence number and to
// ensure the timeout task doesn't execute until the thread has got to
// the TIMED_WAIT state.
assert timeout > 0;
synchronized (timedWaitLock()) {
byte seqNo = ++timedWaitSeqNo;
timeoutTask = schedule(() -> waitTimeoutExpired(seqNo), timeout, MILLISECONDS);
setState(newState = TIMED_WAIT);
}
}
// may have been notified while in transition to wait state
if (notified && compareAndSetState(newState, BLOCKED)) {
// may have even been unblocked already
if (blockPermit && compareAndSetState(BLOCKED, UNBLOCKED)) {
submitRunContinuation();
}
return;
}
// may have been interrupted while in transition to wait state
if (interrupted && compareAndSetState(newState, UNBLOCKED)) {
submitRunContinuation();
return;
}
return;
}
assert false;
}
@ -695,7 +777,7 @@ final class VirtualThread extends BaseVirtualThread {
// park the thread, afterYield will schedule the thread to unpark
boolean yielded = false;
setParkTimeout(nanos);
timeout = nanos;
setState(TIMED_PARKING);
try {
yielded = yieldContinuation();
@ -727,14 +809,6 @@ final class VirtualThread extends BaseVirtualThread {
private void parkOnCarrierThread(boolean timed, long nanos) {
assert state() == RUNNING;
VirtualThreadPinnedEvent event;
try {
event = new VirtualThreadPinnedEvent();
event.begin();
} catch (OutOfMemoryError e) {
event = null;
}
setState(timed ? TIMED_PINNED : PINNED);
try {
if (!parkPermit) {
@ -751,15 +825,18 @@ final class VirtualThread extends BaseVirtualThread {
// consume parking permit
setParkPermit(false);
if (event != null) {
try {
event.commit();
} catch (OutOfMemoryError e) {
// ignore
}
}
// JFR jdk.VirtualThreadPinned event
postPinnedEvent("LockSupport.park");
}
/**
* Call into VM when pinned to record a JFR jdk.VirtualThreadPinned event.
* Recording the event in the VM avoids having JFR event recorded in Java
* with the same name, but different ID, to events recorded by the VM.
*/
@Hidden
private static native void postPinnedEvent(String op);
/**
* Re-enables this virtual thread for scheduling. If this virtual thread is parked
* then its task is scheduled to continue, otherwise its next call to {@code park} or
@ -796,6 +873,49 @@ final class VirtualThread extends BaseVirtualThread {
}
}
/**
* Invoked by unblocker thread to unblock this virtual thread.
*/
private void unblock() {
assert !Thread.currentThread().isVirtual();
blockPermit = true;
if (state() == BLOCKED && compareAndSetState(BLOCKED, UNBLOCKED)) {
submitRunContinuation();
}
}
/**
* Invoked by timer thread when wait timeout for virtual thread has expired.
* If the virtual thread is in timed-wait then this method will unblock the thread
* and submit its task so that it continues and attempts to reenter the monitor.
* This method does nothing if the thread has been woken by notify or interrupt.
*/
private void waitTimeoutExpired(byte seqNo) {
assert !Thread.currentThread().isVirtual();
for (;;) {
boolean unblocked = false;
synchronized (timedWaitLock()) {
if (seqNo != timedWaitSeqNo) {
// this timeout task is for a past timed-wait
return;
}
int s = state();
if (s == TIMED_WAIT) {
unblocked = compareAndSetState(TIMED_WAIT, UNBLOCKED);
} else if (s != (TIMED_WAIT | SUSPENDED)) {
// notified or interrupted, no longer waiting
return;
}
}
if (unblocked) {
submitRunContinuation();
return;
}
// need to retry when thread is suspended in time-wait
Thread.yield();
}
}
/**
* Attempts to yield the current virtual thread (Thread.yield).
*/
@ -926,6 +1046,12 @@ final class VirtualThread extends BaseVirtualThread {
// make available parking permit, unpark thread if parked
unpark();
// if thread is waiting in Object.wait then schedule to try to reenter
int s = state();
if ((s == WAIT || s == TIMED_WAIT) && compareAndSetState(s, UNBLOCKED)) {
submitRunContinuation();
}
} else {
interrupted = true;
carrierThread.setInterrupt();
@ -970,6 +1096,7 @@ final class VirtualThread extends BaseVirtualThread {
return Thread.State.RUNNABLE;
}
case UNPARKED:
case UNBLOCKED:
case YIELDED:
// runnable, not mounted
return Thread.State.RUNNABLE;
@ -992,15 +1119,22 @@ final class VirtualThread extends BaseVirtualThread {
return Thread.State.RUNNABLE;
case PARKING:
case TIMED_PARKING:
case WAITING:
case TIMED_WAITING:
case YIELDING:
// runnable, in transition
return Thread.State.RUNNABLE;
case PARKED:
case PINNED:
case WAIT:
return Thread.State.WAITING;
case TIMED_PARKED:
case TIMED_PINNED:
case TIMED_WAIT:
return Thread.State.TIMED_WAITING;
case BLOCKING:
case BLOCKED:
return Thread.State.BLOCKED;
case TERMINATED:
return Thread.State.TERMINATED;
default:
@ -1046,13 +1180,13 @@ final class VirtualThread extends BaseVirtualThread {
case RUNNING, PINNED, TIMED_PINNED -> {
return null; // mounted
}
case PARKED, TIMED_PARKED -> {
case PARKED, TIMED_PARKED, BLOCKED, WAIT, TIMED_WAIT -> {
// unmounted, not runnable
}
case UNPARKED, YIELDED -> {
case UNPARKED, UNBLOCKED, YIELDED -> {
// unmounted, runnable
}
case PARKING, TIMED_PARKING, YIELDING -> {
case PARKING, TIMED_PARKING, BLOCKING, YIELDING, WAITING, TIMED_WAITING -> {
return null; // in transition
}
default -> throw new InternalError("" + initialState);
@ -1073,7 +1207,7 @@ final class VirtualThread extends BaseVirtualThread {
setState(initialState);
}
boolean resubmit = switch (initialState) {
case UNPARKED, YIELDED -> {
case UNPARKED, UNBLOCKED, YIELDED -> {
// resubmit as task may have run while suspended
yield true;
}
@ -1081,6 +1215,15 @@ final class VirtualThread extends BaseVirtualThread {
// resubmit if unparked while suspended
yield parkPermit && compareAndSetState(initialState, UNPARKED);
}
case BLOCKED -> {
// resubmit if unblocked while suspended
yield blockPermit && compareAndSetState(BLOCKED, UNBLOCKED);
}
case WAIT, TIMED_WAIT -> {
// resubmit if notified or interrupted while waiting (Object.wait)
// waitTimeoutExpired will retry if the timed expired when suspended
yield (notified || interrupted) && compareAndSetState(initialState, UNBLOCKED);
}
default -> throw new InternalError();
};
if (resubmit) {
@ -1175,6 +1318,14 @@ final class VirtualThread extends BaseVirtualThread {
return interruptLock;
}
/**
* Returns a lock object for coordinating timed-wait setup and timeout handling.
*/
private Object timedWaitLock() {
// use this object for now to avoid the overhead of introducing another lock
return runContinuation;
}
/**
* Disallow the current thread be suspended or preempted.
*/
@ -1205,6 +1356,10 @@ final class VirtualThread extends BaseVirtualThread {
return U.compareAndSetInt(this, STATE, expectedValue, newValue);
}
private boolean compareAndSetOnWaitingList(boolean expectedValue, boolean newValue) {
return U.compareAndSetBoolean(this, ON_WAITING_LIST, expectedValue, newValue);
}
private void setParkPermit(boolean newValue) {
if (parkPermit != newValue) {
parkPermit = newValue;
@ -1219,10 +1374,6 @@ final class VirtualThread extends BaseVirtualThread {
}
}
private void setParkTimeout(long timeout) {
parkTimeout = timeout;
}
private void setCarrierThread(Thread carrier) {
// U.putReferenceRelease(this, CARRIER_THREAD, carrier);
this.carrierThread = carrier;
@ -1255,9 +1406,6 @@ final class VirtualThread extends BaseVirtualThread {
// ensure VTHREAD_GROUP is created, may be accessed by JVMTI
var group = Thread.virtualThreadGroup();
// ensure VirtualThreadPinnedEvent is loaded/initialized
U.ensureClassInitialized(VirtualThreadPinnedEvent.class);
}
/**
@ -1338,18 +1486,37 @@ final class VirtualThread extends BaseVirtualThread {
}
/**
* Reads the value of the jdk.tracePinnedThreads property to determine if stack
* traces should be printed when a carrier thread is pinned when a virtual thread
* attempts to park.
* Schedule virtual threads that are ready to be scheduled after they blocked on
* monitor enter.
*/
private static int tracePinningMode() {
String propValue = GetPropertyAction.privilegedGetProperty("jdk.tracePinnedThreads");
if (propValue != null) {
if (propValue.length() == 0 || "full".equalsIgnoreCase(propValue))
return 1;
if ("short".equalsIgnoreCase(propValue))
return 2;
private static void unblockVirtualThreads() {
while (true) {
VirtualThread vthread = takeVirtualThreadListToUnblock();
while (vthread != null) {
assert vthread.onWaitingList;
VirtualThread nextThread = vthread.next;
// remove from list and unblock
vthread.next = null;
boolean changed = vthread.compareAndSetOnWaitingList(true, false);
assert changed;
vthread.unblock();
vthread = nextThread;
}
}
return 0;
}
/**
* Retrieves the list of virtual threads that are waiting to be unblocked, waiting
* if necessary until a list of one or more threads becomes available.
*/
private static native VirtualThread takeVirtualThreadListToUnblock();
static {
var unblocker = InnocuousThread.newThread("VirtualThread-unblocker",
VirtualThread::unblockVirtualThreads);
unblocker.setDaemon(true);
unblocker.start();
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2008, 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2008, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -231,7 +231,7 @@ class MethodType
}
static final ReferencedKeySet<MethodType> internTable =
ReferencedKeySet.create(false, true, new Supplier<>() {
ReferencedKeySet.create(false, new Supplier<>() {
@Override
public Map<ReferenceKey<MethodType>, ReferenceKey<MethodType>> get() {
return new ConcurrentHashMap<>(512);

View file

@ -1134,7 +1134,7 @@ public final class StringConcatFactory {
};
static final ReferencedKeyMap<MethodType, SoftReference<MethodHandlePair>> CACHE =
ReferencedKeyMap.create(true, true,
ReferencedKeyMap.create(true,
new Supplier<>() {
@Override
public Map<ReferenceKey<MethodType>, SoftReference<MethodHandlePair>> get() {

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -35,7 +35,7 @@ final class Finalizer extends FinalReference<Object> { /* Package-private; must
same package as the Reference
class */
private static ReferenceQueue<Object> queue = new NativeReferenceQueue<>();
private static ReferenceQueue<Object> queue = new ReferenceQueue<>();
/** Head of doubly linked list of Finalizers awaiting finalization. */
private static Finalizer unfinalized = null;

View file

@ -1,92 +0,0 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package java.lang.ref;
/**
* An implementation of a ReferenceQueue that uses native monitors.
* The use of java.util.concurrent.lock locks interacts with various mechanisms,
* such as virtual threads and ForkJoinPool, that might not be appropriate for some
* low-level mechanisms, in particular MethodType's weak intern set.
*/
final class NativeReferenceQueue<T> extends ReferenceQueue<T> {
public NativeReferenceQueue() {
super(0);
}
private static class Lock { };
private final Lock lock = new Lock();
@Override
void signal() {
lock.notifyAll();
}
@Override
void await() throws InterruptedException {
lock.wait();
}
@Override
void await(long timeoutMillis) throws InterruptedException {
lock.wait(timeoutMillis);
}
@Override
boolean enqueue(Reference<? extends T> r) {
synchronized(lock) {
return enqueue0(r);
}
}
@Override
public Reference<? extends T> poll() {
if (headIsNull())
return null;
synchronized(lock) {
return poll0();
}
}
@Override
public Reference<? extends T> remove(long timeout)
throws IllegalArgumentException, InterruptedException {
if (timeout < 0)
throw new IllegalArgumentException("Negative timeout value");
if (timeout == 0)
return remove();
synchronized(lock) {
return remove0(timeout);
}
}
@Override
public Reference<? extends T> remove() throws InterruptedException {
synchronized(lock) {
return remove0();
}
}
}

View file

@ -330,11 +330,6 @@ public abstract sealed class Reference<T>
public void runFinalization() {
Finalizer.runFinalization();
}
@Override
public <T> ReferenceQueue<T> newNativeReferenceQueue() {
return new NativeReferenceQueue<T>();
}
});
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 1997, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 1997, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -25,11 +25,10 @@
package java.lang.ref;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import java.util.function.Consumer;
import jdk.internal.misc.VM;
import jdk.internal.vm.Continuation;
import jdk.internal.vm.ContinuationSupport;
/**
* Reference queues, to which registered reference objects are appended by the
@ -50,8 +49,6 @@ import jdk.internal.misc.VM;
public class ReferenceQueue<T> {
private static class Null extends ReferenceQueue<Object> {
public Null() { super(0); }
@Override
boolean enqueue(Reference<?> r) {
return false;
@ -64,35 +61,16 @@ public class ReferenceQueue<T> {
private volatile Reference<? extends T> head;
private long queueLength = 0;
private final ReentrantLock lock;
private final Condition notEmpty;
void signal() {
notEmpty.signalAll();
}
void await() throws InterruptedException {
notEmpty.await();
}
void await(long timeoutMillis) throws InterruptedException {
notEmpty.await(timeoutMillis, TimeUnit.MILLISECONDS);
}
private static class Lock { };
private final Lock lock = new Lock();
/**
* Constructs a new reference-object queue.
*/
public ReferenceQueue() {
this.lock = new ReentrantLock();
this.notEmpty = lock.newCondition();
}
ReferenceQueue(int dummy) {
this.lock = null;
this.notEmpty = null;
}
final boolean enqueue0(Reference<? extends T> r) { // must hold lock
private boolean enqueue0(Reference<? extends T> r) { // must hold lock
// Check that since getting the lock this reference hasn't already been
// enqueued (and even then removed)
ReferenceQueue<?> queue = r.queue;
@ -111,15 +89,11 @@ public class ReferenceQueue<T> {
if (r instanceof FinalReference) {
VM.addFinalRefCount(1);
}
signal();
lock.notifyAll();
return true;
}
final boolean headIsNull() {
return head == null;
}
final Reference<? extends T> poll0() { // must hold lock
private Reference<? extends T> poll0() { // must hold lock
Reference<? extends T> r = head;
if (r != null) {
r.queue = NULL;
@ -142,16 +116,14 @@ public class ReferenceQueue<T> {
return null;
}
final Reference<? extends T> remove0(long timeout)
throws IllegalArgumentException, InterruptedException { // must hold lock
private Reference<? extends T> remove0(long timeout) throws InterruptedException { // must hold lock
Reference<? extends T> r = poll0();
if (r != null) return r;
long start = System.nanoTime();
for (;;) {
await(timeout);
lock.wait(timeout);
r = poll0();
if (r != null) return r;
long end = System.nanoTime();
timeout -= (end - start) / 1000_000;
if (timeout <= 0) return null;
@ -159,23 +131,33 @@ public class ReferenceQueue<T> {
}
}
final Reference<? extends T> remove0() throws InterruptedException { // must hold lock
private Reference<? extends T> remove0() throws InterruptedException { // must hold lock
for (;;) {
var r = poll0();
if (r != null) return r;
await();
lock.wait();
}
}
boolean enqueue(Reference<? extends T> r) { /* Called only by Reference class */
lock.lock();
try {
synchronized (lock) {
return enqueue0(r);
} finally {
lock.unlock();
}
}
private boolean tryDisablePreempt() {
if (Thread.currentThread().isVirtual() && ContinuationSupport.isSupported()) {
Continuation.pin();
return true;
} else {
return false;
}
}
private void enablePreempt() {
Continuation.unpin();
}
/**
* Polls this queue to see if a reference object is available. If one is
* available without further delay then it is removed from the queue and
@ -186,13 +168,18 @@ public class ReferenceQueue<T> {
* @see java.lang.ref.Reference#enqueue()
*/
public Reference<? extends T> poll() {
if (headIsNull())
if (head == null)
return null;
lock.lock();
// Prevent a virtual thread from being preempted as this could potentially
// deadlock with a carrier that is polling the same reference queue.
boolean disabled = tryDisablePreempt();
try {
return poll0();
synchronized (lock) {
return poll0();
}
} finally {
lock.unlock();
if (disabled) enablePreempt();
}
}
@ -224,11 +211,8 @@ public class ReferenceQueue<T> {
if (timeout == 0)
return remove();
lock.lock();
try {
synchronized (lock) {
return remove0(timeout);
} finally {
lock.unlock();
}
}
@ -241,11 +225,8 @@ public class ReferenceQueue<T> {
* @see java.lang.ref.Reference#enqueue()
*/
public Reference<? extends T> remove() throws InterruptedException {
lock.lock();
try {
synchronized (lock) {
return remove0();
} finally {
lock.unlock();
}
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2014, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2014, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -50,11 +50,4 @@ public interface JavaLangRefAccess {
* Invoked by Runtime.runFinalization()
*/
void runFinalization();
/**
* Constructs a new NativeReferenceQueue.
*
* Invoked by jdk.internal.util.ReferencedKeyMap
*/
<T> ReferenceQueue<T> newNativeReferenceQueue();
}

View file

@ -1,31 +0,0 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package jdk.internal.event;
/**
* Event recording that a virtual thread has parked on its carrier thread.
*/
public class VirtualThreadPinnedEvent extends Event {
}

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2021, 2022, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2021, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -39,10 +39,10 @@ public class InternalLock {
private static final boolean CAN_USE_INTERNAL_LOCK;
static {
String s = System.getProperty("jdk.io.useMonitors");
if (s != null && (s.isEmpty() || s.equals("true"))) {
CAN_USE_INTERNAL_LOCK = false;
} else {
if (s != null && s.equals("false")) {
CAN_USE_INTERNAL_LOCK = true;
} else {
CAN_USE_INTERNAL_LOCK = false;
}
}

View file

@ -142,29 +142,7 @@ public final class ReferencedKeyMap<K, V> implements Map<K, V> {
*/
public static <K, V> ReferencedKeyMap<K, V>
create(boolean isSoft, Supplier<Map<ReferenceKey<K>, V>> supplier) {
return create(isSoft, false, supplier);
}
/**
* Create a new {@link ReferencedKeyMap} map.
*
* @param isSoft true if {@link SoftReference} keys are to
* be used, {@link WeakReference} otherwise.
* @param useNativeQueue true if uses NativeReferenceQueue
* otherwise use {@link ReferenceQueue}.
* @param supplier {@link Supplier} of the backing map
*
* @return a new map with {@link Reference} keys
*
* @param <K> the type of keys maintained by the new map
* @param <V> the type of mapped values
*/
public static <K, V> ReferencedKeyMap<K, V>
create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<K>, V>> supplier) {
return new ReferencedKeyMap<K, V>(isSoft, supplier.get(),
useNativeQueue ? SharedSecrets.getJavaLangRefAccess().newNativeReferenceQueue()
: new ReferenceQueue<>()
);
return new ReferencedKeyMap<K, V>(isSoft, supplier.get(), new ReferenceQueue<>());
}
/**

View file

@ -1,5 +1,5 @@
/*
* Copyright (c) 2023, Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2023, 2024, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
@ -106,25 +106,7 @@ public final class ReferencedKeySet<T> extends AbstractSet<T> {
*/
public static <E> ReferencedKeySet<E>
create(boolean isSoft, Supplier<Map<ReferenceKey<E>, ReferenceKey<E>>> supplier) {
return create(isSoft, false, supplier);
}
/**
* Create a new {@link ReferencedKeySet} elements.
*
* @param isSoft true if {@link SoftReference} elements are to
* be used, {@link WeakReference} otherwise.
* @param useNativeQueue true if uses NativeReferenceQueue
* otherwise use {@link ReferenceQueue}.
* @param supplier {@link Supplier} of the backing map
*
* @return a new set with {@link Reference} elements
*
* @param <E> the type of elements maintained by this set
*/
public static <E> ReferencedKeySet<E>
create(boolean isSoft, boolean useNativeQueue, Supplier<Map<ReferenceKey<E>, ReferenceKey<E>>> supplier) {
return new ReferencedKeySet<>(ReferencedKeyMap.create(isSoft, useNativeQueue, supplier));
return new ReferencedKeySet<>(ReferencedKeyMap.create(isSoft, supplier));
}
/**

View file

@ -59,7 +59,9 @@ public class Continuation {
public enum Pinned {
/** Native frame on stack */ NATIVE,
/** Monitor held */ MONITOR,
/** In critical section */ CRITICAL_SECTION }
/** In critical section */ CRITICAL_SECTION,
/** Exception (OOME/SOE) */ EXCEPTION
}
/** Preemption attempt result */
public enum PreemptStatus {
@ -85,6 +87,7 @@ public class Continuation {
case 2 -> Pinned.CRITICAL_SECTION;
case 3 -> Pinned.NATIVE;
case 4 -> Pinned.MONITOR;
case 5 -> Pinned.EXCEPTION;
default -> throw new AssertionError("Unknown pinned reason: " + reason);
};
}
@ -358,8 +361,6 @@ public class Continuation {
@Hidden
@JvmtiHideEvents
private boolean yield0(ContinuationScope scope, Continuation child) {
preempted = false;
if (scope != this.scope)
this.yieldInfo = scope;
int res = doYield();

View file

@ -26,7 +26,9 @@ package sun.nio.ch;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ -40,7 +42,7 @@ import sun.security.action.GetPropertyAction;
* Polls file descriptors. Virtual threads invoke the poll method to park
* until a given file descriptor is ready for I/O.
*/
abstract class Poller {
public abstract class Poller {
private static final Pollers POLLERS;
static {
try {
@ -142,6 +144,20 @@ abstract class Poller {
}
}
/**
* Parks the current thread until a Selector's file descriptor is ready.
* @param fdVal the Selector's file descriptor
* @param nanos the waiting time or 0 to wait indefinitely
*/
static void pollSelector(int fdVal, long nanos) throws IOException {
assert nanos >= 0L;
Poller poller = POLLERS.masterPoller();
if (poller == null) {
poller = POLLERS.readPoller(fdVal);
}
poller.poll(fdVal, nanos, () -> true);
}
/**
* If there is a thread polling the given file descriptor for the given event then
* the thread is unparked.
@ -258,6 +274,18 @@ abstract class Poller {
}
}
/**
* Returns the number I/O operations currently registered with this poller.
*/
public int registered() {
return map.size();
}
@Override
public String toString() {
return Objects.toIdentityString(this) + " [registered = " + registered() + "]";
}
/**
* The Pollers used for read and write events.
*/
@ -344,6 +372,13 @@ abstract class Poller {
}
}
/**
* Returns the master poller, or null if there is no master poller.
*/
Poller masterPoller() {
return masterPoller;
}
/**
* Returns the read poller for the given file descriptor.
*/
@ -360,6 +395,21 @@ abstract class Poller {
return writePollers[index];
}
/**
* Return the list of read pollers.
*/
List<Poller> readPollers() {
return List.of(readPollers);
}
/**
* Return the list of write pollers.
*/
List<Poller> writePollers() {
return List.of(writePollers);
}
/**
* Reads the given property name to get the poller count. If the property is
* set then the value must be a power of 2. Returns 1 if the property is not

View file

@ -25,6 +25,7 @@
package sun.security.ssl;
import java.lang.invoke.MethodHandles;
import java.net.Socket;
import java.security.*;
import java.security.cert.*;
@ -51,6 +52,15 @@ import sun.security.validator.*;
final class X509TrustManagerImpl extends X509ExtendedTrustManager
implements X509TrustManager {
static {
// eagerly initialize to avoid pinning virtual thread during TLS handshake
try {
MethodHandles.lookup().ensureInitialized(AnchorCertificates.class);
} catch (IllegalAccessException e) {
throw new ExceptionInInitializerError(e);
}
}
private final String validatorType;
/**