8028474: sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh timeout, leaves looping process
authoregahlin
Thu, 03 Jul 2014 18:20:42 +0200
changeset 25225 f161e8748e8d
parent 25224 7c7e029826e1
child 25226 b508c04b7005
child 25385 2c53e38b77aa
child 25394 c17d31395e21
8028474: sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh timeout, leaves looping process Reviewed-by: sla, jbachorik, rriggs
jdk/test/ProblemList.txt
jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java
jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh
jdk/test/sun/jvmstat/testlibrary/JavaProcess.java
--- a/jdk/test/ProblemList.txt	Thu Jul 03 09:30:25 2014 +0200
+++ b/jdk/test/ProblemList.txt	Thu Jul 03 18:20:42 2014 +0200
@@ -239,9 +239,6 @@
 
 # jdk_tools
 
-# 8028474
-sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh      generic-all
-
 # Tests take too long, on sparcs see 7143279
 tools/pack200/CommandLineTests.java                             solaris-all, macosx-all
 tools/pack200/Pack200Test.java                                  solaris-all, macosx-all
--- a/jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java	Thu Jul 03 09:30:25 2014 +0200
+++ b/jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java	Thu Jul 03 18:20:42 2014 +0200
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2004, 2014, Oracle and/or its affiliates. All rights reserved.
  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
  *
  * This code is free software; you can redistribute it and/or modify it
@@ -21,228 +21,299 @@
  * questions.
  */
 
-import java.util.concurrent.CountDownLatch;
-import java.util.regex.*;
-import java.util.*;
-import java.net.URISyntaxException;
+import java.io.File;
 import java.io.IOException;
-import sun.jvmstat.monitor.*;
-import sun.jvmstat.monitor.event.*;
+import java.net.URISyntaxException;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.nio.file.StandardOpenOption;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.Semaphore;
 
