1 /* 2 * Copyright (c) 2022, 2023, 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 * @bug 8292240 27 * @summary Test the scenario where a blocking operation pins a virtual thread to its 28 * carrier thread (cT1) and doesn't activate a spare. Subsequent blocking operations 29 * that pin a virtual thread to cT1 should attempt to activate a spare. 30 * @requires vm.continuations 31 * @run main/othervm 32 * -Djdk.virtualThreadScheduler.parallelism=1 33 * -Djdk.virtualThreadScheduler.maxPoolSize=2 ActivateSpareCarrier 100 34 */ 35 36 import java.time.Duration; 37 import java.util.Comparator; 38 import java.util.List; 39 import java.util.concurrent.ForkJoinWorkerThread; 40 import java.util.stream.Collectors; 41 42 public class ActivateSpareCarrier { 43 44 private static final int DEFAULT_ITERTAIONS = 10_000; 45 46 private static final Object LOCK = new Object(); 47 48 public static void main(String[] args) throws Exception { 49 int iterations; 50 if (args.length == 0) { 51 iterations = DEFAULT_ITERTAIONS; 52 } else { 53 iterations = Integer.parseInt(args[0]); 54 } 55 for (int i = 0; i < iterations; i++) { 56 test(i); 57 } 58 } 59 60 /** 61 * This method creates 3 virtual threads: 62 * - thread1 blocks in Object.wait, activating a spare carrier thread 63 * - thread2 is started and runs on the spare carrier thread 64 * - thread1 is notified causing it to re-adjust the release count and terminate 65 * - thread3 is started and should run on the one active thread 66 * 67 * This method need invoked at least twice in the same VM. 68 */ 69 private static void test(int i) throws Exception { 70 System.out.printf("---- %d ----%n", i); 71 72 // thread1 blocks in wait, this triggers a tryCompensate to activate a spare thread 73 Thread thread1 = Thread.ofVirtual().unstarted(() -> { 74 System.out.println(Thread.currentThread()); 75 synchronized (LOCK) { 76 try { 77 LOCK.wait(); 78 } catch (InterruptedException e) { } 79 } 80 }); 81 System.out.printf("starting waiter thread #%d%n", thread1.threadId()); 82 thread1.start(); 83 84 // wait for thread1 to block in Object.wait 85 while (thread1.getState() != Thread.State.WAITING) { 86 Thread.sleep(10); 87 } 88 89 // start another virtual thread, it should run on the spare carrier thread 90 startAndJoinVirtualThread(); 91 92 // notify thread1, this releases the blocker 93 synchronized (LOCK) { 94 LOCK.notifyAll(); 95 } 96 joinThread(thread1); 97 98 // start another virtual thread after counts have been re-adjusted 99 startAndJoinVirtualThread(); 100 } 101 102 /** 103 * Start a virtual thread and wait for it to terminate. 104 */ 105 private static void startAndJoinVirtualThread() throws InterruptedException { 106 Thread thread = Thread.ofVirtual().unstarted(() -> { 107 System.out.println(Thread.currentThread()); 108 }); 109 System.out.format("starting #%d%n", thread.threadId()); 110 thread.start(); 111 joinThread(thread); 112 } 113 114 /** 115 * Wait for the give thread to terminate with diagnostic output if the thread does 116 * not terminate quickly. 117 */ 118 private static void joinThread(Thread thread) throws InterruptedException { 119 long tid = thread.threadId(); 120 System.out.printf("Waiting for #%d to terminate%n", tid); 121 boolean terminated = thread.join(Duration.ofSeconds(2)); 122 if (!terminated) { 123 System.out.printf("#%d did not terminate quickly, continue to wait...%n", tid); 124 printForkJoinWorkerThreads(); 125 thread.join(); 126 } 127 System.out.printf("#%d terminated%n", tid); 128 } 129 130 /** 131 * Print the list of ForkJoinWorkerThreads and their stack traces. 132 */ 133 private static void printForkJoinWorkerThreads() { 134 List<Thread> threads = Thread.getAllStackTraces().keySet().stream() 135 .filter(t -> t instanceof ForkJoinWorkerThread) 136 .sorted(Comparator.comparingLong(Thread::threadId)) 137 .collect(Collectors.toList()); 138 System.out.println("ForkJoinWorkerThreads:"); 139 for (Thread t : threads) { 140 System.out.printf(" %s%n", t); 141 StackTraceElement[] stack = t.getStackTrace(); 142 for (StackTraceElement e : stack) { 143 System.out.printf(" %s%n", e); 144 } 145 } 146 } 147 }