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