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 }