org.apache.geode.test.dunit.standalone.ProcessManager.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.geode.test.dunit.standalone.ProcessManager.java

Source

/*
 * 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 org.apache.geode.test.dunit.standalone;

import static org.apache.geode.distributed.ConfigurationProperties.*;

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.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.io.FileUtils;

import org.apache.geode.distributed.internal.DistributionConfig;
import org.apache.geode.distributed.internal.InternalLocator;
import org.apache.geode.internal.FileUtil;
import org.apache.geode.test.dunit.VM;

/**
 *
 */
public class ProcessManager {
    private int namingPort;
    private Map<Integer, ProcessHolder> processes = new HashMap<>();
    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);
    private VersionManager versionManager;

    public ProcessManager(int namingPort, Registry registry) {
        this.versionManager = VersionManager.getInstance();
        this.namingPort = namingPort;
        this.registry = registry;
    }

    public synchronized void launchVM(int vmNum) throws IOException {
        launchVM(VersionManager.CURRENT_VERSION, vmNum, false);
    }

    public synchronized void launchVM(String version, int vmNum, boolean bouncedVM) throws IOException {
        if (processes.containsKey(vmNum)) {
            throw new IllegalStateException("VM " + vmNum + " is already running.");
        }

        String[] cmd = buildJavaCommand(vmNum, namingPort, version);
        System.out.println("Executing " + Arrays.toString(cmd));
        File workingDir = getVMDir(version, vmNum);
        if (!workingDir.exists()) {
            workingDir.mkdirs();
        } else if (!bouncedVM || DUnitLauncher.MAKE_NEW_WORKING_DIRS) {
            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(version, vmNum, holder, process.getErrorStream(), System.err);
        linkStreams(version, vmNum, holder, process.getInputStream(), System.out);
    }

    public void validateVersion(String version) {
        if (!versionManager.isValidVersion(version)) {
            throw new IllegalArgumentException("Version " + version + " is not configured for use");
        }
    }

    public static File getVMDir(String version, int vmNum) {
        return new File(DUnitLauncher.DUNIT_DIR, VM.getVMName(VersionManager.CURRENT_VERSION, 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(String version, 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(version, vmNum, true);
        } catch (InterruptedException | IOException e) {
            throw new RuntimeException("Unable to restart VM " + vmNum, e);
        }
    }

    private void linkStreams(final String version, 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 = "[" + VM.getVMName(version, 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 version) {
        String cmd = System.getProperty("java.home") + File.separator + "bin" + File.separator + "java";
        String dunitClasspath = System.getProperty("java.class.path");
        String classPath;
        if (!VersionManager.isCurrentVersion(version)) {
            classPath = versionManager.getClasspath(version) + File.pathSeparator + dunitClasspath;
        } else {
            classPath = dunitClasspath;
        }

        // 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"; // ignore version
        ArrayList<String> cmds = new ArrayList<String>();
        cmds.add(cmd);
        cmds.add("-classpath");
        classPath = removeJREJars(classPath);
        cmds.add(classPath);
        cmds.add("-D" + DUnitLauncher.RMI_PORT_PARAM + "=" + namingPort);
        cmds.add("-D" + DUnitLauncher.VM_NUM_PARAM + "=" + vmNum);
        cmds.add("-D" + DUnitLauncher.VM_VERSION_PARAM + "=" + version);
        cmds.add("-D" + DUnitLauncher.WORKSPACE_DIR_PARAM + "=" + new File(".").getAbsolutePath());
        if (vmNum >= 0) { // let the locator print a banner
            if (version.equals(VersionManager.CURRENT_VERSION)) { // enable the banner for older versions
                cmds.add("-D" + InternalLocator.INHIBIT_DM_BANNER + "=true");
            }
        } else {
            // most distributed unit tests were written under the assumption that network partition
            // detection is disabled, so we turn it off in the locator. Tests for network partition
            // detection should create a separate locator that has it enabled
            cmds.add("-D" + DistributionConfig.GEMFIRE_PREFIX + ENABLE_NETWORK_PARTITION_DETECTION + "=false");
        }
        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("-D" + DistributionConfig.RESTRICT_MEMBERSHIP_PORT_RANGE + "=true");
        cmds.add("-ea");
        cmds.add("-XX:MetaspaceSize=512m");
        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;
    }

    private String removeJREJars(String classpath) {
        String[] jars = classpath.split(File.pathSeparator);
        StringBuilder sb = new StringBuilder(classpath.length());
        String jreLib = File.separator + "jre" + File.separator + "lib" + File.separator;
        Boolean firstjar = true;
        for (String jar : jars) {
            if (!jar.contains(jreLib)) {
                if (!firstjar) {
                    sb.append(File.pathSeparator);
                }
                sb.append(jar);
                firstjar = false;
            }
        }
        return sb.toString();
    }

    /**
     * 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;
    }

    public 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 {
        return getStub(VersionManager.CURRENT_VERSION, i);
    }

    public RemoteDUnitVMIF getStub(String version, int i)
            throws AccessException, RemoteException, NotBoundException, InterruptedException {
        waitForVMs(DUnitLauncher.STARTUP_TIMEOUT);
        return (RemoteDUnitVMIF) registry.lookup("vm" + i);
    }

    private static class VersionedVMNumber {
        String version;
        int number;

        VersionedVMNumber(String version, int number) {
            this.version = version;
            this.number = number;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }
            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            VersionedVMNumber that = (VersionedVMNumber) o;

            if (number != that.number) {
                return false;
            }
            return version.equals(that.version);
        }

        @Override
        public int hashCode() {
            int result = version.hashCode();
            result = 31 * result + number;
            return result;
        }
    }
}