/* * 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 * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * 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. */ /* * @test id=default * @summary Test virtual threads doing selection operations * @library /test/lib * @run junit/othervm --enable-native-access=ALL-UNNAMED SelectorOps */ /* * @test id=poller-modes * @requires (os.family == "linux") | (os.family == "mac") * @library /test/lib * @run junit/othervm -Djdk.pollerMode=1 --enable-native-access=ALL-UNNAMED SelectorOps * @run junit/othervm -Djdk.pollerMode=2 --enable-native-access=ALL-UNNAMED SelectorOps */ /* * @test id=no-vmcontinuations * @requires vm.continuations * @library /test/lib * @run junit/othervm -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations * --enable-native-access=ALL-UNNAMED SelectorOps */ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.channels.Pipe; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.concurrent.TimeUnit; import jdk.test.lib.thread.VThreadRunner; import jdk.test.lib.thread.VThreadPinner; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; import static org.junit.jupiter.api.Assertions.*; class SelectorOps { private static String selectorClassName; // platform specific class name @BeforeAll static void setup() throws Exception { try (Selector sel = Selector.open()) { selectorClassName = sel.getClass().getName(); } } /** * Test that select wakes up when a channel is ready for I/O. */ @ParameterizedTest @ValueSource(booleans = { true, false }) public void testSelect(boolean timed) throws Exception { VThreadRunner.run(() -> { Pipe p = Pipe.open(); try (Selector sel = Selector.open()) { Pipe.SinkChannel sink = p.sink(); Pipe.SourceChannel source = p.source(); source.configureBlocking(false); SelectionKey key = source.register(sel, SelectionKey.OP_READ); // write to sink to ensure source is readable ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)); onSelect(() -> sink.write(buf)); int n = timed ? sel.select(60_000) : sel.select(); assertEquals(1, n); assertTrue(sel.isOpen()); assertTrue(key.isReadable()); } finally { closePipe(p); } }); } /** * Test that select wakes up when a channel is ready for I/O and thread is pinned. */ @ParameterizedTest @ValueSource(booleans = { true, false }) public void testSelectWhenPinned(boolean timed) throws Exception { VThreadPinner.runPinned(() -> { testSelect(timed); }); } /** * Test that select wakes up when timeout is reached. */ @Test public void testSelectTimeout() throws Exception { VThreadRunner.run(() -> { Pipe p = Pipe.open(); try (Selector sel = Selector.open()) { Pipe.SourceChannel source = p.source(); source.configureBlocking(false); SelectionKey key = source.register(sel, SelectionKey.OP_READ); long start = millisTime(); int n = sel.select(1000); expectDuration(start, /*min*/500, /*max*/20_000); assertEquals(0, n); assertTrue(sel.isOpen()); assertFalse(key.isReadable()); } finally { closePipe(p); } }); } /** * Test that select wakes up when timeout is reached and thread is pinned. */ @Test public void testSelectTimeoutWhenPinned() throws Exception { VThreadPinner.runPinned(() -> { testSelectTimeout(); }); } /** * Test that selectNow is non-blocking. */ @Test public void testSelectNow() throws Exception { VThreadRunner.run(() -> { Pipe p = Pipe.open(); try (Selector sel = Selector.open()) { Pipe.SinkChannel sink = p.sink(); Pipe.SourceChannel source = p.source(); source.configureBlocking(false); SelectionKey key = source.register(sel, SelectionKey.OP_READ); // selectNow should return immediately for (int i = 0; i < 3; i++) { long start = millisTime(); int n = sel.selectNow(); expectDuration(start, -1, /*max*/20_000); assertEquals(0, n); } // write to sink to ensure source is readable ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8)); sink.write(buf); // call selectNow until key added to selected key set int n = 0; while (n == 0) { Thread.sleep(10); long start = millisTime(); n = sel.selectNow(); expectDuration(start, -1, /*max*/20_000); } assertEquals(1, n); assertTrue(sel.isOpen()); assertTrue(key.isReadable()); } finally { closePipe(p); } }); } /** * Test calling wakeup before select. */ @Test public void testWakeupBeforeSelect() throws Exception { VThreadRunner.run(() -> { try (Selector sel = Selector.open()) { sel.wakeup(); int n = sel.select(); assertEquals(0, n); assertTrue(sel.isOpen()); } }); } /** * Test calling wakeup before select and thread is pinned. */ @Test public void testWakeupBeforeSelectWhenPinned() throws Exception { VThreadPinner.runPinned(() -> { testWakeupBeforeSelect(); }); } /** * Test calling wakeup while a thread is blocked in select. */ @Test public void testWakeupDuringSelect() throws Exception { VThreadRunner.run(() -> { try (Selector sel = Selector.open()) { onSelect(sel::wakeup); int n = sel.select(); assertEquals(0, n); assertTrue(sel.isOpen()); } }); } /** * Test calling wakeup while a thread is blocked in select and the thread is pinned. */ @Test public void testWakeupDuringSelectWhenPinned() throws Exception { VThreadPinner.runPinned(() -> { testWakeupDuringSelect(); }); } /** * Test closing selector while a thread is blocked in select. */ @Test public void testCloseDuringSelect() throws Exception { VThreadRunner.run(() -> { try (Selector sel = Selector.open()) { onSelect(sel::close); int n = sel.select(); assertEquals(0, n); assertFalse(sel.isOpen()); } }); } /** * Test closing selector while a thread is blocked in select and the thread is pinned. */ @Test public void testCloseDuringSelectWhenPinned() throws Exception { VThreadPinner.runPinned(() -> { testCloseDuringSelect(); }); } /** * Test calling select with interrupt status set. */ @Test public void testInterruptBeforeSelect() throws Exception { VThreadRunner.run(() -> { try (Selector sel = Selector.open()) { Thread me = Thread.currentThread(); me.interrupt(); int n = sel.select(); assertEquals(0, n); assertTrue(me.isInterrupted()); assertTrue(sel.isOpen()); } }); } /** * Test calling select with interrupt status set and thread is pinned. */ @Test public void testInterruptBeforeSelectWhenPinned() throws Exception { VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); }); } /** * Test interrupting a thread blocked in select. */ @Test public void testInterruptDuringSelect() throws Exception { VThreadRunner.run(() -> { try (Selector sel = Selector.open()) { Thread me = Thread.currentThread(); onSelect(me::interrupt); int n = sel.select(); assertEquals(0, n); assertTrue(me.isInterrupted()); assertTrue(sel.isOpen()); } }); } /** * Test interrupting a thread blocked in select and the thread is pinned. */ @Test public void testInterruptDuringSelectWhenPinned() throws Exception { VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); }); } /** * Close a pipe's sink and source channels. */ private void closePipe(Pipe p) { try { p.sink().close(); } catch (IOException ignore) { } try { p.source().close(); } catch (IOException ignore) { } } /** * Runs the given action when the current thread is sampled in a selection operation. */ private void onSelect(VThreadRunner.ThrowingRunnable action) { Thread target = Thread.currentThread(); Thread.ofPlatform().daemon().start(() -> { try { boolean found = false; while (!found) { Thread.sleep(20); StackTraceElement[] stack = target.getStackTrace(); found = Arrays.stream(stack) .anyMatch(e -> selectorClassName.equals(e.getClassName()) && "doSelect".equals(e.getMethodName())); } action.run(); } catch (Exception e) { e.printStackTrace(); } }); } /** * Returns the current time in milliseconds. */ private static long millisTime() { long now = System.nanoTime(); return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS); } /** * Check the duration of a task * @param start start time, in milliseconds * @param min minimum expected duration, in milliseconds * @param max maximum expected duration, in milliseconds * @return the duration (now - start), in milliseconds */ private static void expectDuration(long start, long min, long max) { long duration = millisTime() - start; assertTrue(duration >= min, "Duration " + duration + "ms, expected >= " + min + "ms"); assertTrue(duration <= max, "Duration " + duration + "ms, expected <= " + max + "ms"); } }