8028474: sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh timeout, leaves looping process
Reviewed-by: sla, jbachorik, rriggs
--- 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");
- }
-}