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