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