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 */
38
39 /*
40 * @test id=no-vmcontinuations
41 * @requires vm.continuations
42 * @library /test/lib
43 * @run junit/othervm/native -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 }