-public class MonitorVmStartTerminate {
+import jdk.testlibrary.OutputBuffer;
+import jdk.testlibrary.ProcessTools;
+import sun.jvmstat.monitor.MonitorException;
+import sun.jvmstat.monitor.MonitoredHost;
+import sun.jvmstat.monitor.MonitoredVm;
+import sun.jvmstat.monitor.MonitoredVmUtil;
+import sun.jvmstat.monitor.VmIdentifier;
+import sun.jvmstat.monitor.event.HostEvent;
+import sun.jvmstat.monitor.event.HostListener;
+import sun.jvmstat.monitor.event.VmStatusChangeEvent;
+
+/*
+
+ Test starts ten Java processes, each with a unique id.
 
-    private static final int SLEEPERS = 10;
-    private static final int SLEEPTIME = 5000;     // sleep time for a sleeper
-    private static final int EXECINTERVAL = 3000;   // wait time between exec's
+ Each process creates a file named after the id and then it waits for
+ the test to remove the file, at which the Java process exits.
+
+ The processes are monitored by the test to make sure notifications
+ are sent when they are started/terminated.
+
+ To avoid Java processes being left behind, in case of an unexpected
+ failure, shutdown hooks are installed that remove files when the test
+ exits. If files are not removed, i.e. due to a JVM crash, the Java
+ processes will exit themselves after 1000 s.
+
+*/
 
-    public static void main(String args[]) throws Exception {
+/*
+ * @test
+ * @bug 4990825
+ * @summary attach to external but local JVM processes
+ * @library /lib/testlibrary
+ * @build jdk.testlibrary.*
+ * @run main/othervm MonitorVmStartTerminate
+ */
+public final class MonitorVmStartTerminate {
 
-        long now = System.currentTimeMillis();
+    private static final int PROCESS_COUNT = 10;
+    private static final long PROCESS_TIMEOUT_IN_NS = 1000*1000_000_000L;
 
-        String sleeperArgs = SLEEPTIME + " " + now;
-        String sleeperPattern = "Sleeper " + sleeperArgs + " \\d+$";
+    public static void main(String... args) throws Exception {
 
         MonitoredHost host = MonitoredHost.getMonitoredHost("localhost");
-        host.setInterval(200);
+        host.setInterval(1); // 1 ms
+
+        String id = UUID.randomUUID().toString();
 
-        Matcher matcher = Pattern.compile(sleeperPattern).matcher("");
-        SleeperListener listener = new SleeperListener(host, matcher, SLEEPERS);
-        host.addHostListener(listener);
-
-        SleeperStarter ss = new SleeperStarter(SLEEPERS, EXECINTERVAL,
-                                               sleeperArgs);
-        ss.start();
+        List<JavaProcess> javaProcesses = new ArrayList<>();
+        for (int i = 0; i < PROCESS_COUNT; i++) {
+            javaProcesses.add(new JavaProcess(id + "_" + i));
+        }
 
-        System.out.println("Waiting for "
-                           + SLEEPERS + " sleepers to terminate");
-        try {
-            ss.join();
-        } catch (InterruptedException e) {
-            throw new Exception("Timed out waiting for sleepers");
+        Listener listener = new Listener(host, javaProcesses);
+        host.addHostListener(listener);
+        for (JavaProcess javaProcess : javaProcesses) {
+            javaProcess.start();
         }
-        listener.waitForSleepersToStart();
-        listener.waitForSleepersToTerminate();
+
+        // Wait for all processes to start before terminating
+        // them, so pids are not reused within a poll interval.
+        System.out.println("Waiting for all processes to get started notification");
+        listener.started.acquire(PROCESS_COUNT);
+
+        for (JavaProcess javaProcess : javaProcesses) {
+            javaProcess.terminate();
+        }
+        System.out.println("Waiting for all processes to get terminated notification");
+        listener.terminated.acquire(PROCESS_COUNT);
+
+        host.removeHostListener(listener);
     }
 
-    public static class SleeperListener implements HostListener {
-
-        private final List<Integer> targets =  new ArrayList<>();
-        private final CountDownLatch terminateLatch;
-        private final CountDownLatch startLatch;
+    private static final class Listener implements HostListener {
+        private final Semaphore started = new Semaphore(0);
+        private final Semaphore terminated = new Semaphore(0);
         private final MonitoredHost host;
-        private final Matcher patternMatcher;
+        private final List<JavaProcess> processes;
 
-        public SleeperListener(MonitoredHost host, Matcher matcher, int count) {
+        public Listener(MonitoredHost host, List<JavaProcess> processes) {
             this.host = host;
-            this.patternMatcher = matcher;
-            this.terminateLatch = new CountDownLatch(count);
-            this.startLatch = new CountDownLatch(count);
+            this.processes = processes;
+            printStatus();
         }
 
-        public void waitForSleepersToTerminate() throws InterruptedException {
-            terminateLatch.await();
+        @Override
+        @SuppressWarnings("unchecked")
+        public void vmStatusChanged(VmStatusChangeEvent event) {
+            releaseStarted(event.getStarted());
+            releaseTerminated(event.getTerminated());
+            printStatus();
         }
 
-        public void waitForSleepersToStart() throws InterruptedException {
-            startLatch.await();
+        private void printStatus() {
+            System.out.printf("started=%d, terminated=%d\n",
+                    started.availablePermits(), terminated.availablePermits());
+        }
+
+        @Override
+        public void disconnected(HostEvent arg0) {
+            // ignore
         }
 
-        private void printList(Set<Integer> list, String msg) {
-            System.out.println(msg + ":");
-            for (Integer lvmid : list) {
-                try {
-                    VmIdentifier vmid = new VmIdentifier("//" + lvmid.intValue());
-                    MonitoredVm target = host.getMonitoredVm(vmid);
+        private void releaseStarted(Set<Integer> ids) {
+            System.out.println("realeaseStarted(" + ids + ")");
+            for (Integer id : ids) {
+                releaseStarted(id);
+            }
+        }
 
-                    StringMonitor cmdMonitor =
-                            (StringMonitor)target.findByName("sun.rt.javaCommand");
-                    String cmd = cmdMonitor.stringValue();
-
-                    System.out.println("\t" + lvmid.intValue() + ": "
-                                       + "\"" + cmd + "\"" + ": ");
-                } catch (URISyntaxException e) {
-                    System.err.println("Unexpected URISyntaxException: "
-                                       + e.getMessage());
-                } catch (MonitorException e) {
-                    System.out.println("\t" + lvmid.intValue()
-                                       + ": error reading monitoring data: "
-                                       + " target possibly terminated?");
+        private void releaseStarted(Integer id) {
+            for (JavaProcess jp : processes) {
+                if (hasMainArgs(id, jp.getMainArgsIdentifier())) {
+                    // store id for terminated identification
+                    jp.setId(id);
+                    System.out.println("RELEASED (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")");
+                    started.release();
+                    return;
                 }
             }
         }
 
-
-        private int addStarted(Set<Integer> started) {
-            int found = 0;
-            for (Integer lvmid : started) {
-                try {
-                    VmIdentifier vmid = new VmIdentifier("//" + lvmid.intValue());
-                    MonitoredVm target = host.getMonitoredVm(vmid);
-
-                    StringMonitor cmdMonitor =
-                            (StringMonitor)target.findByName("sun.rt.javaCommand");
-                    String cmd = cmdMonitor.stringValue();
+        private void releaseTerminated(Set<Integer> ids) {
+            System.out.println("releaseTerminated(" + ids + ")");
+            for (Integer id : ids) {
+                releaseTerminated(id);
+            }
+        }
 
-                    patternMatcher.reset(cmd);
-                    System.out.print("Started: " + lvmid.intValue()
-                                     + ": " + "\"" + cmd + "\"" + ": ");
-
-                    if (patternMatcher.matches()) {
-                        System.out.println("matches pattern - recorded");
-                        targets.add(lvmid);
-                        found++;
-                    }
-                    else {
-                        System.out.println("does not match pattern - ignored");
-                    }
-                } catch (URISyntaxException e) {
-                    System.err.println("Unexpected URISyntaxException: "
-                                       + e.getMessage());
-                } catch (MonitorException e) {
-                    System.err.println("Unexpected MonitorException: "
-                                       + e.getMessage());
+        private void releaseTerminated(Integer id) {
+            for (JavaProcess jp : processes) {
+                if (id.equals(jp.getId())) {
+                    System.out.println("RELEASED (id=" + jp.getId() + ", args=" + jp.getMainArgsIdentifier() + ")");
+                    terminated.release();
+                    return;
                 }
             }
-            return found;
         }
 
-        private int removeTerminated(Set<Integer> terminated) {
-            int found = 0;
-            for (Integer lvmid : terminated) {
-                /*
-                 * we don't attempt to attach to the target here as it's
-                 * now dead and has no jvmstat share memory file. Just see
-                 * if the process id is among those that we saved when we
-                 * started the targets (note - duplicated allowed and somewhat
-                 * expected on windows);
-                 */
-                System.out.print("Terminated: " + lvmid.intValue() + ": ");
-                if (targets.contains(lvmid)) {
-                    System.out.println("matches pattern - termination recorded");
-                    targets.remove(lvmid);
-                    found++;
+        private boolean hasMainArgs(Integer id, String args) {
+            try {
+                VmIdentifier vmid = new VmIdentifier("//" + id.intValue());
+                MonitoredVm target = host.getMonitoredVm(vmid);
+                String monitoredArgs = MonitoredVmUtil.mainArgs(target);
+                if (monitoredArgs != null && monitoredArgs.contains(args)) {
+                    return true;
                 }
-                else {
-                    System.out.println("does not match pattern - ignored");
-                }
+            } catch (URISyntaxException | MonitorException e) {
+                // ok. process probably not running
             }
-            return found;
+            return false;
+        }
+    }
+
+    public final static class JavaProcess {
+
+        private static final class ShutdownHook extends Thread {
+            private final JavaProcess javaProcess;
+
+            public ShutdownHook(JavaProcess javaProcess) {
+                this.javaProcess = javaProcess;
+            }
+
+            public void run() {
+                javaProcess.terminate();
+            }
         }
 
-        @SuppressWarnings("unchecked")
-        public void vmStatusChanged(VmStatusChangeEvent ev) {
-            printList(ev.getActive(), "Active");
-            printList(ev.getStarted(), "Started");
-            printList(ev.getTerminated(), "Terminated");
+        public static void main(String[] args) throws InterruptedException {
+            try {
+                Path path = Paths.get(args[0]);
+                createFile(path);
+                waitForRemoval(path);
+            } catch (Throwable t) {
+                t.printStackTrace();
+                System.exit(1);
+            }
+        }
 
-            int recentlyStarted = addStarted(ev.getStarted());
-            int recentlyTerminated = removeTerminated(ev.getTerminated());
+        public Integer getId() {
+            return id;
+        }
 
-            for (int i = 0; i < recentlyTerminated; i++) {
-                terminateLatch.countDown();
-            }
-            for (int i = 0; i < recentlyStarted; i++) {
-                startLatch.countDown();
+        public void setId(Integer id) {
+            this.id = id;
+        }
+
+        private static void createFile(Path path) throws IOException {
+            Files.write(path, new byte[0], StandardOpenOption.CREATE);
+            if (!Files.exists(path)) {
+                throw new Error("Newly created file " + path
+                        + " does not exist!");
             }
         }
 
-        public void disconnected(HostEvent ev) {
+        private static void waitForRemoval(Path path) {
+            long start = System.nanoTime();
+            while (true) {
+                long now = System.nanoTime();
+                long waited = now - start;
+                System.out.println("Waiting for " + path + " to be removed, " + waited + " ns");
+                if (!Files.exists(path)) {
+                    return;
+                }
+                if (waited > PROCESS_TIMEOUT_IN_NS) {
+                    System.out.println("Start: " + start);
+                    System.out.println("Now: " + now);
+                    System.out.print("Process timed out after " + waited + " ns. Abort.");
+                    System.exit(1);
+                }
+                takeNap();
+            }
         }
-    }
-
-    public static class SleeperStarter extends Thread {
 
-        private final JavaProcess[] processes;
-        private final int execInterval;
-        private final String args;
+        private static void takeNap() {
+            try {
+                Thread.sleep(100);
+            } catch (InterruptedException e) {
+                // ignore
+            }
+        }
 
-        public SleeperStarter(int sleepers, int execInterval, String args) {
-            this.execInterval = execInterval;
-            this.args = args;
-            this.processes = new JavaProcess[sleepers];
+        private final String mainArgsIdentifier;
+        private final ShutdownHook shutdownHook;
+        private volatile Integer id;
+
+        public JavaProcess(String mainArgsIdentifier) {
+            this.mainArgsIdentifier = mainArgsIdentifier;
+            this.shutdownHook = new ShutdownHook(this);
         }
 
-        private synchronized int active() {
-            int active = processes.length;
-            for(JavaProcess jp : processes) {
-                try {
-                    jp.exitValue();
-                    active--;
-                } catch (IllegalThreadStateException e) {
-                    // process hasn't exited yet
+        /**
+         * Starts a Java process asynchronously.
+         *
+         * The process runs until {@link #stop()} is called. If test exits
+         * unexpectedly the process will be cleaned up by a shutdown hook.
+         *
+         * @throws Exception
+         */
+        public void start() throws Exception {
+            Runtime.getRuntime().addShutdownHook(shutdownHook);
+            System.out.println("Starting " + getMainArgsIdentifier());
+
+            Runnable r = new Runnable() {
+                @Override
+                public void run() {
+                    try {
+                        executeJava();
+                    } catch (Throwable t) {
+                        t.printStackTrace();
+                    }
                 }
-            }
-            return active;
+            };
+            new Thread(r).start();
         }
 
-        public void run() {
-           System.out.println("Starting " + processes.length + " sleepers");
-
-           String[] classpath = {
-               "-classpath",
-               System.getProperty("java.class.path")
-           };
+        public void terminate() {
+            try {
+                System.out.println("Terminating " + mainArgsIdentifier);
+                Files.delete(Paths.get(mainArgsIdentifier));
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+            Runtime.getRuntime().removeShutdownHook(shutdownHook);
+        }
 
-           for (int i = 0; i < processes.length; i++) {
-               try {
-                   System.out.println("Starting Sleeper " + i);
-                   synchronized(this) {
-                       processes[i] = new JavaProcess("Sleeper", args + " " + i);
-                       processes[i].addOptions(classpath);
-                   }
-                   processes[i].start();
-                   Thread.sleep(execInterval);
-               } catch (InterruptedException ignore) {
-               } catch (IOException e) {
-                   System.err.println(
-                           "IOException trying to start Sleeper " + i + ": "
-                           + e.getMessage());
-               }
-           }
+        private void executeJava() throws Exception, IOException {
+            String className = JavaProcess.class.getName();
+            String classPath = System.getProperty("test.classes");
+            ProcessBuilder pb = ProcessTools.createJavaProcessBuilder("-cp",
+                    classPath, className, mainArgsIdentifier);
+            OutputBuffer ob = ProcessTools.getOutput(pb.start());
+            System.out.println("Java Process " + getMainArgsIdentifier() + " stder:"
+                    + ob.getStderr());
+            System.err.println("Java Process " + getMainArgsIdentifier() + " stdout:"
+                    + ob.getStdout());
+        }
 
-           // spin waiting for the processes to terminate
-           while (active() > 0) ;
+        public String getMainArgsIdentifier() {
+            return mainArgsIdentifier;
         }
     }
 }
-
--- a/jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh	Thu Jul 03 09:30:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,43 +0,0 @@
-#
-# Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
-# DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
-#
-# This code is free software; you can redistribute it and/or modify it
-# under the terms of the GNU General Public License version 2 only, as
-# published by the Free Software Foundation.
-#
-# This code is distributed in the hope that it will be useful, but WITHOUT
-# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
-# FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
-# version 2 for more details (a copy is included in the LICENSE file that
-# accompanied this code).
-#
-# You should have received a copy of the GNU General Public License version
-# 2 along with this work; if not, write to the Free Software Foundation,
-# Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
-#
-# Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
-# or visit www.oracle.com if you need additional information or have any
-# questions.
-#
-
-#
-# @test
-# @bug 4990825
-# @summary attach to external but local JVM processes
-# @library ../../testlibrary
-# @build Sleeper
-# @build JavaProcess
-# @build MonitorVmStartTerminate
-# @run shell MonitorVmStartTerminate.sh
-#
-
-. ${TESTSRC-.}/../../testlibrary/utils.sh
-
-setup
-verify_os
-
-JAVA="${TESTJAVA}/bin/java"
-CP=${TESTJAVA}${FS}lib${FS}tools.jar${PS}${TESTCLASSES}${PS}${TESTCLASSES}${FS}..${FS}..${FS}testlibrary
-
-${JAVA} ${TESTVMOPTS} -classpath ${CP} MonitorVmStartTerminate
--- a/jdk/test/sun/jvmstat/testlibrary/JavaProcess.java	Thu Jul 03 09:30:25 2014 +0200
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,151 +0,0 @@
-/*
- * Copyright (c) 2004, 2013, Oracle and/or its affiliates. All rights reserved.
- * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
- *
- * This code is free software; you can redistribute it and/or modify it
- * under the terms of the GNU General Public License version 2 only, as
- * published by the Free Software Foundation.
- *
- * This code is distributed in the hope that it will be useful, but WITHOUT
- * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
- * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
- * version 2 for more details (a copy is included in the LICENSE file that
- * accompanied this code).
- *
- * You should have received a copy of the GNU General Public License version
- * 2 along with this work; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
- *
- * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
- * or visit www.oracle.com if you need additional information or have any
- * questions.
- */
-
-/**
- *
- */
-
-import java.io.*;
-
-public class JavaProcess {
-
-    protected Process process = null;
-
-    private String classname;
-    private StringBuilder classArgs;
-    private StringBuilder javaOptions;
-
-    private static String java = System.getProperty("java.home")
-                                 + File.separator + "bin"
-                                 + File.separator + "java";
-
-    public JavaProcess(String classname) {
-        this(classname, "", "");
-    }
-
-    public JavaProcess(String classname, String classArgs) {
-        this(classname, "", classArgs);
-    }
-
-    public JavaProcess(String classname, String javaOptions, String classArgs) {
-        this.classname = classname;
-        this.javaOptions = new StringBuilder(javaOptions);
-        this.classArgs = new StringBuilder(classArgs);
-    }
-
-    /**
-     * add java options to the java command
-     */
-    public void addOptions(String[] opts) {
-        if (javaOptions != null && javaOptions.length() > 0) {
-            javaOptions.append(" ");
-        }
-
-        for (int i = 0; i < opts.length; i++) {
-            if (i != 0) {
-                javaOptions.append(" ");
-            }
-            javaOptions.append(opts[i]);
-        }
-    }
-
-    /**
-     * add arguments to the class arguments
-     */
-    public void addArguments(String[] args) {
-        if (classArgs != null && classArgs.length() > 0) {
-            classArgs.append(" ");
-        }
-
-        for (int i = 0; i < args.length; i++) {
-            if (i != 0) {
-                classArgs.append(" ");
-            }
-            classArgs.append(args[i]);
-        }
-    }
-
-    /**
-     * start the java process
-     */
-    public void start() throws IOException {
-        if (process != null) {
-            return;
-        }
-
-        String javaCommand = java + " " + javaOptions + " "
-                             + classname + " " + classArgs;
-
-        System.out.println("exec'ing: " + javaCommand);
-
-        process = Runtime.getRuntime().exec(javaCommand);
-    }
-
-    /**
-     * destroy the java process
-     */
-    public void destroy() {
-        if (process != null) {
-            process.destroy();
-        }
-        process = null;
-    }
-
-    public int exitValue() {
-        if (process != null) {
-            return process.exitValue();
-        }
-        throw new RuntimeException("exitValue called with process == null");
-    }
-
-    public InputStream getErrorStream() {
-        if (process != null) {
-            return process.getErrorStream();
-        }
-        throw new RuntimeException(
-                "getErrorStream() called with process == null");
-    }
-
-    public InputStream getInputStream() {
-        if (process != null) {
-            return process.getInputStream();
-        }
-        throw new RuntimeException(
-                "getInputStream() called with process == null");
-    }
-
-    public OutputStream getOutputStream() {
-        if (process != null) {
-            return process.getOutputStream();
-        }
-        throw new RuntimeException(
-                "getOutputStream() called with process == null");
-    }
-
-    public int waitFor() throws InterruptedException {
-        if (process != null) {
-            return process.waitFor();
-        }
-        throw new RuntimeException("waitFor() called with process == null");
-    }
-}