Java tutorial
/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.gemstone.gemfire.test.dunit.standalone; import com.gemstone.gemfire.distributed.internal.DistributionConfig; import com.gemstone.gemfire.distributed.internal.InternalLocator; import com.gemstone.gemfire.internal.FileUtil; import org.apache.commons.io.FileUtils; import java.io.*; import java.lang.management.ManagementFactory; import java.lang.management.RuntimeMXBean; import java.rmi.AccessException; import java.rmi.NotBoundException; import java.rmi.RemoteException; import java.rmi.registry.Registry; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.Map; import static com.gemstone.gemfire.distributed.ConfigurationProperties.*; /** * */ public class ProcessManager { private int namingPort; private Map<Integer, ProcessHolder> processes = new HashMap<Integer, ProcessHolder>(); private File log4jConfig; private int pendingVMs; private Registry registry; private int debugPort = Integer.getInteger("dunit.debug.basePort", 0); private int suspendVM = Integer.getInteger("dunit.debug.suspendVM", -100); public ProcessManager(int namingPort, Registry registry) { this.namingPort = namingPort; this.registry = registry; } public synchronized void launchVM(int vmNum) throws IOException { if (processes.containsKey(vmNum)) { throw new IllegalStateException("VM " + vmNum + " is already running."); } String[] cmd = buildJavaCommand(vmNum, namingPort); System.out.println("Executing " + Arrays.asList(cmd)); File workingDir = getVMDir(vmNum); try { FileUtil.delete(workingDir); } catch (IOException e) { //This delete is occasionally failing on some platforms, maybe due to a lingering //process. Allow the process to be launched anyway. System.err.println( "Unable to delete " + workingDir + ". Currently contains " + Arrays.asList(workingDir.list())); } workingDir.mkdirs(); if (log4jConfig != null) { FileUtils.copyFileToDirectory(log4jConfig, workingDir); } //TODO - delete directory contents, preferably with commons io FileUtils Process process = Runtime.getRuntime().exec(cmd, null, workingDir); pendingVMs++; ProcessHolder holder = new ProcessHolder(process); processes.put(vmNum, holder); linkStreams(vmNum, holder, process.getErrorStream(), System.err); linkStreams(vmNum, holder, process.getInputStream(), System.out); } public static File getVMDir(int vmNum) { return new File(DUnitLauncher.DUNIT_DIR, "vm" + vmNum); } public synchronized void killVMs() { for (ProcessHolder process : processes.values()) { if (process != null) { process.kill(); } } } public synchronized boolean hasLiveVMs() { for (ProcessHolder process : processes.values()) { if (process != null && process.isAlive()) { return true; } } return false; } public synchronized void bounce(int vmNum) { if (!processes.containsKey(vmNum)) { throw new IllegalStateException("No such process " + vmNum); } try { ProcessHolder holder = processes.remove(vmNum); holder.kill(); holder.getProcess().waitFor(); launchVM(vmNum); } catch (InterruptedException | IOException e) { throw new RuntimeException("Unable to restart VM " + vmNum, e); } } private void linkStreams(final int vmNum, final ProcessHolder holder, final InputStream in, final PrintStream out) { Thread ioTransport = new Thread() { public void run() { BufferedReader reader = new BufferedReader(new InputStreamReader(in)); String vmName = (vmNum == -2) ? "[locator]" : "[vm_" + vmNum + "]"; try { String line = reader.readLine(); while (line != null) { if (line.length() == 0) { out.println(); } else { out.print(vmName); out.println(line); } line = reader.readLine(); } } catch (Exception e) { if (!holder.isKilled()) { out.println("Error transporting IO from child process"); e.printStackTrace(out); } } } }; ioTransport.setDaemon(true); ioTransport.start(); } private String[] buildJavaCommand(int vmNum, int namingPort) { String cmd = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java"; String classPath = System.getProperty("java.class.path"); //String tmpDir = System.getProperty("java.io.tmpdir"); String agent = getAgentString(); String jdkDebug = ""; if (debugPort > 0) { jdkDebug += ",address=" + debugPort; debugPort++; } String jdkSuspend = vmNum == suspendVM ? "y" : "n"; ArrayList<String> cmds = new ArrayList<String>(); cmds.add(cmd); cmds.add("-classpath"); cmds.add(classPath); cmds.add("-D" + DUnitLauncher.RMI_PORT_PARAM + "=" + namingPort); cmds.add("-D" + DUnitLauncher.VM_NUM_PARAM + "=" + vmNum); cmds.add("-D" + DUnitLauncher.WORKSPACE_DIR_PARAM + "=" + new File(".").getAbsolutePath()); if (vmNum >= 0) { // let the locator print a banner cmds.add("-D" + InternalLocator.INHIBIT_DM_BANNER + "=true"); } cmds.add("-D" + LOG_LEVEL + "=" + DUnitLauncher.logLevel); if (DUnitLauncher.LOG4J != null) { cmds.add("-Dlog4j.configurationFile=" + DUnitLauncher.LOG4J); } cmds.add("-Djava.library.path=" + System.getProperty("java.library.path")); cmds.add("-Xrunjdwp:transport=dt_socket,server=y,suspend=" + jdkSuspend + jdkDebug); cmds.add("-XX:+HeapDumpOnOutOfMemoryError"); cmds.add("-Xmx512m"); cmds.add("-D" + DistributionConfig.GEMFIRE_PREFIX + "DEFAULT_MAX_OPLOG_SIZE=10"); cmds.add("-D" + DistributionConfig.GEMFIRE_PREFIX + "disallowMcastDefaults=true"); cmds.add("-ea"); cmds.add("-XX:+PrintGC"); cmds.add("-XX:+PrintGCDetails"); cmds.add("-XX:+PrintGCTimeStamps"); cmds.add(agent); cmds.add(ChildVM.class.getName()); String[] rst = new String[cmds.size()]; cmds.toArray(rst); return rst; } /** * Get the java agent passed to this process and pass it to the child VMs. * This was added to support jacoco code coverage reports */ private String getAgentString() { RuntimeMXBean runtimeBean = ManagementFactory.getRuntimeMXBean(); if (runtimeBean != null) { for (String arg : runtimeBean.getInputArguments()) { if (arg.contains("-javaagent:")) { //HACK for gradle bug GRADLE-2859. Jacoco is passing a relative path //That won't work when we pass this to dunit VMs in a different //directory arg = arg.replace("-javaagent:..", "-javaagent:" + System.getProperty("user.dir") + File.separator + ".."); arg = arg.replace("destfile=..", "destfile=" + System.getProperty("user.dir") + File.separator + ".."); return arg; } } } return "-DdummyArg=true"; } synchronized void signalVMReady() { pendingVMs--; this.notifyAll(); } public synchronized boolean waitForVMs(long timeout) throws InterruptedException { long end = System.currentTimeMillis() + timeout; while (pendingVMs > 0) { long remaining = end - System.currentTimeMillis(); if (remaining <= 0) { return false; } this.wait(remaining); } return true; } private static class ProcessHolder { private final Process process; private volatile boolean killed = false; public ProcessHolder(Process process) { this.process = process; } public void kill() { this.killed = true; process.destroy(); } public Process getProcess() { return process; } public boolean isKilled() { return killed; } public boolean isAlive() { return !killed && process.isAlive(); } } public RemoteDUnitVMIF getStub(int i) throws AccessException, RemoteException, NotBoundException, InterruptedException { waitForVMs(DUnitLauncher.STARTUP_TIMEOUT); return (RemoteDUnitVMIF) registry.lookup("vm" + i); } }