|
1 | 1 | /*
|
2 |
| - * Copyright (c) 2019, Oracle and/or its affiliates. All rights reserved. |
| 2 | + * Copyright (c) 2019, 2024, Oracle and/or its affiliates. All rights reserved. |
3 | 3 | * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
|
4 | 4 | *
|
5 | 5 | * This code is free software; you can redistribute it and/or modify it
|
|
25 | 25 | * @test
|
26 | 26 | * @bug 8236246
|
27 | 27 | * @modules java.base/sun.nio.ch
|
28 |
| - * @run testng InterruptibleOrNot |
| 28 | + * @run junit InterruptibleOrNot |
29 | 29 | * @summary Test SelectorProviderImpl.openDatagramChannel(boolean) to create
|
30 | 30 | * DatagramChannel objects that optionally support interrupt
|
31 | 31 | */
|
|
40 | 40 | import java.nio.channels.ClosedByInterruptException;
|
41 | 41 | import java.nio.channels.DatagramChannel;
|
42 | 42 | import java.time.Duration;
|
43 |
| -import java.util.concurrent.Executors; |
44 |
| -import java.util.concurrent.Future; |
45 |
| -import java.util.concurrent.ScheduledExecutorService; |
46 |
| -import java.util.concurrent.TimeUnit; |
| 43 | +import java.util.Arrays; |
47 | 44 | import sun.nio.ch.DefaultSelectorProvider;
|
48 | 45 |
|
49 |
| -import org.testng.annotations.Test; |
50 |
| -import static org.testng.Assert.*; |
| 46 | +import org.junit.jupiter.api.Test; |
| 47 | +import org.junit.jupiter.api.BeforeAll; |
| 48 | +import org.junit.jupiter.api.function.Executable; |
| 49 | +import static org.junit.jupiter.api.Assertions.*; |
51 | 50 |
|
52 |
| -@Test |
53 | 51 | public class InterruptibleOrNot {
|
| 52 | + // DatagramChannel implementation class |
| 53 | + private static String dcImplClassName; |
54 | 54 |
|
55 |
| - public void testInterruptBeforeInterruptibleReceive() throws Exception { |
56 |
| - testInterruptBeforeReceive(true); |
57 |
| - } |
58 |
| - |
59 |
| - public void testInterruptDuringInterruptibleReceive() throws Exception { |
60 |
| - testInterruptDuringReceive(true); |
61 |
| - } |
62 |
| - |
63 |
| - public void testInterruptBeforeUninterruptibleReceive() throws Exception { |
64 |
| - testInterruptBeforeReceive(false); |
65 |
| - } |
66 |
| - |
67 |
| - public void testInterruptDuringUninterruptibleReceive() throws Exception { |
68 |
| - testInterruptDuringReceive(false); |
69 |
| - } |
70 |
| - |
71 |
| - public void testInterruptBeforeInterruptibleSend() throws Exception { |
72 |
| - testInterruptBeforeSend(true); |
| 55 | + @BeforeAll |
| 56 | + static void setup() throws Exception { |
| 57 | + try (DatagramChannel dc = boundDatagramChannel(true)) { |
| 58 | + dcImplClassName = dc.getClass().getName(); |
| 59 | + } |
73 | 60 | }
|
74 | 61 |
|
75 |
| - public void testInterruptBeforeUninterruptibleSend() throws Exception { |
76 |
| - testInterruptBeforeSend(false); |
| 62 | + /** |
| 63 | + * Call DatagramChannel.receive with the interrupt status set, the DatagramChannel |
| 64 | + * is interruptible. |
| 65 | + */ |
| 66 | + @Test |
| 67 | + public void testInterruptBeforeInterruptibleReceive() throws Exception { |
| 68 | + try (DatagramChannel dc = boundDatagramChannel(true)) { |
| 69 | + ByteBuffer buf = ByteBuffer.allocate(100); |
| 70 | + Thread.currentThread().interrupt(); |
| 71 | + assertThrows(ClosedByInterruptException.class, () -> dc.receive(buf)); |
| 72 | + assertFalse(dc.isOpen()); |
| 73 | + } finally { |
| 74 | + Thread.interrupted(); // clear interrupt status |
| 75 | + } |
77 | 76 | }
|
78 | 77 |
|
79 | 78 | /**
|
80 |
| - * Test invoking DatagramChannel receive with interrupt status set |
| 79 | + * Test interrupting a thread blocked in DatagramChannel.receive, the DatagramChannel |
| 80 | + * is interruptible. |
81 | 81 | */
|
82 |
| - static void testInterruptBeforeReceive(boolean interruptible) |
83 |
| - throws Exception |
84 |
| - { |
85 |
| - try (DatagramChannel dc = openDatagramChannel(interruptible)) { |
86 |
| - dc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); |
87 |
| - Future<?> timeout = scheduleClose(dc, Duration.ofSeconds(2)); |
88 |
| - try { |
89 |
| - ByteBuffer buf = ByteBuffer.allocate(100); |
90 |
| - Thread.currentThread().interrupt(); |
91 |
| - assertThrows(expectedException(interruptible), () -> dc.receive(buf)); |
92 |
| - } finally { |
93 |
| - timeout.cancel(false); |
94 |
| - } |
| 82 | + @Test |
| 83 | + public void testInterruptDuringInterruptibleReceive() throws Exception { |
| 84 | + try (DatagramChannel dc = boundDatagramChannel(true)) { |
| 85 | + ByteBuffer buf = ByteBuffer.allocate(100); |
| 86 | + Thread thread = Thread.currentThread(); |
| 87 | + onReceive(thread::interrupt); |
| 88 | + assertThrows(ClosedByInterruptException.class, () -> dc.receive(buf)); |
| 89 | + assertFalse(dc.isOpen()); |
95 | 90 | } finally {
|
96 |
| - Thread.interrupted(); // clear interrupt |
| 91 | + Thread.interrupted(); // clear interrupt status |
97 | 92 | }
|
98 | 93 | }
|
99 | 94 |
|
100 | 95 | /**
|
101 |
| - * Test Thread.interrupt when target thread is blocked in DatagramChannel receive |
| 96 | + * Call DatagramChannel.receive with the interrupt status set, the DatagramChannel |
| 97 | + * is not interruptible. |
102 | 98 | */
|
103 |
| - static void testInterruptDuringReceive(boolean interruptible) |
104 |
| - throws Exception |
105 |
| - { |
106 |
| - try (DatagramChannel dc = openDatagramChannel(interruptible)) { |
107 |
| - dc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); |
108 |
| - Future<?> timerTask = scheduleClose(dc, Duration.ofSeconds(5)); |
109 |
| - Future<?> interruptTask = scheduleInterrupt(Thread.currentThread(), Duration.ofSeconds(1)); |
110 |
| - try { |
111 |
| - ByteBuffer buf = ByteBuffer.allocate(100); |
112 |
| - assertThrows(expectedException(interruptible), () -> dc.receive(buf)); |
113 |
| - } finally { |
114 |
| - timerTask.cancel(false); |
115 |
| - interruptTask.cancel(false); |
116 |
| - } |
| 99 | + @Test |
| 100 | + public void testInterruptBeforeUninterruptibleReceive() throws Exception { |
| 101 | + try (DatagramChannel dc = boundDatagramChannel(false)) { |
| 102 | + ByteBuffer buf = ByteBuffer.allocate(100); |
| 103 | + onReceive(() -> { |
| 104 | + // close the channel after a delay to ensure receive wakes up |
| 105 | + Thread.sleep(1000); |
| 106 | + dc.close(); |
| 107 | + }); |
| 108 | + Thread.currentThread().interrupt(); |
| 109 | + assertThrows(AsynchronousCloseException.class, () -> dc.receive(buf)); |
| 110 | + assertFalse(dc.isOpen()); |
117 | 111 | } finally {
|
118 |
| - Thread.interrupted(); // clear interrupt |
| 112 | + Thread.interrupted(); // clear interrupt status |
119 | 113 | }
|
120 | 114 | }
|
121 | 115 |
|
122 | 116 | /**
|
123 |
| - * Test invoking DatagramChannel send with interrupt status set |
| 117 | + * Test interrupting a thread blocked in DatagramChannel.receive, the DatagramChannel |
| 118 | + * is not interruptible. |
124 | 119 | */
|
125 |
| - static void testInterruptBeforeSend(boolean interruptible) |
126 |
| - throws Exception |
127 |
| - { |
128 |
| - try (DatagramChannel dc = openDatagramChannel(interruptible)) { |
129 |
| - dc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); |
130 |
| - Future<?> timeout = scheduleClose(dc, Duration.ofSeconds(2)); |
131 |
| - try { |
132 |
| - ByteBuffer buf = ByteBuffer.allocate(100); |
133 |
| - SocketAddress target = dc.getLocalAddress(); |
134 |
| - Thread.currentThread().interrupt(); |
135 |
| - if (interruptible) { |
136 |
| - assertThrows(ClosedByInterruptException.class, () -> dc.send(buf, target)); |
137 |
| - } else { |
138 |
| - int n = dc.send(buf, target); |
139 |
| - assertTrue(n == 100); |
140 |
| - } |
141 |
| - } finally { |
142 |
| - timeout.cancel(false); |
143 |
| - } |
| 120 | + @Test |
| 121 | + public void testInterruptDuringUninterruptibleReceive() throws Exception { |
| 122 | + try (DatagramChannel dc = boundDatagramChannel(true)) { |
| 123 | + ByteBuffer buf = ByteBuffer.allocate(100); |
| 124 | + |
| 125 | + Thread thread = Thread.currentThread(); |
| 126 | + onReceive(() -> { |
| 127 | + // interrupt should not cause the receive to wakeup |
| 128 | + thread.interrupt(); |
| 129 | + |
| 130 | + // close the channel after a delay to ensure receive wakes up |
| 131 | + Thread.sleep(1000); |
| 132 | + dc.close(); |
| 133 | + }); |
| 134 | + assertThrows(AsynchronousCloseException.class, () -> dc.receive(buf)); |
| 135 | + assertFalse(dc.isOpen()); |
144 | 136 | } finally {
|
145 |
| - Thread.interrupted(); // clear interrupt |
| 137 | + Thread.interrupted(); // clear interrupt status |
146 | 138 | }
|
147 | 139 | }
|
148 | 140 |
|
149 | 141 | /**
|
150 |
| - * Creates a DatagramChannel that is interruptible or not. |
| 142 | + * Call DatagramChannel.send with the interrupt status set, the DatagramChannel |
| 143 | + * is interruptible. |
151 | 144 | */
|
152 |
| - static DatagramChannel openDatagramChannel(boolean interruptible) throws IOException { |
153 |
| - if (interruptible) { |
154 |
| - return DatagramChannel.open(); |
155 |
| - } else { |
156 |
| - return DefaultSelectorProvider.get().openUninterruptibleDatagramChannel(); |
| 145 | + @Test |
| 146 | + public void testInterruptBeforeInterruptibleSend() throws Exception { |
| 147 | + try (DatagramChannel dc = boundDatagramChannel(true)) { |
| 148 | + ByteBuffer buf = ByteBuffer.allocate(100); |
| 149 | + SocketAddress target = dc.getLocalAddress(); |
| 150 | + Thread.currentThread().interrupt(); |
| 151 | + assertThrows(ClosedByInterruptException.class, () -> dc.send(buf, target)); |
| 152 | + assertFalse(dc.isOpen()); |
| 153 | + } finally { |
| 154 | + Thread.interrupted(); // clear interrupt |
157 | 155 | }
|
158 | 156 | }
|
159 | 157 |
|
160 | 158 | /**
|
161 |
| - * Expect ClosedByInterruptException if interruptible. |
| 159 | + * Call DatagramChannel.send with the interrupt status set, the DatagramChannel |
| 160 | + * is not interruptible. |
162 | 161 | */
|
163 |
| - static Class<? extends Exception> expectedException(boolean expectInterrupt) { |
164 |
| - if (expectInterrupt) { |
165 |
| - return ClosedByInterruptException.class; |
166 |
| - } else { |
167 |
| - return AsynchronousCloseException.class; |
| 162 | + @Test |
| 163 | + public void testInterruptBeforeUninterruptibleSend() throws Exception { |
| 164 | + try (DatagramChannel dc = boundDatagramChannel(false)) { |
| 165 | + ByteBuffer buf = ByteBuffer.allocate(100); |
| 166 | + SocketAddress target = dc.getLocalAddress(); |
| 167 | + Thread.currentThread().interrupt(); |
| 168 | + int n = dc.send(buf, target); |
| 169 | + assertEquals(100, n); |
| 170 | + assertTrue(dc.isOpen()); |
| 171 | + } finally { |
| 172 | + Thread.interrupted(); // clear interrupt status |
168 | 173 | }
|
169 | 174 | }
|
170 | 175 |
|
171 | 176 | /**
|
172 |
| - * Schedule the given object to be closed. |
| 177 | + * Creates a DatagramChannel that is interruptible or not, and bound to the loopback |
| 178 | + * address. |
173 | 179 | */
|
174 |
| - static Future<?> scheduleClose(Closeable c, Duration timeout) { |
175 |
| - long nanos = TimeUnit.NANOSECONDS.convert(timeout); |
176 |
| - return STPE.schedule(() -> { |
177 |
| - c.close(); |
178 |
| - return null; |
179 |
| - }, nanos, TimeUnit.NANOSECONDS); |
| 180 | + static DatagramChannel boundDatagramChannel(boolean interruptible) throws IOException { |
| 181 | + DatagramChannel dc; |
| 182 | + if (interruptible) { |
| 183 | + dc = DatagramChannel.open(); |
| 184 | + } else { |
| 185 | + dc = DefaultSelectorProvider.get().openUninterruptibleDatagramChannel(); |
| 186 | + } |
| 187 | + try { |
| 188 | + dc.bind(new InetSocketAddress(InetAddress.getLoopbackAddress(), 0)); |
| 189 | + } catch (IOException ioe) { |
| 190 | + dc.close(); |
| 191 | + throw ioe; |
| 192 | + } |
| 193 | + return dc; |
180 | 194 | }
|
181 | 195 |
|
182 | 196 | /**
|
183 |
| - * Schedule the given thread to be interrupted. |
| 197 | + * Runs the given action when the current thread is sampled in DatagramChannel.receive. |
184 | 198 | */
|
185 |
| - static Future<?> scheduleInterrupt(Thread t, Duration timeout) { |
186 |
| - long nanos = TimeUnit.NANOSECONDS.convert(timeout); |
187 |
| - return STPE.schedule(t::interrupt, nanos, TimeUnit.NANOSECONDS); |
| 199 | + static void onReceive(Executable action) { |
| 200 | + Thread target = Thread.currentThread(); |
| 201 | + Thread.ofPlatform().daemon().start(() -> { |
| 202 | + try { |
| 203 | + boolean found = false; |
| 204 | + while (!found) { |
| 205 | + Thread.sleep(20); |
| 206 | + StackTraceElement[] stack = target.getStackTrace(); |
| 207 | + found = Arrays.stream(stack) |
| 208 | + .anyMatch(e -> dcImplClassName.equals(e.getClassName()) |
| 209 | + && "receive".equals(e.getMethodName())); |
| 210 | + } |
| 211 | + action.execute(); |
| 212 | + } catch (Throwable ex) { |
| 213 | + ex.printStackTrace(); |
| 214 | + } |
| 215 | + }); |
188 | 216 | }
|
189 |
| - |
190 |
| - static final ScheduledExecutorService STPE = Executors.newScheduledThreadPool(0); |
191 | 217 | }
|
0 commit comments