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