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 }