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 }