1 /* 2 * Copyright (c) 2004, 2017, 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 import java.io.File; 25 import java.io.IOException; 26 import java.net.URISyntaxException; 27 import java.nio.file.Files; 28 import java.nio.file.Path; 29 import java.nio.file.Paths; 30 import java.nio.file.StandardOpenOption; 31 import java.util.ArrayList; 32 import java.util.List; 33 import java.util.Set; 34 import java.util.UUID; 35 import java.util.concurrent.Semaphore; 36 37 import jdk.testlibrary.OutputAnalyzer; 38 import jdk.testlibrary.ProcessTools; 39 import sun.jvmstat.monitor.MonitorException; 40 import sun.jvmstat.monitor.MonitoredHost; 41 import sun.jvmstat.monitor.MonitoredVm; 42 import sun.jvmstat.monitor.MonitoredVmUtil; 43 import sun.jvmstat.monitor.VmIdentifier; 44 import sun.jvmstat.monitor.event.HostEvent; 45 import sun.jvmstat.monitor.event.HostListener; 46 import sun.jvmstat.monitor.event.VmStatusChangeEvent; 47 48 /* 49 50 Test starts ten Java processes, each with a unique id. 51 52 Each process creates a file named after the id and then it waits for 53 the test to remove the file, at which the Java process exits. 54 55 The processes are monitored by the test to make sure notifications 56 are sent when they are started/terminated. 57 58 To avoid Java processes being left behind, in case of an unexpected 59 failure, shutdown hooks are installed that remove files when the test 60 exits. If files are not removed, i.e. due to a JVM crash, the Java 61 processes will exit themselves after 1000 s. 62 63 */ 64 65 /* 66 * @test 67 * @bug 4990825 68 * @summary attach to external but local JVM processes 69 * 70 * @library /lib/testlibrary 71 * @modules java.management 72 * jdk.internal.jvmstat/sun.jvmstat.monitor 73 * jdk.internal.jvmstat/sun.jvmstat.monitor.event 74 * 75 * @build jdk.testlibrary.* 76 * @run main/othervm MonitorVmStartTerminate 77 */ 78 public final class MonitorVmStartTerminate { 79 80 private static final int PROCESS_COUNT = 10; 81 82 public static void main(String... args) throws Exception { 83 84 MonitoredHost host = MonitoredHost.getMonitoredHost("localhost"); 85 host.setInterval(1); // 1 ms 86 87 String id = UUID.randomUUID().toString(); 88 89 List<JavaProcess> javaProcesses = new ArrayList<>(); 90 for (int i = 0; i < PROCESS_COUNT; i++) { 91 javaProcesses.add(new JavaProcess(id + "_" + i)); 92 } 93 94 Listener listener = new Listener(host, javaProcesses); 95 host.addHostListener(listener); 96 for (JavaProcess javaProcess : javaProcesses) { 97 javaProcess.start(); 98 } 99 100 // Wait for all processes to start before terminating 101 // them, so pids are not reused within a poll interval. 102 System.out.println("Waiting for all processes to get started notification"); 103 listener.started.acquire(PROCESS_COUNT); 104 105 for (JavaProcess javaProcess : javaProcesses) { 106 javaProcess.terminate(); 107 } 108 System.out.println("Waiting for all processes to get terminated notification"); 109 listener.terminated.acquire(PROCESS_COUNT); 110 111 host.removeHostListener(listener); 112 } 113 114 private static final class Listener implements HostListener { 115 private final Semaphore started = new Semaphore(0); 116 private final Semaphore terminated = new Semaphore(0); 117 private final MonitoredHost host; 118 private final List<JavaProcess> processes; 119 120 public Listener(MonitoredHost host, List<JavaProcess> processes) { 121 this.host = host; 122 this.processes = processes; 123 printStatus(); 124 } 125 126 @Override 127 @SuppressWarnings("unchecked") 128 public void vmStatusChanged(VmStatusChangeEvent event) { 129 releaseStarted(event.getStarted()); 130 releaseTerminated(event.getTerminated()); 131 printStatus(); 132 } 133 134 private void printStatus() { 135 System.out.printf("started=%d, terminated=%d\n", 136 started.availablePermits(), terminated.availablePermits()); 137 } 138 139 @Override 140 public void disconnected(HostEvent arg0) { 141 // ignore 142 } 143 144 private void releaseStarted(Set<Integer> ids) { 145 System.out.println("realeaseStarted(" + ids + ")"); 146 for (Integer id : ids) { 147 releaseStarted(id); 148 } 149 } 150 151 private void releaseStarted(Integer id) { 152 for (JavaProcess jp : processes) { 153 if (hasMainArgs(id, jp.getMainArgsIdentifier())) { 154 // store id for terminated identification 155 jp.setId(id); 156 System.out.println("RELEASED (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")"); 157 started.release(); 158 return; 159 } 160 } 161 } 162 163 private void releaseTerminated(Set<Integer> ids) { 164 System.out.println("releaseTerminated(" + ids + ")"); 165 for (Integer id : ids) { 166 releaseTerminated(id); 167 } 168 } 169 170 private void releaseTerminated(Integer id) { 171 for (JavaProcess jp : processes) { 172 if (id.equals(jp.getId())) { 173 System.out.println("RELEASED (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")"); 174 terminated.release(); 175 return; 176 } 177 } 178 } 179 180 private boolean hasMainArgs(Integer id, String args) { 181 try { 182 VmIdentifier vmid = new VmIdentifier("//" + id.intValue()); 183 MonitoredVm target = host.getMonitoredVm(vmid); 184 String monitoredArgs = MonitoredVmUtil.mainArgs(target); 185 if (monitoredArgs != null && monitoredArgs.contains(args)) { 186 return true; 187 } 188 } catch (URISyntaxException | MonitorException e) { 189 // ok. process probably not running 190 } 191 return false; 192 } 193 } 194 195 public final static class JavaProcess { 196 197 private static final class ShutdownHook extends Thread { 198 private final JavaProcess javaProcess; 199 200 public ShutdownHook(JavaProcess javaProcess) { 201 this.javaProcess = javaProcess; 202 } 203 204 public void run() { 205 javaProcess.terminate(); 206 } 207 } 208 209 public static void main(String[] args) throws InterruptedException { 210 try { 211 Path path = Paths.get(args[0]); 212 createFile(path); 213 waitForRemoval(path); 214 } catch (Throwable t) { 215 t.printStackTrace(); 216 System.exit(1); 217 } 218 } 219 220 public Integer getId() { 221 return id; 222 } 223 224 public void setId(Integer id) { 225 this.id = id; 226 } 227 228 private static void createFile(Path path) throws IOException { 229 Files.write(path, new byte[0], StandardOpenOption.CREATE); 230 } 231 232 private static void waitForRemoval(Path path) { 233 String timeoutFactorText = System.getProperty("test.timeout.factor", "1.0"); 234 double timeoutFactor = Double.parseDouble(timeoutFactorText); 235 long timeoutNanos = 1000_000_000L*(long)(1000*timeoutFactor); 236 long start = System.nanoTime(); 237 while (true) { 238 long now = System.nanoTime(); 239 long waited = now - start; 240 System.out.println("Waiting for " + path + " to be removed, " + waited + " ns"); 241 if (!Files.exists(path)) { 242 return; 243 } 244 if (waited > timeoutNanos) { 245 System.out.println("Start: " + start); 246 System.out.println("Now: " + now); 247 System.out.println("Process timed out after " + waited + " ns. Abort."); 248 System.exit(1); 249 } 250 takeNap(); 251 } 252 } 253 254 private static void takeNap() { 255 try { 256 Thread.sleep(100); 257 } catch (InterruptedException e) { 258 // ignore 259 } 260 } 261 262 private final String mainArgsIdentifier; 263 private final ShutdownHook shutdownHook; 264 private volatile Integer id; 265 266 public JavaProcess(String mainArgsIdentifier) { 267 this.mainArgsIdentifier = mainArgsIdentifier; 268 this.shutdownHook = new ShutdownHook(this); 269 } 270 271 /** 272 * Starts a Java process asynchronously. 273 * 274 * The process runs until {@link #stop()} is called. If test exits 275 * unexpectedly the process will be cleaned up by a shutdown hook. 276 * 277 * @throws Exception 278 */ 279 public void start() throws Exception { 280 Runtime.getRuntime().addShutdownHook(shutdownHook); 281 System.out.println("Starting " + getMainArgsIdentifier()); 282 283 Runnable r = new Runnable() { 284 @Override 285 public void run() { 286 try { 287 executeJava(); 288 } catch (Throwable t) { 289 t.printStackTrace(); 290 } 291 } 292 }; 293 new Thread(r).start(); 294 } 295 296 public void terminate() { 297 try { 298 System.out.println("Terminating " + mainArgsIdentifier); 299 // File must be created before proceeding, 300 // otherwise Java process may loop forever 301 // waiting for file to be removed. 302 Path path = Paths.get(mainArgsIdentifier); 303 while (!Files.exists(path)) { 304 takeNap(); 305 } 306 Files.delete(path); 307 } catch (IOException e) { 308 e.printStackTrace(); 309 } 310 Runtime.getRuntime().removeShutdownHook(shutdownHook); 311 } 312 313 private void executeJava() throws Throwable { 314 String className = JavaProcess.class.getName(); 315 String classPath = System.getProperty("test.classes"); 316 ProcessBuilder pb = ProcessTools.createJavaProcessBuilder( 317 "-Dtest.timeout.factor=" + System.getProperty("test.timeout.factor", "1.0"), 318 "-cp", classPath, className, mainArgsIdentifier); 319 OutputAnalyzer ob = ProcessTools.executeProcess(pb); 320 System.out.println("Java Process " + getMainArgsIdentifier() + " stderr:" 321 + ob.getStderr()); 322 System.err.println("Java Process " + getMainArgsIdentifier() + " stdout:" 323 + ob.getStdout()); 324 } 325 326 public String getMainArgsIdentifier() { 327 return mainArgsIdentifier; 328 } 329 } 330 }