# HG changeset patch # User egahlin # Date 1404404442 -7200 # Node ID f161e8748e8d1b325ceb64f5d78a8cc23a345565 # Parent 7c7e029826e18d1aa0527566c4015fde5274fd7b 8028474: sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh timeout, leaves looping process Reviewed-by: sla, jbachorik, rriggs diff -r 7c7e029826e1 -r f161e8748e8d jdk/test/ProblemList.txt --- 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 diff -r 7c7e029826e1 -r f161e8748e8d jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.java --- 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 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 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 processes; - public SleeperListener(MonitoredHost host, Matcher matcher, int count) { + public Listener(MonitoredHost host, List 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 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 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 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 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 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; } } } - diff -r 7c7e029826e1 -r f161e8748e8d jdk/test/sun/jvmstat/monitor/MonitoredVm/MonitorVmStartTerminate.sh --- 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 diff -r 7c7e029826e1 -r f161e8748e8d jdk/test/sun/jvmstat/testlibrary/JavaProcess.java --- 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"); - } -}