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