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