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=no-vmcontinuations
42 * @requires vm.continuations
43 * @library /test/lib
44 * @run junit/othervm/native -XX:+UnlockExperimentalVMOptions -XX:-VMContinuations
45 * --enable-native-access=ALL-UNNAMED SelectorOps
46 */
47
48 import java.io.IOException;
49 import java.nio.ByteBuffer;
50 import java.nio.channels.Pipe;
51 import java.nio.channels.SelectionKey;
52 import java.nio.channels.Selector;
53 import java.nio.charset.StandardCharsets;
54 import java.util.Arrays;
55 import java.util.concurrent.TimeUnit;
56
57 import jdk.test.lib.thread.VThreadRunner;
58 import jdk.test.lib.thread.VThreadPinner;
59 import org.junit.jupiter.api.Test;
60 import org.junit.jupiter.api.BeforeAll;
61 import org.junit.jupiter.params.ParameterizedTest;
62 import org.junit.jupiter.params.provider.ValueSource;
63 import static org.junit.jupiter.api.Assertions.*;
64
65 class SelectorOps {
66 private static String selectorClassName; // platform specific class name
67
68 @BeforeAll
69 static void setup() throws Exception {
70 try (Selector sel = Selector.open()) {
71 selectorClassName = sel.getClass().getName();
72 }
73 }
74
75 /**
76 * Test that select wakes up when a channel is ready for I/O.
77 */
78 @ParameterizedTest
79 @ValueSource(booleans = { true, false })
80 public void testSelect(boolean timed) throws Exception {
81 VThreadRunner.run(() -> {
82 Pipe p = Pipe.open();
83 try (Selector sel = Selector.open()) {
84 Pipe.SinkChannel sink = p.sink();
85 Pipe.SourceChannel source = p.source();
86 source.configureBlocking(false);
87 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
88
89 // write to sink to ensure source is readable
90 ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
91 onSelect(() -> sink.write(buf));
92
93 int n = timed ? sel.select(60_000) : sel.select();
94 assertEquals(1, n);
95 assertTrue(sel.isOpen());
96 assertTrue(key.isReadable());
97 } finally {
98 closePipe(p);
99 }
100 });
101 }
102
103 /**
104 * Test that select wakes up when a channel is ready for I/O and thread is pinned.
105 */
106 @ParameterizedTest
107 @ValueSource(booleans = { true, false })
108 public void testSelectWhenPinned(boolean timed) throws Exception {
109 VThreadPinner.runPinned(() -> { testSelect(timed); });
110 }
111
112 /**
113 * Test that select wakes up when timeout is reached.
114 */
115 @Test
116 public void testSelectTimeout() throws Exception {
117 VThreadRunner.run(() -> {
118 Pipe p = Pipe.open();
119 try (Selector sel = Selector.open()) {
120 Pipe.SourceChannel source = p.source();
121 source.configureBlocking(false);
122 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
123
124 long start = millisTime();
125 int n = sel.select(1000);
126 expectDuration(start, /*min*/500, /*max*/20_000);
127
128 assertEquals(0, n);
129 assertTrue(sel.isOpen());
130 assertFalse(key.isReadable());
131 } finally {
132 closePipe(p);
133 }
134 });
135 }
136
137 /**
138 * Test that select wakes up when timeout is reached and thread is pinned.
139 */
140 @Test
141 public void testSelectTimeoutWhenPinned() throws Exception {
142 VThreadPinner.runPinned(() -> { testSelectTimeout(); });
143 }
144
145 /**
146 * Test that selectNow is non-blocking.
147 */
148 @Test
149 public void testSelectNow() throws Exception {
150 VThreadRunner.run(() -> {
151 Pipe p = Pipe.open();
152 try (Selector sel = Selector.open()) {
153 Pipe.SinkChannel sink = p.sink();
154 Pipe.SourceChannel source = p.source();
155 source.configureBlocking(false);
156 SelectionKey key = source.register(sel, SelectionKey.OP_READ);
157
158 // selectNow should return immediately
159 for (int i = 0; i < 3; i++) {
160 long start = millisTime();
161 int n = sel.selectNow();
162 expectDuration(start, -1, /*max*/20_000);
163 assertEquals(0, n);
164 }
165
166 // write to sink to ensure source is readable
167 ByteBuffer buf = ByteBuffer.wrap("hello".getBytes(StandardCharsets.UTF_8));
168 sink.write(buf);
169
170 // call selectNow until key added to selected key set
171 int n = 0;
172 while (n == 0) {
173 Thread.sleep(10);
174 long start = millisTime();
175 n = sel.selectNow();
176 expectDuration(start, -1, /*max*/20_000);
177 }
178 assertEquals(1, n);
179 assertTrue(sel.isOpen());
180 assertTrue(key.isReadable());
181 } finally {
182 closePipe(p);
183 }
184 });
185 }
186
187 /**
188 * Test calling wakeup before select.
189 */
190 @Test
191 public void testWakeupBeforeSelect() throws Exception {
192 VThreadRunner.run(() -> {
193 try (Selector sel = Selector.open()) {
194 sel.wakeup();
195 int n = sel.select();
196 assertEquals(0, n);
197 assertTrue(sel.isOpen());
198 }
199 });
200 }
201
202 /**
203 * Test calling wakeup before select and thread is pinned.
204 */
205 @Test
206 public void testWakeupBeforeSelectWhenPinned() throws Exception {
207 VThreadPinner.runPinned(() -> { testWakeupBeforeSelect(); });
208 }
209
210 /**
211 * Test calling wakeup while a thread is blocked in select.
212 */
213 @Test
214 public void testWakeupDuringSelect() throws Exception {
215 VThreadRunner.run(() -> {
216 try (Selector sel = Selector.open()) {
217 onSelect(sel::wakeup);
218 int n = sel.select();
219 assertEquals(0, n);
220 assertTrue(sel.isOpen());
221 }
222 });
223 }
224
225 /**
226 * Test calling wakeup while a thread is blocked in select and the thread is pinned.
227 */
228 @Test
229 public void testWakeupDuringSelectWhenPinned() throws Exception {
230 VThreadPinner.runPinned(() -> { testWakeupDuringSelect(); });
231 }
232
233 /**
234 * Test closing selector while a thread is blocked in select.
235 */
236 @Test
237 public void testCloseDuringSelect() throws Exception {
238 VThreadRunner.run(() -> {
239 try (Selector sel = Selector.open()) {
240 onSelect(sel::close);
241 int n = sel.select();
242 assertEquals(0, n);
243 assertFalse(sel.isOpen());
244 }
245 });
246 }
247
248 /**
249 * Test closing selector while a thread is blocked in select and the thread is pinned.
250 */
251 @Test
252 public void testCloseDuringSelectWhenPinned() throws Exception {
253 VThreadPinner.runPinned(() -> { testCloseDuringSelect(); });
254 }
255
256 /**
257 * Test calling select with interrupted status set.
258 */
259 @Test
260 public void testInterruptBeforeSelect() throws Exception {
261 VThreadRunner.run(() -> {
262 try (Selector sel = Selector.open()) {
263 Thread me = Thread.currentThread();
264 me.interrupt();
265 int n = sel.select();
266 assertEquals(0, n);
267 assertTrue(me.isInterrupted());
268 assertTrue(sel.isOpen());
269 }
270 });
271 }
272
273 /**
274 * Test calling select with interrupted status set and thread is pinned.
275 */
276 @Test
277 public void testInterruptBeforeSelectWhenPinned() throws Exception {
278 VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); });
279 }
280
281 /**
282 * Test interrupting a thread blocked in select.
283 */
284 @Test
285 public void testInterruptDuringSelect() throws Exception {
286 VThreadRunner.run(() -> {
287 try (Selector sel = Selector.open()) {
288 Thread me = Thread.currentThread();
289 onSelect(me::interrupt);
290 int n = sel.select();
291 assertEquals(0, n);
292 assertTrue(me.isInterrupted());
293 assertTrue(sel.isOpen());
294 }
295 });
296 }
297
298 /**
299 * Test interrupting a thread blocked in select and the thread is pinned.
300 */
301 @Test
302 public void testInterruptDuringSelectWhenPinned() throws Exception {
303 VThreadPinner.runPinned(() -> { testInterruptDuringSelect(); });
304 }
305
306 /**
307 * Close a pipe's sink and source channels.
308 */
309 private void closePipe(Pipe p) {
310 try { p.sink().close(); } catch (IOException ignore) { }
311 try { p.source().close(); } catch (IOException ignore) { }
312 }
313
314 /**
315 * Runs the given action when the current thread is sampled in a selection operation.
316 */
317 private void onSelect(VThreadRunner.ThrowingRunnable<Exception> action) {
318 Thread target = Thread.currentThread();
319 Thread.ofPlatform().daemon().start(() -> {
320 try {
321 boolean found = false;
322 while (!found) {
323 Thread.sleep(20);
324 StackTraceElement[] stack = target.getStackTrace();
325 found = Arrays.stream(stack)
326 .anyMatch(e -> selectorClassName.equals(e.getClassName())
327 && "doSelect".equals(e.getMethodName()));
328 }
329 action.run();
330 } catch (Exception e) {
331 e.printStackTrace();
332 }
333 });
334 }
335
336 /**
337 * Returns the current time in milliseconds.
338 */
339 private static long millisTime() {
340 long now = System.nanoTime();
341 return TimeUnit.MILLISECONDS.convert(now, TimeUnit.NANOSECONDS);
342 }
343
344 /**
345 * Check the duration of a task
346 * @param start start time, in milliseconds
347 * @param min minimum expected duration, in milliseconds
348 * @param max maximum expected duration, in milliseconds
349 * @return the duration (now - start), in milliseconds
350 */
351 private static void expectDuration(long start, long min, long max) {
352 long duration = millisTime() - start;
353 assertTrue(duration >= min,
354 "Duration " + duration + "ms, expected >= " + min + "ms");
355 assertTrue(duration <= max,
356 "Duration " + duration + "ms, expected <= " + max + "ms");
357 }
358 }