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 }