| /* | |
| * Copyright (c) 2017, 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 | |
| * 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 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; | |
| import java.util.concurrent.ThreadFactory; | |
| import java.util.concurrent.locks.LockSupport; | |
| import java.util.function.BooleanSupplier; | |
| import jdk.internal.misc.InnocuousThread; | |
| import jdk.internal.vm.annotation.Stable; | |
| /** | |
| * Polls file descriptors. Virtual threads invoke the poll method to park | |
| * until a given file descriptor is ready for I/O. | |
| */ | |
| public abstract class Poller { | |
| private static final Pollers POLLERS; | |
| static { | |
| try { | |
| var pollers = new Pollers(); | |
| pollers.start(); | |
| POLLERS = pollers; | |
| } catch (IOException ioe) { | |
| throw new ExceptionInInitializerError(ioe); | |
| } | |
| } | |
| // the poller or sub-poller thread | |
| private @Stable Thread owner; | |
| // maps file descriptors to parked Thread | |
| private final Map<Integer, Thread> map = new ConcurrentHashMap<>(); | |
| /** | |
| * Poller mode. | |
| */ | |
| enum Mode { | |
| /** | |
| * ReadPoller and WritePoller are dedicated platform threads that block waiting | |
| * for events and unpark virtual threads when file descriptors are ready for I/O. | |
| */ | |
| SYSTEM_THREADS, | |
| /** | |
| * ReadPoller and WritePoller threads are virtual threads that poll for events, | |
| * yielding between polls and unparking virtual threads when file descriptors are | |
| * ready for I/O. If there are no events then the poller threads park until there | |
| * are I/O events to poll. This mode helps to integrate polling with virtual | |
| * thread scheduling. The approach is similar to the default scheme in "User-level | |
| * Threading: Have Your Cake and Eat It Too" by Karsten and Barghi 2020 | |
| * (https://dl.acm.org/doi/10.1145/3379483). | |
| */ | |
| VTHREAD_POLLERS | |
| } | |
| /** | |
| * Initialize a Poller. | |
| */ | |
| protected Poller() { | |
| } | |
| /** | |
| * Returns the poller's file descriptor, used when the read and write poller threads | |
| * are virtual threads. | |
| * | |
| * @throws UnsupportedOperationException if not supported | |
| */ | |
| int fdVal() { | |
| throw new UnsupportedOperationException(); | |
| } | |
| /** | |
| * Register the file descriptor. The registration is "one shot", meaning it should | |
| * be polled at most once. | |
| */ | |
| abstract void implRegister(int fdVal) throws IOException; | |
| /** | |
| * Deregister the file descriptor. | |
| * @param polled true if the file descriptor has already been polled | |
| */ | |
| abstract void implDeregister(int fdVal, boolean polled); | |
| /** | |
| * Poll for events. The {@link #polled(int)} method is invoked for each | |
| * polled file descriptor. | |
| * | |
| * @param timeout if positive then block for up to {@code timeout} milliseconds, | |
| * if zero then don't block, if -1 then block indefinitely | |
| * @return the number of file descriptors polled | |
| */ | |
| abstract int poll(int timeout) throws IOException; | |
| /** | |
| * Callback by the poll method when a file descriptor is polled. | |
| */ | |
| final void polled(int fdVal) { | |
| wakeup(fdVal); | |
| } | |
| /** | |
| * Parks the current thread until a file descriptor is ready for the given op. | |
| * @param fdVal the file descriptor | |
| * @param event POLLIN or POLLOUT | |
| * @param nanos the waiting time or 0 to wait indefinitely | |
| * @param supplier supplies a boolean to indicate if the enclosing object is open | |
| */ | |
| static void poll(int fdVal, int event, long nanos, BooleanSupplier supplier) | |
| throws IOException | |
| { | |
| assert nanos >= 0L; | |
| if (event == Net.POLLIN) { | |
| POLLERS.readPoller(fdVal).poll(fdVal, nanos, supplier); | |
| } else if (event == Net.POLLOUT) { | |
| POLLERS.writePoller(fdVal).poll(fdVal, nanos, supplier); | |
| } else { | |
| assert false; | |
| } | |
| } | |
| /** | |
| * 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. | |
| */ | |
| static void stopPoll(int fdVal, int event) { | |
| if (event == Net.POLLIN) { | |
| POLLERS.readPoller(fdVal).wakeup(fdVal); | |
| } else if (event == Net.POLLOUT) { | |
| POLLERS.writePoller(fdVal).wakeup(fdVal); | |
| } else { | |
| throw new IllegalArgumentException(); | |
| } | |
| } | |
| /** | |
| * If there are any threads polling the given file descriptor then they are unparked. | |
| */ | |
| static void stopPoll(int fdVal) { | |
| stopPoll(fdVal, Net.POLLIN); | |
| stopPoll(fdVal, Net.POLLOUT); | |
| } | |
| /** | |
| * Parks the current thread until a file descriptor is ready. | |
| */ | |
| private void poll(int fdVal, long nanos, BooleanSupplier supplier) throws IOException { | |
| register(fdVal); | |
| try { | |
| boolean isOpen = supplier.getAsBoolean(); | |
| if (isOpen) { | |
| if (nanos > 0) { | |
| LockSupport.parkNanos(nanos); | |
| } else { | |
| LockSupport.park(); | |
| } | |
| } | |
| } finally { | |
| deregister(fdVal); | |
| } | |
| } | |
| /** | |
| * Registers the file descriptor to be polled at most once when the file descriptor | |
| * is ready for I/O. | |
| */ | |
| private void register(int fdVal) throws IOException { | |
| Thread previous = map.put(fdVal, Thread.currentThread()); | |
| assert previous == null; | |
| try { | |
| implRegister(fdVal); | |
| } catch (Throwable t) { | |
| map.remove(fdVal); | |
| throw t; | |
| } | |
| } | |
| /** | |
| * Deregister the file descriptor so that the file descriptor is not polled. | |
| */ | |
| private void deregister(int fdVal) { | |
| Thread previous = map.remove(fdVal); | |
| boolean polled = (previous == null); | |
| assert polled || previous == Thread.currentThread(); | |
| implDeregister(fdVal, polled); | |
| } | |
| /** | |
| * Unparks any thread that is polling the given file descriptor. | |
| */ | |
| private void wakeup(int fdVal) { | |
| Thread t = map.remove(fdVal); | |
| if (t != null) { | |
| LockSupport.unpark(t); | |
| } | |
| } | |
| /** | |
| * Master polling loop. The {@link #polled(int)} method is invoked for each file | |
| * descriptor that is polled. | |
| */ | |
| private void pollerLoop() { | |
| owner = Thread.currentThread(); | |
| try { | |
| for (;;) { | |
| poll(-1); | |
| } | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| /** | |
| * Sub-poller polling loop. The {@link #polled(int)} method is invoked for each file | |
| * descriptor that is polled. | |
| * | |
| * The sub-poller registers its file descriptor with the master poller to park until | |
| * there are events to poll. When unparked, it does non-blocking polls and parks | |
| * again when there are no more events. The sub-poller yields after each poll to help | |
| * with fairness and to avoid re-registering with the master poller where possible. | |
| */ | |
| private void subPollerLoop(Poller masterPoller) { | |
| assert Thread.currentThread().isVirtual(); | |
| owner = Thread.currentThread(); | |
| try { | |
| int polled = 0; | |
| for (;;) { | |
| if (polled == 0) { | |
| masterPoller.poll(fdVal(), 0, () -> true); // park | |
| } else { | |
| Thread.yield(); | |
| } | |
| polled = poll(0); | |
| } | |
| } catch (Exception e) { | |
| e.printStackTrace(); | |
| } | |
| } | |
| /** | |
| * Returns the number I/O operations currently registered with this poller. | |
| */ | |
| public int registered() { | |
| return map.size(); | |
| } | |
| @Override | |
| public String toString() { | |
| return String.format("%s [registered = %d, owner = %s]", | |
| Objects.toIdentityString(this), registered(), owner); | |
| } | |
| /** | |
| * The Pollers used for read and write events. | |
| */ | |
| private static class Pollers { | |
| private final PollerProvider provider; | |
| private final Poller.Mode pollerMode; | |
| private final Poller masterPoller; | |
| private final Poller[] readPollers; | |
| private final Poller[] writePollers; | |
| // used by start method to executor is kept alive | |
| private Executor executor; | |
| /** | |
| * Creates the Poller instances based on configuration. | |
| */ | |
| Pollers() throws IOException { | |
| PollerProvider provider = PollerProvider.provider(); | |
| Poller.Mode mode; | |
| String s = System.getProperty("jdk.pollerMode"); | |
| if (s != null) { | |
| if (s.equalsIgnoreCase(Mode.SYSTEM_THREADS.name()) || s.equals("1")) { | |
| mode = Mode.SYSTEM_THREADS; | |
| } else if (s.equalsIgnoreCase(Mode.VTHREAD_POLLERS.name()) || s.equals("2")) { | |
| mode = Mode.VTHREAD_POLLERS; | |
| } else { | |
| throw new RuntimeException("Can't parse '" + s + "' as polling mode"); | |
| } | |
| } else { | |
| mode = provider.defaultPollerMode(); | |
| } | |
| // vthread poller mode needs a master poller | |
| Poller masterPoller = (mode == Mode.VTHREAD_POLLERS) | |
| ? provider.readPoller(false) | |
| : null; | |
| // read pollers (or sub-pollers) | |
| int readPollerCount = pollerCount("jdk.readPollers", provider.defaultReadPollers(mode)); | |
| Poller[] readPollers = new Poller[readPollerCount]; | |
| for (int i = 0; i < readPollerCount; i++) { | |
| readPollers[i] = provider.readPoller(mode == Mode.VTHREAD_POLLERS); | |
| } | |
| // write pollers (or sub-pollers) | |
| int writePollerCount = pollerCount("jdk.writePollers", provider.defaultWritePollers(mode)); | |
| Poller[] writePollers = new Poller[writePollerCount]; | |
| for (int i = 0; i < writePollerCount; i++) { | |
| writePollers[i] = provider.writePoller(mode == Mode.VTHREAD_POLLERS); | |
| } | |
| this.provider = provider; | |
| this.pollerMode = mode; | |
| this.masterPoller = masterPoller; | |
| this.readPollers = readPollers; | |
| this.writePollers = writePollers; | |
| } | |
| /** | |
| * Starts the Poller threads. | |
| */ | |
| void start() { | |
| if (pollerMode == Mode.VTHREAD_POLLERS) { | |
| startPlatformThread("MasterPoller", masterPoller::pollerLoop); | |
| ThreadFactory factory = Thread.ofVirtual() | |
| .inheritInheritableThreadLocals(false) | |
| .name("SubPoller-", 0) | |
| .uncaughtExceptionHandler((t, e) -> e.printStackTrace()) | |
| .factory(); | |
| executor = Executors.newThreadPerTaskExecutor(factory); | |
| Arrays.stream(readPollers).forEach(p -> { | |
| executor.execute(() -> p.subPollerLoop(masterPoller)); | |
| }); | |
| Arrays.stream(writePollers).forEach(p -> { | |
| executor.execute(() -> p.subPollerLoop(masterPoller)); | |
| }); | |
| } else { | |
| Arrays.stream(readPollers).forEach(p -> { | |
| startPlatformThread("Read-Poller", p::pollerLoop); | |
| }); | |
| Arrays.stream(writePollers).forEach(p -> { | |
| startPlatformThread("Write-Poller", p::pollerLoop); | |
| }); | |
| } | |
| } | |
| /** | |
| * 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. | |
| */ | |
| Poller readPoller(int fdVal) { | |
| int index = provider.fdValToIndex(fdVal, readPollers.length); | |
| return readPollers[index]; | |
| } | |
| /** | |
| * Returns the write poller for the given file descriptor. | |
| */ | |
| Poller writePoller(int fdVal) { | |
| int index = provider.fdValToIndex(fdVal, writePollers.length); | |
| 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 | |
| * set. | |
| * @throws IllegalArgumentException if the property is set to a value that | |
| * is not a power of 2. | |
| */ | |
| private static int pollerCount(String propName, int defaultCount) { | |
| String s = System.getProperty(propName); | |
| int count = (s != null) ? Integer.parseInt(s) : defaultCount; | |
| // check power of 2 | |
| if (count != Integer.highestOneBit(count)) { | |
| String msg = propName + " is set to a value that is not a power of 2"; | |
| throw new IllegalArgumentException(msg); | |
| } | |
| return count; | |
| } | |
| /** | |
| * Starts a platform thread to run the given task. | |
| */ | |
| private void startPlatformThread(String name, Runnable task) { | |
| try { | |
| Thread thread = InnocuousThread.newSystemThread(name, task); | |
| thread.setDaemon(true); | |
| thread.setUncaughtExceptionHandler((t, e) -> e.printStackTrace()); | |
| thread.start(); | |
| } catch (Exception e) { | |
| throw new InternalError(e); | |
| } | |
| } | |
| } | |
| /** | |
| * Return the master poller or null if there is no master poller. | |
| */ | |
| public static Poller masterPoller() { | |
| return POLLERS.masterPoller(); | |
| } | |
| /** | |
| * Return the list of read pollers. | |
| */ | |
| public static List<Poller> readPollers() { | |
| return POLLERS.readPollers(); | |
| } | |
| /** | |
| * Return the list of write pollers. | |
| */ | |
| public static List<Poller> writePollers() { | |
| return POLLERS.writePollers(); | |
| } | |
| } |