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  */
 48 
 49 /*
 50  * @test id=no-vmcontinuations
 51  * @requires vm.continuations
 52  * @library /test/lib
 53  * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
 54  *     --enable-native-access=ALL-UNNAMED SelectorOps
 55  */
 56 
 57 import java.io.IOException;
 58 import java.nio.ByteBuffer;
 59 import java.nio.channels.Pipe;
 60 import java.nio.channels.SelectionKey;
 61 import java.nio.channels.Selector;
 62 import java.nio.charset.StandardCharsets;
 63 import java.util.Arrays;
 64 import java.util.concurrent.TimeUnit;
 65 
 66 import jdk.test.lib.thread.VThreadRunner;
 67 import jdk.test.lib.thread.VThreadPinner;
 68 import org.junit.jupiter.api.Test;
 69 import org.junit.jupiter.api.BeforeAll;
 70 import org.junit.jupiter.params.ParameterizedTest;
 71 import org.junit.jupiter.params.provider.ValueSource;
 72 import static org.junit.jupiter.api.Assertions.*;
 73 
 74 class SelectorOps {
 75     private static String selectorClassName;  // platform specific class name
 76 
 77     @BeforeAll
 78     static void setup() throws Exception {
 79         try (Selector sel = Selector.open()) {
 80             selectorClassName = sel.getClass().getName();
 81         }
 82     }
 83 
 84     /**
 85      * Test that select wakes up when a channel is ready for I/O.
 86      */
 87     @ParameterizedTest
 88     @ValueSource(booleans = { true, false })
 89     public void testSelect(boolean timed) throws Exception {
 90         VThreadRunner.run(() -> {
 91             Pipe p = Pipe.open();
 92             try (Selector sel = Selector.open()) {
 93                 Pipe.SinkChannel sink = p.sink();
 94                 Pipe.SourceChannel source = p.source();
 95                 source.configureBlocking(false);
 96                 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
 97 
 98                 // write to sink to ensure source is readable
 99                 ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
100                 onSelect(() -> sink.write(buf));
101 
102                 int n = timed ? sel.select(60_000) : sel.select();
103                 assertEquals(1, n);
104                 assertTrue(sel.isOpen());
105                 assertTrue(key.isReadable());
106             } finally {
107                 closePipe(p);
108             }
109         });
110     }
111 
112     /**
113      * Test that select wakes up when a channel is ready for I/O and thread is pinned.
114      */
115     @ParameterizedTest
116     @ValueSource(booleans = { true, false })
117     public void testSelectWhenPinned(boolean timed) throws Exception {
118         VThreadPinner.runPinned(() -> { testSelect(timed); });
119     }
120 
121     /**
122      * Test that select wakes up when timeout is reached.
123      */
124     @Test
125     public void testSelectTimeout() throws Exception {
126         VThreadRunner.run(() -> {
127             Pipe p = Pipe.open();
128             try (Selector sel = Selector.open()) {
129                 Pipe.SourceChannel source = p.source();
130                 source.configureBlocking(false);
131                 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
132 
133                 long start = millisTime();
134                 int n = sel.select(1000);
135                 expectDuration(start, /*min*/500, /*max*/20_000);
136 
137                 assertEquals(0, n);
138                 assertTrue(sel.isOpen());
139                 assertFalse(key.isReadable());
140             } finally {
141                 closePipe(p);
142             }
143         });
144     }
145 
146     /**
147      * Test that select wakes up when timeout is reached and thread is pinned.
148      */
149     @Test
150     public void testSelectTimeoutWhenPinned() throws Exception {
151         VThreadPinner.runPinned(() -> { testSelectTimeout(); });
152     }
153 
154     /**
155      * Test that selectNow is non-blocking.
156      */
157     @Test
158     public void testSelectNow() throws Exception {
159         VThreadRunner.run(() -> {
160             Pipe p = Pipe.open();
161             try (Selector sel = Selector.open()) {
162                 Pipe.SinkChannel sink = p.sink();
163                 Pipe.SourceChannel source = p.source();
164                 source.configureBlocking(false);
165                 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
166 
167                 // selectNow should return immediately
168                 for (int i = 0; i < 3; i++) {
169                     long start = millisTime();
170                     int n = sel.selectNow();
171                     expectDuration(start, -1, /*max*/20_000);
172                     assertEquals(0, n);
173                 }
174 
175                 // write to sink to ensure source is readable
176                 ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
177                 sink.write(buf);
178 
179                 // call selectNow until key added to selected key set
180                 int n = 0;
181                 while (n == 0) {
182                     Thread.sleep(10);
183                     long start = millisTime();
184                     n = sel.selectNow();
185                     expectDuration(start, -1, /*max*/20_000);
186                 }
187                 assertEquals(1, n);
188                 assertTrue(sel.isOpen());
189                 assertTrue(key.isReadable());
190             } finally {
191                 closePipe(p);
192             }
193         });
194     }
195 
196     /**
197      * Test calling wakeup before select.
198      */
199     @Test
200     public void testWakeupBeforeSelect() throws Exception {
201         VThreadRunner.run(() -> {
202             try (Selector sel = Selector.open()) {
203                 sel.wakeup();
204                 int n = sel.select();
205                 assertEquals(0, n);
206                 assertTrue(sel.isOpen());
207             }
208         });
209     }
210 
211     /**
212      * Test calling wakeup before select and thread is pinned.
213      */
214     @Test
215     public void testWakeupBeforeSelectWhenPinned() throws Exception {
216         VThreadPinner.runPinned(() -> { testWakeupBeforeSelect(); });
217     }
218 
219     /**
220      * Test calling wakeup while a thread is blocked in select.
221      */
222     @Test
223     public void testWakeupDuringSelect() throws Exception {
224         VThreadRunner.run(() -> {
225             try (Selector sel = Selector.open()) {
226                 onSelect(sel::wakeup);
227                 int n = sel.select();
228                 assertEquals(0, n);
229                 assertTrue(sel.isOpen());
230             }
231         });
232     }
233 
234     /**
235      * Test calling wakeup while a thread is blocked in select and the thread is pinned.
236      */
237     @Test
238     public void testWakeupDuringSelectWhenPinned() throws Exception {
239         VThreadPinner.runPinned(() -> { testWakeupDuringSelect(); });
240     }
241 
242     /**
243      * Test closing selector while a thread is blocked in select.
244      */
245     @Test
246     public void testCloseDuringSelect() throws Exception {
247         VThreadRunner.run(() -> {
248             try (Selector sel = Selector.open()) {
249                 onSelect(sel::close);
250                 int n = sel.select();
251                 assertEquals(0, n);
252                 assertFalse(sel.isOpen());
253             }
254         });
255     }
256 
257     /**
258      * Test closing selector while a thread is blocked in select and the thread is pinned.
259      */
260     @Test
261     public void testCloseDuringSelectWhenPinned() throws Exception {
262         VThreadPinner.runPinned(() -> { testCloseDuringSelect(); });
263     }
264 
265     /**
266      * Test calling select with interrupt status set.
267      */
268     @Test
269     public void testInterruptBeforeSelect() throws Exception {
270         VThreadRunner.run(() -> {
271             try (Selector sel = Selector.open()) {
272                 Thread me = Thread.currentThread();
273                 me.interrupt();
274                 int n = sel.select();
275                 assertEquals(0, n);
276                 assertTrue(me.isInterrupted());
277                 assertTrue(sel.isOpen());
278             }
279         });
280     }
281 
282     /**
283      * Test calling select with interrupt status set and thread is pinned.
284      */
285     @Test
286     public void testInterruptBeforeSelectWhenPinned() throws Exception {
287         VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); });
288     }
289 
290     /**
291      * Test interrupting a thread blocked in select.
292      */
293     @Test
294     public void testInterruptDuringSelect() throws Exception {
295         VThreadRunner.run(() -> {
296             try (Selector sel = Selector.open()) {
297                 Thread me = Thread.currentThread();
298                 onSelect(me::interrupt);
299                 int n = sel.select();
300                 assertEquals(0, n);
301                 assertTrue(me.isInterrupted());
302                 assertTrue(sel.isOpen());
303             }
304         });
305     }
306 
307     /**
308      * Test interrupting a thread blocked in select and the thread is pinned.
309      */
310     @Test
311     public void testInterruptDuringSelectWhenPinned() throws Exception {
312         VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); });
313     }
314 
315     /**
316      * Close a pipe's sink and source channels.
317      */
318     private void closePipe(Pipe p) {
319         try { p.sink().close(); } catch (IOException ignore) { }
320         try { p.source().close(); } catch (IOException ignore) { }
321     }
322 
323     /**
324      * Runs the given action when the current thread is sampled in a selection operation.
325      */
326     private void onSelect(VThreadRunner.ThrowingRunnable<Exception> action) {
327         Thread target = Thread.currentThread();
328         Thread.ofPlatform().daemon().start(() -> {
329             try {
330                 boolean found = false;
331                 while (!found) {
332                     Thread.sleep(20);
333                     StackTraceElement[] stack = target.getStackTrace();
334                     found = Arrays.stream(stack)
335                             .anyMatch(e -> selectorClassName.equals(e.getClassName())
336                                     && "doSelect".equals(e.getMethodName()));
337                 }
338                 action.run();
339             } catch (Exception e) {
340                 e.printStackTrace();
341             }
342         });
343     }
344 
345     /**
346      * Returns the current time in milliseconds.
347      */
348     private static long millisTime() {
349         long now = System.nanoTime();
350         return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
351     }
352 
353     /**
354      * Check the duration of a task
355      * @param start start time, in milliseconds
356      * @param min minimum expected duration, in milliseconds
357      * @param max maximum expected duration, in milliseconds
358      * @return the duration (now - start), in milliseconds
359      */
360     private static void expectDuration(long start, long min, long max) {
361         long duration = millisTime() - start;
362         assertTrue(duration >= min,
363                 "Duration " + duration + "ms, expected >= " + min + "ms");
364         assertTrue(duration <= max,
365                 "Duration " + duration + "ms, expected <= " + max + "ms");
366     }
367 }