blob: 5b388169a272777ea2723a7500d9d0f782060d97 [file] [log] [blame]
/*=========================================================================
* Copyright (c) 2002-2014 Pivotal Software, Inc. All Rights Reserved.
* This product is protected by U.S. and international copyright
* and intellectual property laws. Pivotal products are covered by
* more patents listed at http://www.pivotal.io/patents.
*=========================================================================
*/
package dunit.standalone;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintStream;
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.Arrays;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.io.FileUtils;
import com.gemstone.gemfire.distributed.internal.DistributionConfig;
import com.gemstone.gemfire.internal.FileUtil;
import com.gemstone.gemfire.internal.logging.LogService;
import com.gemstone.gemfire.internal.sequencelog.EntryLogger;
import com.gemstone.gemfire.internal.sequencelog.SequenceLoggerImpl;
import dunit.RemoteDUnitVMIF;
/**
* @author dsmith
*
*/
public class ProcessManager {
private int namingPort;
private Map<Integer, ProcessHolder> processes = new HashMap<Integer, ProcessHolder>();
private File log4jConfig;
private int pendingVMs;
private Registry registry;
public ProcessManager(int namingPort, Registry registry) {
this.namingPort = namingPort;
this.registry = registry;
}
public void launchVMs() throws IOException, NotBoundException {
log4jConfig = LogService.findLog4jConfigInCurrentDir();
}
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) {
//TODO - stop it gracefully? Why bother
process.kill();
}
}
}
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) {
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();
return new String[] {
cmd, "-classpath", classPath,
"-D" + DUnitLauncher.RMI_PORT_PARAM + "=" + namingPort,
"-D" + DUnitLauncher.VM_NUM_PARAM + "=" + vmNum,
"-D" + DUnitLauncher.WORKSPACE_DIR_PARAM + "=" + new File(".").getAbsolutePath(),
"-DlogLevel=" + DUnitLauncher.LOG_LEVEL,
"-Djava.library.path=" + System.getProperty("java.library.path"),
"-Xrunjdwp:transport=dt_socket,server=y,suspend=n",
"-XX:+HeapDumpOnOutOfMemoryError",
"-Xmx512m",
"-XX:MaxPermSize=256M",
"-Dgemfire.DEFAULT_MAX_OPLOG_SIZE=10",
"-Dgemfire.disallowMcastDefaults=true",
"-XX:MaxPermSize=256M",
"-ea",
agent,
"dunit.standalone.ChildVM"
};
}
/**
* 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 RemoteDUnitVMIF getStub(int i) throws AccessException, RemoteException, NotBoundException, InterruptedException {
waitForVMs(DUnitLauncher.STARTUP_TIMEOUT);
return (RemoteDUnitVMIF) registry.lookup("vm" + i);
}
}