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 }