1 /* 2 * Copyright (c) 2019, 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 26 * @summary Test virtual threads using Object.wait/notifyAll 27 * @modules java.base/java.lang:+open 28 * @library /test/lib 29 * @run junit MonitorWaitNotify 30 */ 31 32 import java.util.concurrent.CountDownLatch; 33 import java.util.concurrent.Semaphore; 34 import java.util.concurrent.atomic.AtomicBoolean; 35 import java.util.concurrent.locks.LockSupport; 36 37 import jdk.test.lib.thread.VThreadRunner; 38 import org.junit.jupiter.api.Test; 39 import static org.junit.jupiter.api.Assertions.*; 40 41 class MonitorWaitNotify { 42 43 /** 44 * Test virtual thread waits, notified by platform thread. 45 */ 46 @Test 47 void testWaitNotify1() throws Exception { 48 var lock = new Object(); 49 var ready = new Semaphore(0); 50 var thread = Thread.ofVirtual().start(() -> { 51 synchronized (lock) { 52 ready.release(); 53 try { 54 lock.wait(); 55 } catch (InterruptedException e) { } 56 } 57 }); 58 // thread invokes notify 59 ready.acquire(); 60 synchronized (lock) { 61 lock.notifyAll(); 62 } 63 thread.join(); 64 } 65 66 /** 67 * Test platform thread waits, notified by virtual thread. 68 */ 69 @Test 70 void testWaitNotify2() throws Exception { 71 var lock = new Object(); 72 var ready = new Semaphore(0); 73 var thread = Thread.ofVirtual().start(() -> { 74 ready.acquireUninterruptibly(); 75 synchronized (lock) { 76 lock.notifyAll(); 77 } 78 }); 79 synchronized (lock) { 80 ready.release(); 81 lock.wait(); 82 } 83 thread.join(); 84 } 85 86 /** 87 * Test virtual thread waits, notified by another virtual thread. 88 */ 89 @Test 90 void testWaitNotify3() throws Exception { 91 // need at least two carrier threads due to pinning 92 int previousParallelism = VThreadRunner.ensureParallelism(2); 93 try { 94 var lock = new Object(); 95 var ready = new Semaphore(0); 96 var thread1 = Thread.ofVirtual().start(() -> { 97 synchronized (lock) { 98 ready.release(); 99 try { 100 lock.wait(); 101 } catch (InterruptedException e) { } 102 } 103 }); 104 var thread2 = Thread.ofVirtual().start(() -> { 105 ready.acquireUninterruptibly(); 106 synchronized (lock) { 107 lock.notifyAll(); 108 } 109 }); 110 thread1.join(); 111 thread2.join(); 112 } finally { 113 // restore 114 VThreadRunner.setParallelism(previousParallelism); 115 } 116 } 117 118 /** 119 * Test interrupt status set when calling Object.wait. 120 */ 121 @Test 122 void testWaitNotify4() throws Exception { 123 VThreadRunner.run(() -> { 124 Thread t = Thread.currentThread(); 125 t.interrupt(); 126 Object lock = new Object(); 127 synchronized (lock) { 128 try { 129 lock.wait(); 130 fail(); 131 } catch (InterruptedException e) { 132 // interrupt status should be cleared 133 assertFalse(t.isInterrupted()); 134 } 135 } 136 }); 137 } 138 139 /** 140 * Test interrupt when blocked in Object.wait. 141 */ 142 @Test 143 void testWaitNotify5() throws Exception { 144 VThreadRunner.run(() -> { 145 Thread t = Thread.currentThread(); 146 scheduleInterrupt(t, 1000); 147 Object lock = new Object(); 148 synchronized (lock) { 149 try { 150 lock.wait(); 151 fail(); 152 } catch (InterruptedException e) { 153 // interrupt status should be cleared 154 assertFalse(t.isInterrupted()); 155 } 156 } 157 }); 158 } 159 160 /** 161 * Testing invoking Object.wait with interrupt status set. 162 */ 163 @Test 164 void testWaitWithInterruptSet() throws Exception { 165 VThreadRunner.run(() -> { 166 Object obj = new Object(); 167 synchronized (obj) { 168 Thread.currentThread().interrupt(); 169 assertThrows(InterruptedException.class, obj::wait); 170 assertFalse(Thread.currentThread().isInterrupted()); 171 } 172 }); 173 } 174 175 /** 176 * Test interrupting a virtual thread waiting in Object.wait. 177 */ 178 @Test 179 void testInterruptWait() throws Exception { 180 var lock = new Object(); 181 var started = new CountDownLatch(1); 182 var interruptedException = new AtomicBoolean(); 183 var vthread = Thread.ofVirtual().start(() -> { 184 started.countDown(); 185 synchronized (lock) { 186 try { 187 lock.wait(); 188 } catch (InterruptedException e) { 189 interruptedException.set(true); 190 } 191 } 192 }); 193 194 // wait for thread to start and wait 195 started.await(); 196 await(vthread, Thread.State.WAITING); 197 198 // interrupt thread, it should throw InterruptedException and terminate 199 vthread.interrupt(); 200 vthread.join(); 201 assertTrue(interruptedException.get()); 202 } 203 204 /** 205 * Test interrupting a virtual thread blocked waiting to reenter after waiting. 206 */ 207 @Test 208 void testInterruptReenter() throws Exception { 209 var lock = new Object(); 210 var started = new CountDownLatch(1); 211 var interruptedException = new AtomicBoolean(); 212 var vthread = Thread.ofVirtual().start(() -> { 213 started.countDown(); 214 synchronized (lock) { 215 try { 216 lock.wait(); 217 } catch (InterruptedException e) { 218 interruptedException.set(true); 219 } 220 } 221 }); 222 223 // wait for thread to start and wait 224 started.await(); 225 await(vthread, Thread.State.WAITING); 226 227 // notify, thread should block waiting to reenter 228 synchronized (lock) { 229 lock.notifyAll(); 230 await(vthread, Thread.State.BLOCKED); 231 vthread.interrupt(); 232 } 233 234 vthread.join(); 235 assertFalse(interruptedException.get()); 236 assertTrue(vthread.isInterrupted()); 237 } 238 239 /** 240 * Test that Object.wait does not consume the thread's parking permit. 241 */ 242 @Test 243 void testParkingPermitNotConsumed() throws Exception { 244 var lock = new Object(); 245 var started = new CountDownLatch(1); 246 var completed = new AtomicBoolean(); 247 var vthread = Thread.ofVirtual().start(() -> { 248 started.countDown(); 249 LockSupport.unpark(Thread.currentThread()); 250 synchronized (lock) { 251 try { 252 lock.wait(); 253 } catch (InterruptedException e) { 254 fail("wait interrupted"); 255 } 256 } 257 LockSupport.park(); // should not park 258 completed.set(true); 259 }); 260 261 // wait for thread to start and wait 262 started.await(); 263 await(vthread, Thread.State.WAITING); 264 265 // wakeup thread 266 synchronized (lock) { 267 lock.notifyAll(); 268 } 269 270 // thread should terminate 271 vthread.join(); 272 assertTrue(completed.get()); 273 } 274 275 /** 276 * Test that Object.wait does not make available the thread's parking permit. 277 */ 278 @Test 279 void testParkingPermitNotOffered() throws Exception { 280 var lock = new Object(); 281 var started = new CountDownLatch(1); 282 var readyToPark = new CountDownLatch(1); 283 var completed = new AtomicBoolean(); 284 var vthread = Thread.ofVirtual().start(() -> { 285 started.countDown(); 286 synchronized (lock) { 287 try { 288 lock.wait(); 289 } catch (InterruptedException e) { 290 fail("wait interrupted"); 291 } 292 } 293 readyToPark.countDown(); 294 LockSupport.park(); // should park 295 completed.set(true); 296 }); 297 298 // wait for thread to start and wait 299 started.await(); 300 await(vthread, Thread.State.WAITING); 301 302 // wakeup thread 303 synchronized (lock) { 304 lock.notifyAll(); 305 } 306 307 // thread should park 308 readyToPark.await(); 309 await(vthread, Thread.State.WAITING); 310 311 LockSupport.unpark(vthread); 312 313 // thread should terminate 314 vthread.join(); 315 assertTrue(completed.get()); 316 } 317 318 /** 319 * Test that wait(long) throws IAE when timeout is negative. 320 */ 321 @Test 322 void testIllegalArgumentException() throws Exception { 323 VThreadRunner.run(() -> { 324 Object obj = new Object(); 325 synchronized (obj) { 326 assertThrows(IllegalArgumentException.class, () -> obj.wait(-1L)); 327 assertThrows(IllegalArgumentException.class, () -> obj.wait(-1000L)); 328 assertThrows(IllegalArgumentException.class, () -> obj.wait(Long.MIN_VALUE)); 329 } 330 }); 331 } 332 333 /** 334 * Test that wait throws IMSE when not owner. 335 */ 336 @Test 337 void testIllegalMonitorStateException() throws Exception { 338 VThreadRunner.run(() -> { 339 Object obj = new Object(); 340 assertThrows(IllegalMonitorStateException.class, () -> obj.wait()); 341 assertThrows(IllegalMonitorStateException.class, () -> obj.wait(0)); 342 assertThrows(IllegalMonitorStateException.class, () -> obj.wait(1000)); 343 assertThrows(IllegalMonitorStateException.class, () -> obj.wait(Long.MAX_VALUE)); 344 }); 345 } 346 347 /** 348 * Waits for the given thread to reach a given state. 349 */ 350 private void await(Thread thread, Thread.State expectedState) throws InterruptedException { 351 Thread.State state = thread.getState(); 352 while (state != expectedState) { 353 assertTrue(state != Thread.State.TERMINATED, "Thread has terminated"); 354 Thread.sleep(10); 355 state = thread.getState(); 356 } 357 } 358 359 /** 360 * Schedule a thread to be interrupted after a delay. 361 */ 362 private static void scheduleInterrupt(Thread thread, long delay) { 363 Runnable interruptTask = () -> { 364 try { 365 Thread.sleep(delay); 366 thread.interrupt(); 367 } catch (Exception e) { 368 e.printStackTrace(); 369 } 370 }; 371 new Thread(interruptTask).start(); 372 } 373 }