1 /* 2 * Copyright (c) 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 8312174 27 * @summary missing JVMTI events from vthreads parked during JVMTI attach 28 * @requires vm.continuations 29 * @requires vm.jvmti 30 * @requires vm.compMode != "Xcomp" 31 * @run main/othervm/native 32 * -Djdk.virtualThreadScheduler.parallelism=9 33 * -Djdk.attach.allowAttachSelf=true -XX:+EnableDynamicAgentLoading VThreadEventTest attach 34 */ 35 36 import com.sun.tools.attach.VirtualMachine; 37 import java.util.concurrent.ExecutorService; 38 import java.util.concurrent.Executors; 39 import java.util.concurrent.locks.LockSupport; 40 import java.util.List; 41 import java.util.ArrayList; 42 43 /* 44 * The test uses custom implementation of the CountDownLatch class. 45 * The reason is we want the state of tested thread to be predictable. 46 * With java.util.concurrent.CountDownLatch it is not clear what thread state is expected. 47 */ 48 class CountDownLatch { 49 private int count = 0; 50 51 CountDownLatch(int count) { 52 this.count = count; 53 } 54 55 public synchronized void countDown() { 56 count--; 57 notify(); 58 } 59 60 public synchronized void await() throws InterruptedException { 61 while (count > 0) { 62 wait(1); 63 } 64 } 65 } 66 67 public class VThreadEventTest { 68 static final int TCNT1 = 10; 69 static final int TCNT2 = 4; 70 static final int TCNT3 = 4; 71 static final int THREAD_CNT = TCNT1 + TCNT2 + TCNT3; 72 73 private static void log(String msg) { System.out.println(msg); } 74 75 private static native int threadEndCount(); 76 private static native int threadMountCount(); 77 private static native int threadUnmountCount(); 78 79 private static volatile boolean attached; 80 private static boolean failed; 81 private static List<Thread> test1Threads = new ArrayList(TCNT1); 82 83 private static CountDownLatch ready0 = new CountDownLatch(THREAD_CNT); 84 private static CountDownLatch ready1 = new CountDownLatch(TCNT1); 85 private static CountDownLatch ready2 = new CountDownLatch(THREAD_CNT); 86 private static CountDownLatch mready = new CountDownLatch(1); 87 88 private static void await(CountDownLatch dumpedLatch) { 89 try { 90 dumpedLatch.await(); 91 } catch (InterruptedException e) { 92 throw new RuntimeException(e); 93 } 94 } 95 96 // The test1 vthreads are kept unmounted until interrupted after agent attach. 97 static final Runnable test1 = () -> { 98 synchronized (test1Threads) { 99 test1Threads.add(Thread.currentThread()); 100 } 101 log("test1 vthread started"); 102 ready0.countDown(); 103 await(mready); 104 ready1.countDown(); // to guaranty state is not State.WAITING after await(mready) 105 try { 106 Thread.sleep(20000); // big timeout to keep unmounted until interrupted 107 } catch (InterruptedException ex) { 108 // it is expected, ignore 109 } 110 ready2.countDown(); 111 }; 112 113 // The test2 vthreads are kept mounted until agent attach. 114 static final Runnable test2 = () -> { 115 log("test2 vthread started"); 116 ready0.countDown(); 117 await(mready); 118 while (!attached) { 119 // keep mounted 120 } 121 ready2.countDown(); 122 }; 123 124 // The test3 vthreads are kept mounted until agent attach. 125 static final Runnable test3 = () -> { 126 log("test3 vthread started"); 127 ready0.countDown(); 128 await(mready); 129 while (!attached) { 130 // keep mounted 131 } 132 LockSupport.parkNanos(10_000_000L); // will cause extra mount and unmount 133 ready2.countDown(); 134 }; 135 136 public static void main(String[] args) throws Exception { 137 if (Runtime.getRuntime().availableProcessors() < 8) { 138 log("WARNING: test expects at least 8 processors."); 139 } 140 try (ExecutorService executorService = Executors.newVirtualThreadPerTaskExecutor()) { 141 for (int i = 0; i < TCNT1; i++) { 142 executorService.execute(test1); 143 } 144 for (int i = 0; i < TCNT2; i++) { 145 executorService.execute(test2); 146 } 147 for (int i = 0; i < TCNT3; i++) { 148 executorService.execute(test3); 149 } 150 await(ready0); 151 mready.countDown(); 152 await(ready1); // to guarantee state is not State.TIMED_WAITING after await(mready) in test1() 153 // wait for test1 threads to reach TIMED_WAITING state in sleep() 154 for (Thread t : test1Threads) { 155 Thread.State state = t.getState(); 156 log("DBG: state: " + state); 157 while (state != Thread.State.TIMED_WAITING) { 158 Thread.sleep(10); 159 state = t.getState(); 160 log("DBG: state: " + state); 161 } 162 } 163 164 VirtualMachine vm = VirtualMachine.attach(String.valueOf(ProcessHandle.current().pid())); 165 vm.loadAgentLibrary("VThreadEventTest"); 166 Thread.sleep(200); // to allow the agent to get ready 167 168 attached = true; 169 for (Thread t : test1Threads) { 170 t.interrupt(); 171 } 172 ready2.await(); 173 } 174 // wait until all VirtualThreadEnd events have been sent 175 for (int sleepNo = 1; threadEndCount() < THREAD_CNT; sleepNo++) { 176 Thread.sleep(100); 177 if (sleepNo % 100 == 0) { // 10 sec period of waiting 178 log("main: waited seconds: " + sleepNo/10); 179 } 180 } 181 int threadEndCnt = threadEndCount(); 182 int threadMountCnt = threadMountCount(); 183 int threadUnmountCnt = threadUnmountCount(); 184 int threadEndExp = THREAD_CNT; 185 int threadMountExp = THREAD_CNT - TCNT2; 186 int threadUnmountExp = THREAD_CNT + TCNT3; 187 188 log("ThreadEnd cnt: " + threadEndCnt + " (expected: " + threadEndExp + ")"); 189 log("ThreadMount cnt: " + threadMountCnt + " (expected: " + threadMountExp + ")"); 190 log("ThreadUnmount cnt: " + threadUnmountCnt + " (expected: " + threadUnmountExp + ")"); 191 192 if (threadEndCnt != threadEndExp) { 193 log("FAILED: unexpected count of ThreadEnd events"); 194 failed = true; 195 } 196 if (threadMountCnt != threadMountExp) { 197 log("FAILED: unexpected count of ThreadMount events"); 198 failed = true; 199 } 200 if (threadUnmountCnt != threadUnmountExp) { 201 log("FAILED: unexpected count of ThreadUnmount events"); 202 failed = true; 203 } 204 if (failed) { 205 throw new RuntimeException("FAILED: event count is wrong"); 206 } 207 } 208 209 } 210