1 /*
  2  * Copyright (c) 2023, 2025, Oracle and/or its affiliates. All rights reserved.
  3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  4  *
  5  * This code is free software; you can redistribute it and/or modify it
  6  * under the terms of the GNU General Public License version 2 only, as
  7  * published by the Free Software Foundation.
  8  *
  9  * This code is distributed in the hope that it will be useful, but WITHOUT
 10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 12  * version 2 for more details (a copy is included in the LICENSE file that
 13  * accompanied this code).
 14  *
 15  * You should have received a copy of the GNU General Public License version
 16  * 2 along with this work; if not, write to the Free Software Foundation,
 17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 18  *
 19  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 20  * or visit www.oracle.com if you need additional information or have any
 21  * questions.
 22  */
 23 
 24 /*
 25  * @test id=default
 26  * @summary Test virtual threads doing selection operations
 27  * @library /test/lib
 28  * @run junit/othervm/native --enable-native-access=ALL-UNNAMED SelectorOps
 29  */
 30 
 31 /*
 32  * @test id=poller-modes
 33  * @requires (os.family == "linux") | (os.family == "mac")
 34  * @library /test/lib
 35  * @run junit/othervm/native -Djdk.pollerMode=1 --enable-native-access=ALL-UNNAMED SelectorOps
 36  * @run junit/othervm/native -Djdk.pollerMode=2 --enable-native-access=ALL-UNNAMED SelectorOps
 37  * @run junit/othervm/native -Djdk.pollerMode=3 --enable-native-access=ALL-UNNAMED SelectorOps
 38  */
 39 
 40 /*
 41  * @test id=io_uring
 42  * @requires os.family == "linux"
 43  * @library /test/lib
 44  * @run junit/othervm -Djdk.pollerMode=1 -Djdk.io_uring=true --enable-native-access=ALL-UNNAMED SelectorOps
 45  * @run junit/othervm -Djdk.pollerMode=2 -Djdk.io_uring=true --enable-native-access=ALL-UNNAMED SelectorOps
 46  * @run junit/othervm -Djdk.pollerMode=3 -Djdk.io_uring=true --enable-native-access=ALL-UNNAMED SelectorOps
 47  * @run junit/othervm -Djdk.pollerMode=1 -Djdk.io_uring=true -Djdk.io_uring.sqpoll_idle=20 --enable-native-access=ALL-UNNAMED SelectorOps
 48  * @run junit/othervm -Djdk.pollerMode=2 -Djdk.io_uring=true -Djdk.io_uring.sqpoll_idle=20 --enable-native-access=ALL-UNNAMED SelectorOps
 49  * @run junit/othervm -Djdk.pollerMode=3 -Djdk.io_uring=true -Djdk.io_uring.sqpoll_idle=20 --enable-native-access=ALL-UNNAMED SelectorOps
 50  */
 51 
 52 /*
 53  * @test id=no-vmcontinuations
 54  * @requires vm.continuations
 55  * @library /test/lib
 56  * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
 57  *     --enable-native-access=ALL-UNNAMED SelectorOps
 58  */
 59 
 60 import java.io.IOException;
 61 import java.nio.ByteBuffer;
 62 import java.nio.channels.Pipe;
 63 import java.nio.channels.SelectionKey;
 64 import java.nio.channels.Selector;
 65 import java.nio.charset.StandardCharsets;
 66 import java.util.Arrays;
 67 import java.util.concurrent.TimeUnit;
 68 
 69 import jdk.test.lib.thread.VThreadRunner;
 70 import jdk.test.lib.thread.VThreadPinner;
 71 import org.junit.jupiter.api.Test;
 72 import org.junit.jupiter.api.BeforeAll;
 73 import org.junit.jupiter.params.ParameterizedTest;
 74 import org.junit.jupiter.params.provider.ValueSource;
 75 import static org.junit.jupiter.api.Assertions.*;
 76 
 77 class SelectorOps {
 78     private static String selectorClassName;  // platform specific class name
 79 
 80     @BeforeAll
 81     static void setup() throws Exception {
 82         try (Selector sel = Selector.open()) {
 83             selectorClassName = sel.getClass().getName();
 84         }
 85     }
 86 
 87     /**
 88      * Test that select wakes up when a channel is ready for I/O.
 89      */
 90     @ParameterizedTest
 91     @ValueSource(booleans = { true, false })
 92     public void testSelect(boolean timed) throws Exception {
 93         VThreadRunner.run(() -> {
 94             Pipe p = Pipe.open();
 95             try (Selector sel = Selector.open()) {
 96                 Pipe.SinkChannel sink = p.sink();
 97                 Pipe.SourceChannel source = p.source();
 98                 source.configureBlocking(false);
 99                 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
100 
101                 // write to sink to ensure source is readable
102                 ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
103                 onSelect(() -> sink.write(buf));
104 
105                 int n = timed ? sel.select(60_000) : sel.select();
106                 assertEquals(1, n);
107                 assertTrue(sel.isOpen());
108                 assertTrue(key.isReadable());
109             } finally {
110                 closePipe(p);
111             }
112         });
113     }
114 
115     /**
116      * Test that select wakes up when a channel is ready for I/O and thread is pinned.
117      */
118     @ParameterizedTest
119     @ValueSource(booleans = { true, false })
120     public void testSelectWhenPinned(boolean timed) throws Exception {
121         VThreadPinner.runPinned(() -> { testSelect(timed); });
122     }
123 
124     /**
125      * Test that select wakes up when timeout is reached.
126      */
127     @Test
128     public void testSelectTimeout() throws Exception {
129         VThreadRunner.run(() -> {
130             Pipe p = Pipe.open();
131             try (Selector sel = Selector.open()) {
132                 Pipe.SourceChannel source = p.source();
133                 source.configureBlocking(false);
134                 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
135 
136                 long start = millisTime();
137                 int n = sel.select(1000);
138                 expectDuration(start, /*min*/500, /*max*/20_000);
139 
140                 assertEquals(0, n);
141                 assertTrue(sel.isOpen());
142                 assertFalse(key.isReadable());
143             } finally {
144                 closePipe(p);
145             }
146         });
147     }
148 
149     /**
150      * Test that select wakes up when timeout is reached and thread is pinned.
151      */
152     @Test
153     public void testSelectTimeoutWhenPinned() throws Exception {
154         VThreadPinner.runPinned(() -> { testSelectTimeout(); });
155     }
156 
157     /**
158      * Test that selectNow is non-blocking.
159      */
160     @Test
161     public void testSelectNow() throws Exception {
162         VThreadRunner.run(() -> {
163             Pipe p = Pipe.open();
164             try (Selector sel = Selector.open()) {
165                 Pipe.SinkChannel sink = p.sink();
166                 Pipe.SourceChannel source = p.source();
167                 source.configureBlocking(false);
168                 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
169 
170                 // selectNow should return immediately
171                 for (int i = 0; i < 3; i++) {
172                     long start = millisTime();
173                     int n = sel.selectNow();
174                     expectDuration(start, -1, /*max*/20_000);
175                     assertEquals(0, n);
176                 }
177 
178                 // write to sink to ensure source is readable
179                 ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
180                 sink.write(buf);
181 
182                 // call selectNow until key added to selected key set
183                 int n = 0;
184                 while (n == 0) {
185                     Thread.sleep(10);
186                     long start = millisTime();
187                     n = sel.selectNow();
188                     expectDuration(start, -1, /*max*/20_000);
189                 }
190                 assertEquals(1, n);
191                 assertTrue(sel.isOpen());
192                 assertTrue(key.isReadable());
193             } finally {
194                 closePipe(p);
195             }
196         });
197     }
198 
199     /**
200      * Test calling wakeup before select.
201      */
202     @Test
203     public void testWakeupBeforeSelect() throws Exception {
204         VThreadRunner.run(() -> {
205             try (Selector sel = Selector.open()) {
206                 sel.wakeup();
207                 int n = sel.select();
208                 assertEquals(0, n);
209                 assertTrue(sel.isOpen());
210             }
211         });
212     }
213 
214     /**
215      * Test calling wakeup before select and thread is pinned.
216      */
217     @Test
218     public void testWakeupBeforeSelectWhenPinned() throws Exception {
219         VThreadPinner.runPinned(() -> { testWakeupBeforeSelect(); });
220     }
221 
222     /**
223      * Test calling wakeup while a thread is blocked in select.
224      */
225     @Test
226     public void testWakeupDuringSelect() throws Exception {
227         VThreadRunner.run(() -> {
228             try (Selector sel = Selector.open()) {
229                 onSelect(sel::wakeup);
230                 int n = sel.select();
231                 assertEquals(0, n);
232                 assertTrue(sel.isOpen());
233             }
234         });
235     }
236 
237     /**
238      * Test calling wakeup while a thread is blocked in select and the thread is pinned.
239      */
240     @Test
241     public void testWakeupDuringSelectWhenPinned() throws Exception {
242         VThreadPinner.runPinned(() -> { testWakeupDuringSelect(); });
243     }
244 
245     /**
246      * Test closing selector while a thread is blocked in select.
247      */
248     @Test
249     public void testCloseDuringSelect() throws Exception {
250         VThreadRunner.run(() -> {
251             try (Selector sel = Selector.open()) {
252                 onSelect(sel::close);
253                 int n = sel.select();
254                 assertEquals(0, n);
255                 assertFalse(sel.isOpen());
256             }
257         });
258     }
259 
260     /**
261      * Test closing selector while a thread is blocked in select and the thread is pinned.
262      */
263     @Test
264     public void testCloseDuringSelectWhenPinned() throws Exception {
265         VThreadPinner.runPinned(() -> { testCloseDuringSelect(); });
266     }
267 
268     /**
269      * Test calling select with interrupt status set.
270      */
271     @Test
272     public void testInterruptBeforeSelect() throws Exception {
273         VThreadRunner.run(() -> {
274             try (Selector sel = Selector.open()) {
275                 Thread me = Thread.currentThread();
276                 me.interrupt();
277                 int n = sel.select();
278                 assertEquals(0, n);
279                 assertTrue(me.isInterrupted());
280                 assertTrue(sel.isOpen());
281             }
282         });
283     }
284 
285     /**
286      * Test calling select with interrupt status set and thread is pinned.
287      */
288     @Test
289     public void testInterruptBeforeSelectWhenPinned() throws Exception {
290         VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); });
291     }
292 
293     /**
294      * Test interrupting a thread blocked in select.
295      */
296     @Test
297     public void testInterruptDuringSelect() throws Exception {
298         VThreadRunner.run(() -> {
299             try (Selector sel = Selector.open()) {
300                 Thread me = Thread.currentThread();
301                 onSelect(me::interrupt);
302                 int n = sel.select();
303                 assertEquals(0, n);
304                 assertTrue(me.isInterrupted());
305                 assertTrue(sel.isOpen());
306             }
307         });
308     }
309 
310     /**
311      * Test interrupting a thread blocked in select and the thread is pinned.
312      */
313     @Test
314     public void testInterruptDuringSelectWhenPinned() throws Exception {
315         VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); });
316     }
317 
318     /**
319      * Close a pipe's sink and source channels.
320      */
321     private void closePipe(Pipe p) {
322         try { p.sink().close(); } catch (IOException ignore) { }
323         try { p.source().close(); } catch (IOException ignore) { }
324     }
325 
326     /**
327      * Runs the given action when the current thread is sampled in a selection operation.
328      */
329     private void onSelect(VThreadRunner.ThrowingRunnable<Exception> action) {
330         Thread target = Thread.currentThread();
331         Thread.ofPlatform().daemon().start(() -> {
332             try {
333                 boolean found = false;
334                 while (!found) {
335                     Thread.sleep(20);
336                     StackTraceElement[] stack = target.getStackTrace();
337                     found = Arrays.stream(stack)
338                             .anyMatch(e -> selectorClassName.equals(e.getClassName())
339                                     && "doSelect".equals(e.getMethodName()));
340                 }
341                 action.run();
342             } catch (Exception e) {
343                 e.printStackTrace();
344             }
345         });
346     }
347 
348     /**
349      * Returns the current time in milliseconds.
350      */
351     private static long millisTime() {
352         long now = System.nanoTime();
353         return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
354     }
355 
356     /**
357      * Check the duration of a task
358      * @param start start time, in milliseconds
359      * @param min minimum expected duration, in milliseconds
360      * @param max maximum expected duration, in milliseconds
361      * @return the duration (now - start), in milliseconds
362      */
363     private static void expectDuration(long start, long min, long max) {
364         long duration = millisTime() - start;
365         assertTrue(duration >= min,
366                 "Duration " + duration + "ms, expected >= " + min + "ms");
367         assertTrue(duration <= max,
368                 "Duration " + duration + "ms, expected <= " + max + "ms");
369     }
370 }