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 }