http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/FileProcessController.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/FileProcessController.java b/geode-core/src/main/java/org/apache/geode/internal/process/FileProcessController.java index c8ec49d..062cfb6 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/FileProcessController.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/FileProcessController.java @@ -14,13 +14,10 @@ */ package org.apache.geode.internal.process; -import org.apache.commons.lang.StringUtils; -import org.apache.geode.distributed.internal.DistributionConfig; -import org.apache.geode.internal.i18n.LocalizedStrings; -import org.apache.geode.internal.logging.LogService; -import org.apache.geode.internal.process.ControlFileWatchdog.ControlRequestHandler; -import org.apache.geode.lang.AttachAPINotFoundException; -import org.apache.logging.log4j.Logger; +import static java.util.concurrent.TimeUnit.MILLISECONDS; +import static org.apache.commons.lang.StringUtils.isBlank; +import static org.apache.commons.lang.Validate.isTrue; +import static org.apache.commons.lang.Validate.notNull; import java.io.BufferedReader; import java.io.File; @@ -30,70 +27,72 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicReference; +import org.apache.geode.internal.i18n.LocalizedStrings; +import org.apache.geode.internal.process.ControlFileWatchdog.ControlRequestHandler; +import org.apache.geode.lang.AttachAPINotFoundException; + /** * Controls a {@link ControllableProcess} using files to communicate between processes. * * @since GemFire 8.0 */ -public class FileProcessController implements ProcessController { - private static final Logger logger = LogService.getLogger(); +class FileProcessController implements ProcessController { - public static final String STATUS_TIMEOUT_PROPERTY = - DistributionConfig.GEMFIRE_PREFIX + "FileProcessController.STATUS_TIMEOUT"; + static final long DEFAULT_STATUS_TIMEOUT_MILLIS = 60 * 1000; private final long statusTimeoutMillis; - private final FileControllerParameters arguments; + private final FileControllerParameters parameters; private final int pid; /** * Constructs an instance for controlling a local process. * - * @param arguments details about the controllable process + * @param parameters details about the controllable process * @param pid process id identifying the process to control * * @throws IllegalArgumentException if pid is not a positive integer */ - public FileProcessController(final FileControllerParameters arguments, final int pid) { - this(arguments, pid, Long.getLong(STATUS_TIMEOUT_PROPERTY, 60 * 1000), TimeUnit.MILLISECONDS); + FileProcessController(final FileControllerParameters parameters, final int pid) { + this(parameters, pid, DEFAULT_STATUS_TIMEOUT_MILLIS, MILLISECONDS); } /** * Constructs an instance for controlling a local process. * - * @param arguments details about the controllable process + * @param parameters details about the controllable process * @param pid process id identifying the process to control * @param timeout the timeout that operations must complete within * @param units the units of the timeout * * @throws IllegalArgumentException if pid is not a positive integer */ - public FileProcessController(final FileControllerParameters arguments, final int pid, + FileProcessController(final FileControllerParameters parameters, final int pid, final long timeout, final TimeUnit units) { - if (pid < 1) { - throw new IllegalArgumentException("Invalid pid '" + pid + "' specified"); - } + notNull(parameters, "Invalid parameters '" + parameters + "' specified"); + isTrue(pid > 0, "Invalid pid '" + pid + "' specified"); + isTrue(timeout >= 0, "Invalid timeout '" + timeout + "' specified"); + notNull(units, "Invalid units '" + units + "' specified"); + this.pid = pid; - this.arguments = arguments; + this.parameters = parameters; this.statusTimeoutMillis = units.toMillis(timeout); } @Override public int getProcessId() { - return this.pid; + return pid; } @Override public String status() throws UnableToControlProcessException, IOException, InterruptedException, TimeoutException { - return status(this.arguments.getWorkingDirectory(), - this.arguments.getProcessType().getStatusRequestFileName(), - this.arguments.getProcessType().getStatusFileName()); + return status(parameters.getDirectory(), parameters.getProcessType().getStatusRequestFileName(), + parameters.getProcessType().getStatusFileName()); } @Override public void stop() throws UnableToControlProcessException, IOException { - stop(this.arguments.getWorkingDirectory(), - this.arguments.getProcessType().getStopRequestFileName()); + stop(parameters.getDirectory(), parameters.getProcessType().getStopRequestFileName()); } @Override @@ -102,8 +101,12 @@ public class FileProcessController implements ProcessController { LocalizedStrings.Launcher_ATTACH_API_NOT_FOUND_ERROR_MESSAGE.toLocalizedString()); } + long getStatusTimeoutMillis() { + return statusTimeoutMillis; + } + private void stop(final File workingDir, final String stopRequestFileName) throws IOException { - final File stopRequestFile = new File(workingDir, stopRequestFileName); + File stopRequestFile = new File(workingDir, stopRequestFileName); if (!stopRequestFile.exists()) { stopRequestFile.createNewFile(); } @@ -112,56 +115,40 @@ public class FileProcessController implements ProcessController { private String status(final File workingDir, final String statusRequestFileName, final String statusFileName) throws IOException, InterruptedException, TimeoutException { // monitor for statusFile - final File statusFile = new File(workingDir, statusFileName); - final AtomicReference<String> statusRef = new AtomicReference<>(); - - final ControlRequestHandler statusHandler = new ControlRequestHandler() { - @Override - public void handleRequest() throws IOException { - // read the statusFile - final BufferedReader reader = new BufferedReader(new FileReader(statusFile)); - final StringBuilder lines = new StringBuilder(); - try { - String line = null; - while ((line = reader.readLine()) != null) { - lines.append(line); - } - } finally { - statusRef.set(lines.toString()); - reader.close(); - } + File statusFile = new File(workingDir, statusFileName); + AtomicReference<String> statusRef = new AtomicReference<>(); + + ControlRequestHandler statusHandler = () -> { + // read the statusFile + StringBuilder lines = new StringBuilder(); + try (BufferedReader reader = new BufferedReader(new FileReader(statusFile))) { + reader.lines().forEach(lines::append); + } finally { + statusRef.set(lines.toString()); } }; - final ControlFileWatchdog statusFileWatchdog = + ControlFileWatchdog statusFileWatchdog = new ControlFileWatchdog(workingDir, statusFileName, statusHandler, true); statusFileWatchdog.start(); - final File statusRequestFile = new File(workingDir, statusRequestFileName); + File statusRequestFile = new File(workingDir, statusRequestFileName); if (!statusRequestFile.exists()) { statusRequestFile.createNewFile(); } // if timeout invoke stop and then throw TimeoutException - final long start = System.currentTimeMillis(); + long start = System.currentTimeMillis(); while (statusFileWatchdog.isAlive()) { Thread.sleep(10); - if (System.currentTimeMillis() >= start + this.statusTimeoutMillis) { - final TimeoutException te = - new TimeoutException("Timed out waiting for process to create " + statusFile); - try { - statusFileWatchdog.stop(); - } catch (InterruptedException e) { - logger.info("Interrupted while stopping status file watchdog.", e); - } catch (RuntimeException e) { - logger.info("Unexpected failure while stopping status file watchdog.", e); - } - throw te; + if (System.currentTimeMillis() >= start + statusTimeoutMillis) { + statusFileWatchdog.stop(); + throw new TimeoutException("Timed out waiting for process to create " + statusFile); } } - final String lines = statusRef.get(); - if (StringUtils.isBlank(lines)) { + String lines = statusRef.get(); + if (isBlank(lines)) { throw new IllegalStateException("Failed to read status file"); } return lines;
http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessController.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessController.java b/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessController.java deleted file mode 100755 index fbea19e..0000000 --- a/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessController.java +++ /dev/null @@ -1,478 +0,0 @@ -/* - * 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.internal.process; - -import java.io.BufferedReader; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.FileReader; -import java.io.FilenameFilter; -import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; -import java.util.Properties; -import java.util.Set; -import javax.management.InstanceNotFoundException; -import javax.management.MBeanException; -import javax.management.MBeanServerConnection; -import javax.management.ObjectName; -import javax.management.Query; -import javax.management.QueryExp; -import javax.management.ReflectionException; -import javax.management.remote.JMXConnector; -import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXServiceURL; - -import org.apache.geode.internal.util.IOUtils; -import com.sun.tools.attach.AgentInitializationException; -import com.sun.tools.attach.AgentLoadException; -import com.sun.tools.attach.AttachNotSupportedException; -import com.sun.tools.attach.VirtualMachine; - -/** - * Attaches to a local process to control it via JMX. - * - * @since GemFire 7.0 - * @deprecated as of 8.0 please use {@link ControllableProcess} instead - */ -public class LocalProcessController { - - /** Property name for the JMX local connector address (from sun.management.Agent) */ - private static final String LOCAL_CONNECTOR_ADDRESS_PROP = - "com.sun.management.jmxremote.localConnectorAddress"; - - private final int pid; - - protected JMXConnector jmxc; - protected MBeanServerConnection server; - - /** - * Constructs an instance for controlling a local process. - * - * @param pid process id identifying the process to attach to - * - * @throws IllegalArgumentException if pid is not a positive integer - */ - public LocalProcessController(final int pid) { - if (pid < 1) { - throw new IllegalArgumentException("Invalid pid '" + pid + "' specified"); - } - this.pid = pid; - } - - /** - * Constructs an instance for controlling a local process. - * - * @param pidFile file containing the pid of the process to attach to - * - * @throws FileNotFoundException if the specified file name is not found within the directory - * @throws IOException if unable to read from the specified file - * @throws IllegalArgumentException if the pid in the pidFile is not a positive integer - * @throws NumberFormatException if the pid file does not contain a parsable integer - */ - public LocalProcessController(final File pidFile) throws IOException { - this(readPid(pidFile)); - } - - /** - * Constructs an instance for controlling a local process. - * - * @param directory directory containing a file of name pidFileName - * @param pidFilename name of the file containing the pid of the process to attach to - * - * @throws FileNotFoundException if the specified file name is not found within the directory - * @throws IOException if an I/O error occurs - * @throws IllegalArgumentException if the pid in the pidFile is not a positive integer - * @throws IllegalStateException if dir is not an existing directory - * @throws NumberFormatException if the pid file does not contain a parsable integer - */ - public LocalProcessController(final File directory, final String pidFilename) throws IOException { - this(readPid(directory, pidFilename)); - } - - /** - * Connects to the process and tells it to shut down. - * - * @param namePattern the name pattern of the MBean to use for stopping - * @param pidAttribute the name of the MBean attribute with the process id to compare against - * @param stopMethod the name of the MBean operation to invoke - * @param attributes the names of the MBean attributes to compare with expected values - * @param values the expected values of the specified MBean attributes - * - * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector - * in the process - * @throws IOException if a communication problem occurred when talking to the MBean server - * @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason - * @throws PidUnavailableException if parsing the pid from the RuntimeMXBean name fails - */ - public void stop(final ObjectName namePattern, final String pidAttribute, final String stopMethod, - final String[] attributes, final Object[] values) throws ConnectionFailedException, - IOException, MBeanInvocationFailedException, PidUnavailableException { - invokeOperationOnTargetMBean(namePattern, pidAttribute, stopMethod, attributes, values); - } - - /** - * Connects to the process and acquires its status. - * - * @param namePattern the name pattern of the MBean to use for stopping - * @param pidAttribute the name of the MBean attribute with the process id to compare against - * @param statusMethod the name of the MBean operation to invoke - * @param attributes the names of the MBean attributes to compare with expected values - * @param values the expected values of the specified MBean attributes - * - * @return string describing the status of the process - * - * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector - * in the process - * @throws IOException if a communication problem occurred when talking to the MBean server - * @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason - * @throws PidUnavailableException if parsing the pid from the RuntimeMXBean name fails - */ - public String status(final ObjectName namePattern, final String pidAttribute, - final String statusMethod, final String[] attributes, final Object[] values) - throws ConnectionFailedException, IOException, MBeanInvocationFailedException, - PidUnavailableException { - return invokeOperationOnTargetMBean(namePattern, pidAttribute, statusMethod, attributes, values) - .toString(); - } - - /** - * Connects to the process and use its MBean to stop it. - * - * @param namePattern the name pattern of the MBean to use for stopping - * @param pidAttribute the name of the MBean attribute with the process id to compare against - * @param methodName the name of the MBean operation to invoke - * @param attributes the names of the MBean attributes to compare with expected values - * @param values the expected values of the specified MBean attributes - * - * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector - * in the process - * @throws IOException if a communication problem occurred when talking to the MBean server - * @throws MBeanInvocationFailedException if failed to invoke stop on the MBean for any reason - * @throws PidUnavailableException if parsing the pid from the RuntimeMXBean name fails - */ - private Object invokeOperationOnTargetMBean(final ObjectName namePattern, - final String pidAttribute, final String methodName, final String[] attributes, - final Object[] values) throws ConnectionFailedException, IOException, - MBeanInvocationFailedException, PidUnavailableException { - ObjectName objectName = namePattern; - connect(); - try { - final QueryExp constraint = buildQueryExp(pidAttribute, attributes, values); - final Set<ObjectName> mbeanNames = this.server.queryNames(namePattern, constraint); - - if (mbeanNames.isEmpty()) { - throw new MBeanInvocationFailedException("Failed to find mbean matching '" + namePattern - + "' with attribute '" + pidAttribute + "' of value '" + this.pid + "'"); - } - if (mbeanNames.size() > 1) { - throw new MBeanInvocationFailedException("Found more than one mbean matching '" - + namePattern + "' with attribute '" + pidAttribute + "' of value '" + this.pid + "'"); - } - - objectName = mbeanNames.iterator().next(); - return invoke(objectName, methodName); - } catch (InstanceNotFoundException e) { - throw new MBeanInvocationFailedException( - "Failed to invoke " + methodName + " on " + objectName, e); - } catch (MBeanException e) { - throw new MBeanInvocationFailedException( - "Failed to invoke " + methodName + " on " + objectName, e); - } catch (ReflectionException e) { - throw new MBeanInvocationFailedException( - "Failed to invoke " + methodName + " on " + objectName, e); - } finally { - disconnect(); - } - } - - /** - * Returns the process id (pid) of the process. - * - * @return the process id (pid) of the process - */ - public int getProcessId() { - return this.pid; - } - - /** - * Connects to the JMX agent in the local process. - * - * @throws ConnectionFailedException if there was a failure to connect to the local JMX connector - * in the process - * @throws IOException if the JDK management agent cannot be found and loaded - */ - void connect() throws ConnectionFailedException, IOException { - try { - final JMXServiceURL jmxUrl = getJMXServiceURL(); - this.jmxc = JMXConnectorFactory.connect(jmxUrl); - this.server = this.jmxc.getMBeanServerConnection(); - } catch (AttachNotSupportedException e) { - throw new ConnectionFailedException("Failed to connect to process '" + this.pid + "'", e); - } - } - - /** - * Disconnects from the JMX agent in the local process. - */ - void disconnect() { - this.server = null; - if (this.jmxc != null) { - try { - this.jmxc.close(); - } catch (IOException e) { - // ignore - } - } - this.jmxc = null; - } - - /** - * Ensures that the other process identifies itself by the same pid used by this stopper to - * connect to that process. NOT USED EXCEPT IN TEST. - * - * @return true if the pid matches - * - * @throws IllegalStateException if the other process identifies itself by a different pid - * @throws IOException if a communication problem occurred when accessing the - * MBeanServerConnection - * @throws PidUnavailableException if parsing the pid from the RuntimeMXBean name fails - */ - boolean checkPidMatches() throws IllegalStateException, IOException, PidUnavailableException { - final RuntimeMXBean proxy = ManagementFactory.newPlatformMXBeanProxy(this.server, - ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class); - final int remotePid = ProcessUtils.identifyPid(proxy.getName()); - if (remotePid != this.pid) { - throw new IllegalStateException( - "Process has different pid '" + remotePid + "' than expected pid '" + this.pid + "'"); - } else { - return true; - } - } - - /** - * Uses the Attach API to connect to the local process and ensures that it has loaded the JMX - * management agent. The JMXServiceURL identifying the local connector address for the JMX agent - * in the process is returned. - * - * @return the address of the JMX API connector server for connecting to the local process - * - * @throws AttachNotSupportedException if unable to use the Attach API to connect to the process - * @throws IOException if the JDK management agent cannot be found and loaded - */ - private JMXServiceURL getJMXServiceURL() throws AttachNotSupportedException, IOException { - String connectorAddress = null; - final VirtualMachine vm = VirtualMachine.attach(String.valueOf(this.pid)); - try { - Properties agentProps = vm.getAgentProperties(); - connectorAddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); - - if (connectorAddress == null) { - // need to load the management-agent and get the address - - final String javaHome = vm.getSystemProperties().getProperty("java.home"); - - // assume java.home is JDK and look in JRE for agent - String managementAgentPath = javaHome + File.separator + "jre" + File.separator + "lib" - + File.separator + "management-agent.jar"; - File managementAgent = new File(managementAgentPath); - if (!managementAgent.exists()) { - // assume java.home is JRE and look in lib for agent - managementAgentPath = - javaHome + File.separator + "lib" + File.separator + "management-agent.jar"; - managementAgent = new File(managementAgentPath); - if (!managementAgent.exists()) { - throw new IOException("JDK management agent not found"); - } - } - - // attempt to load the management agent - managementAgentPath = managementAgent.getCanonicalPath(); - try { - vm.loadAgent(managementAgentPath, "com.sun.management.jmxremote"); - } catch (AgentLoadException e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } catch (AgentInitializationException e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } - - // get the connector address - agentProps = vm.getAgentProperties(); - connectorAddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); - } - } finally { - vm.detach(); - } - - if (connectorAddress == null) { - // should never reach here - throw new IOException("Failed to find address to attach to process"); - } - - return new JMXServiceURL(connectorAddress); - } - - /** - * Builds the QueryExp used to identify the target MBean. - * - * @param pidAttribute the name of the MBean attribute with the process id to compare against - * @param attributes the names of additional MBean attributes to compare with expected values - * @param values the expected values of the specified MBean attributes - * - * @return the main QueryExp for matching the target MBean - */ - private QueryExp buildQueryExp(final String pidAttribute, final String[] attributes, - final Object[] values) { - final QueryExp optionalAttributes = buildOptionalQueryExp(attributes, values); - QueryExp constraint; - if (optionalAttributes != null) { - constraint = - Query.and(optionalAttributes, Query.eq(Query.attr(pidAttribute), Query.value(this.pid))); - } else { - constraint = Query.eq(Query.attr(pidAttribute), Query.value(this.pid)); - } - return constraint; - } - - /** - * Builds an optional QueryExp to aid in matching the correct MBean using additional attributes - * with the specified values. Returns null if no attributes and values were specified during - * construction. - * - * @param attributes the names of additional MBean attributes to compare with expected values - * @param values the expected values of the specified MBean attributes - * - * @return optional QueryExp to aid in matching the correct MBean - */ - private QueryExp buildOptionalQueryExp(final String[] attributes, final Object[] values) { - QueryExp queryExp = null; - for (int i = 0; i < attributes.length; i++) { - if (values[i] instanceof Boolean) { - if (queryExp == null) { - queryExp = Query.eq(Query.attr(attributes[i]), Query.value(((Boolean) values[i]))); - } else { - queryExp = Query.and(queryExp, - Query.eq(Query.attr(attributes[i]), Query.value(((Boolean) values[i])))); - } - } else if (values[i] instanceof Number) { - if (queryExp == null) { - queryExp = Query.eq(Query.attr(attributes[i]), Query.value((Number) values[i])); - } else { - queryExp = Query.and(queryExp, - Query.eq(Query.attr(attributes[i]), Query.value((Number) values[i]))); - } - } else if (values[i] instanceof String) { - if (queryExp == null) { - queryExp = Query.eq(Query.attr(attributes[i]), Query.value((String) values[i])); - } else { - queryExp = Query.and(queryExp, - Query.eq(Query.attr(attributes[i]), Query.value((String) values[i]))); - } - } - } - return queryExp; - } - - /** - * Invokes an operation on the specified MBean. - * - * @param objectName identifies the MBean - * @param method the name of the operation method invoke - * - * @return the result of invoking the operation on the MBean specified or null - * - * @throws InstanceNotFoundException if the specified MBean is not registered in the MBean server - * @throws IOException if a communication problem occurred when talking to the MBean server - * @throws MBeanException if the MBean operation throws an exception - * @throws ReflectionException if the MBean does not have the specified operation - */ - private Object invoke(final ObjectName objectName, final String method) - throws InstanceNotFoundException, IOException, MBeanException, ReflectionException { - return this.server.invoke(objectName, method, new Object[] {}, new String[] {}); - } - - /** - * Reads in the pid from the specified file. - * - * @param pidFile the file containing the pid of the process to stop - * - * @return the process id (pid) contained within the pidFile - * - * @throws IllegalArgumentException if the pid in the pidFile is not a positive integer - * @throws IOException if unable to read from the specified file - * @throws NumberFormatException if the pid file does not contain a parsable integer - */ - private static int readPid(final File pidFile) throws IOException { - BufferedReader fileReader = null; - String pidValue = null; - - try { - fileReader = new BufferedReader(new FileReader(pidFile)); - pidValue = fileReader.readLine(); - - final int pid = Integer.parseInt(pidValue); - - if (pid < 1) { - throw new IllegalArgumentException("Invalid pid '" + pid + "' found in " + pidFile); - } - - return pid; - } catch (NumberFormatException e) { - throw new IllegalArgumentException("Invalid pid '" + pidValue + "' found in " + pidFile); - } finally { - IOUtils.close(fileReader); - } - } - - /** - * Reads in the pid from the named file contained within the specified directory. - * - * @param directory directory containing a file of name pidFileName - * @param pidFilename name of the file containing the pid of the process to stop - * - * @return the process id (pid) contained within the pidFile - * - * @throws FileNotFoundException if the specified file name is not found within the directory - * @throws IllegalArgumentException if the pid in the pidFile is not a positive integer - * @throws IllegalStateException if dir is not an existing directory - * @throws IOException if an I/O error occurs - * @throws NumberFormatException if the pid file does not contain a parsable integer - */ - private static int readPid(final File directory, final String pidFilename) throws IOException { - if (!directory.isDirectory() && directory.exists()) { - throw new IllegalArgumentException( - "Argument '" + directory + "' must be an existing directory!"); - } - - final File[] files = directory.listFiles(new FilenameFilter() { - @Override - public boolean accept(File file, String filename) { - return filename.equals(pidFilename); - } - }); - - if (files.length == 0) { - throw new FileNotFoundException( - "Unable to find PID file '" + pidFilename + "' in directory " + directory); - } - - return readPid(files[0]); - } - -} http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessLauncher.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessLauncher.java b/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessLauncher.java index 04809c2..598a75e 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessLauncher.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/LocalProcessLauncher.java @@ -14,16 +14,21 @@ */ package org.apache.geode.internal.process; -import org.apache.geode.distributed.internal.DistributionConfig; +import static org.apache.commons.lang.Validate.notNull; +import static org.apache.geode.internal.process.ProcessUtils.identifyPid; import java.io.File; import java.io.FileWriter; import java.io.IOException; +import org.apache.geode.distributed.internal.DistributionConfig; + /** * Creates a pid file and writes the process id to the pid file. - * <p/> + * + * <p> * Related articles and libraries: + * * <ul> * <li>http://barelyenough.org/blog/2005/03/java-daemon/ * <li>http://stackoverflow.com/questions/534648/how-to-daemonize-a-java-program @@ -31,12 +36,13 @@ import java.io.IOException; * <li>http://wrapper.tanukisoftware.com/ * <li>http://weblogs.java.net/blog/kohsuke/archive/2009/01/writing_a_unix.html * <li>http://www.enderunix.org/docs/eng/daemon.php + * </ul> * * @since GemFire 7.0 */ -public class LocalProcessLauncher { +class LocalProcessLauncher { - public static final String PROPERTY_IGNORE_IS_PID_ALIVE = + static final String PROPERTY_IGNORE_IS_PID_ALIVE = DistributionConfig.GEMFIRE_PREFIX + "test.LocalProcessLauncher.ignoreIsPidAlive"; private final int pid; @@ -55,9 +61,11 @@ public class LocalProcessLauncher { * * @see java.lang.management.RuntimeMXBean */ - public LocalProcessLauncher(final File pidFile, final boolean force) + LocalProcessLauncher(final File pidFile, final boolean force) throws FileAlreadyExistsException, IOException, PidUnavailableException { - this.pid = ProcessUtils.identifyPid(); + notNull(pidFile, "Invalid pidFile '" + pidFile + "' specified"); + + this.pid = identifyPid(); this.pidFile = pidFile; writePid(force); } @@ -67,8 +75,8 @@ public class LocalProcessLauncher { * * @return the process id (pid) */ - public int getPid() { - return this.pid; + int getPid() { + return pid; } /** @@ -76,15 +84,28 @@ public class LocalProcessLauncher { * * @return the pid file */ - public File getPidFile() { - return this.pidFile; + File getPidFile() { + return pidFile; } /** * Delete the pid file now. {@link java.io.File#deleteOnExit()} is set on the pid file. + * */ void close() { - this.pidFile.delete(); + pidFile.delete(); + } + + /** + * Delete the pid file now. {@link java.io.File#deleteOnExit()} is set on the pid file. + * + * @param deletePidFileOnClose if true then the pid file will be deleted now instead of during JVM + * exit + */ + void close(final boolean deletePidFileOnClose) { + if (deletePidFileOnClose) { + pidFile.delete(); + } } /** @@ -96,30 +117,44 @@ public class LocalProcessLauncher { * @throws IOException if unable to create or write to the file */ private void writePid(final boolean force) throws FileAlreadyExistsException, IOException { - final boolean created = this.pidFile.createNewFile(); - if (!created && !force) { - int otherPid = 0; - try { - otherPid = ProcessUtils.readPid(this.pidFile); - } catch (IOException e) { - // suppress - } catch (NumberFormatException e) { - // suppress - } - boolean ignorePidFile = false; - if (otherPid != 0 && !ignoreIsPidAlive()) { - ignorePidFile = !ProcessUtils.isProcessAlive(otherPid); - } - if (!ignorePidFile) { - throw new FileAlreadyExistsException("Pid file already exists: " + this.pidFile + " for " - + (otherPid > 0 ? "process " + otherPid : "unknown process")); + if (pidFile.exists()) { + if (!force) { + checkOtherPid(readOtherPid()); } + pidFile.delete(); + } + + File tempPidFile = new File(pidFile.getParent(), pidFile.getName() + ".tmp"); + tempPidFile.createNewFile(); + + try (FileWriter writer = new FileWriter(tempPidFile)) { + writer.write(String.valueOf(pid)); + writer.flush(); } - this.pidFile.deleteOnExit(); - final FileWriter writer = new FileWriter(this.pidFile); - writer.write(String.valueOf(this.pid)); - writer.flush(); - writer.close(); + + tempPidFile.renameTo(pidFile); + pidFile.deleteOnExit(); + } + + private int readOtherPid() { + int otherPid = 0; + try { + otherPid = ProcessUtils.readPid(pidFile); + } catch (NumberFormatException | IOException ignore) { + // suppress + } + return otherPid; + } + + private void checkOtherPid(final int otherPid) throws FileAlreadyExistsException { + if (ignoreIsPidAlive() || otherPid != 0 && isProcessAlive(otherPid)) { + throw new FileAlreadyExistsException("Pid file already exists: " + pidFile + " for " + + (otherPid > 0 ? "process " + otherPid : "unknown process")); + } + } + + private boolean isProcessAlive(final int pid) { + return ignoreIsPidAlive() || ProcessUtils.isProcessAlive(pid); } private static boolean ignoreIsPidAlive() { http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/MBeanControllerParameters.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/MBeanControllerParameters.java b/geode-core/src/main/java/org/apache/geode/internal/process/MBeanControllerParameters.java index 857c52d..2ea0fa4 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/MBeanControllerParameters.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/MBeanControllerParameters.java @@ -25,15 +25,16 @@ import org.apache.geode.internal.process.ProcessController.Arguments; * @since GemFire 8.0 */ interface MBeanControllerParameters extends Arguments { - public ObjectName getNamePattern(); - public String getPidAttribute(); + ObjectName getNamePattern(); - public String getStatusMethod(); + String getPidAttribute(); - public String getStopMethod(); + String getStatusMethod(); - public String[] getAttributes(); + String getStopMethod(); - public Object[] getValues(); + String[] getAttributes(); + + Object[] getValues(); } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/MBeanInvocationFailedException.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/MBeanInvocationFailedException.java b/geode-core/src/main/java/org/apache/geode/internal/process/MBeanInvocationFailedException.java index 724a4d7..d4ba3ec 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/MBeanInvocationFailedException.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/MBeanInvocationFailedException.java @@ -23,23 +23,23 @@ public class MBeanInvocationFailedException extends Exception { private static final long serialVersionUID = 7991096466859690801L; /** - * Creates a new <code>MBeanInvocationFailedException</code>. + * Creates a new {@code MBeanInvocationFailedException}. */ public MBeanInvocationFailedException(final String message) { super(message); } /** - * Creates a new <code>MBeanInvocationFailedException</code> that was caused by a given exception + * Creates a new {@code MBeanInvocationFailedException} that was caused by a given exception */ - public MBeanInvocationFailedException(final String message, final Throwable thr) { - super(message, thr); + public MBeanInvocationFailedException(final String message, final Throwable cause) { + super(message, cause); } /** - * Creates a new <code>MBeanInvocationFailedException</code> that was caused by a given exception + * Creates a new {@code MBeanInvocationFailedException} that was caused by a given exception */ - public MBeanInvocationFailedException(final Throwable thr) { - super(thr.getMessage(), thr); + public MBeanInvocationFailedException(final Throwable cause) { + super(cause.getMessage(), cause); } } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/MBeanProcessController.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/MBeanProcessController.java b/geode-core/src/main/java/org/apache/geode/internal/process/MBeanProcessController.java index ea5946e..1a19719 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/MBeanProcessController.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/MBeanProcessController.java @@ -14,10 +14,11 @@ */ package org.apache.geode.internal.process; +import static org.apache.commons.lang.Validate.isTrue; +import static org.apache.commons.lang.Validate.notNull; + import java.io.File; import java.io.IOException; -import java.lang.management.ManagementFactory; -import java.lang.management.RuntimeMXBean; import java.util.Properties; import java.util.Set; @@ -42,17 +43,20 @@ import com.sun.tools.attach.VirtualMachine; * * @since GemFire 8.0 */ -public class MBeanProcessController implements ProcessController { +class MBeanProcessController implements ProcessController { /** Property name for the JMX local connector address (from sun.management.Agent) */ - private static final String LOCAL_CONNECTOR_ADDRESS_PROP = + private static final String PROPERTY_LOCAL_CONNECTOR_ADDRESS = "com.sun.management.jmxremote.localConnectorAddress"; + private static final Object[] PARAMS = {}; + private static final String[] SIGNATURE = {}; + private final MBeanControllerParameters arguments; private final int pid; - protected JMXConnector jmxc; - protected MBeanServerConnection server; + private JMXConnector jmxc; + private MBeanServerConnection server; /** * Constructs an instance for controlling a local process. @@ -61,36 +65,37 @@ public class MBeanProcessController implements ProcessController { * * @throws IllegalArgumentException if pid is not a positive integer */ - public MBeanProcessController(final MBeanControllerParameters arguments, final int pid) { - if (pid < 1) { - throw new IllegalArgumentException("Invalid pid '" + pid + "' specified"); - } + MBeanProcessController(final MBeanControllerParameters arguments, final int pid) { + notNull(arguments, "Invalid arguments '" + arguments + "' specified"); + isTrue(pid > 0, "Invalid pid '" + pid + "' specified"); + this.pid = pid; this.arguments = arguments; } @Override public int getProcessId() { - return this.pid; + return pid; } @Override public String status() throws UnableToControlProcessException, ConnectionFailedException, IOException, MBeanInvocationFailedException { - return status(this.arguments.getNamePattern(), this.arguments.getPidAttribute(), - this.arguments.getStatusMethod(), this.arguments.getAttributes(), - this.arguments.getValues()); + return status(arguments.getNamePattern(), arguments.getPidAttribute(), + arguments.getStatusMethod(), arguments.getAttributes(), arguments.getValues()); } @Override public void stop() throws UnableToControlProcessException, ConnectionFailedException, IOException, MBeanInvocationFailedException { - stop(this.arguments.getNamePattern(), this.arguments.getPidAttribute(), - this.arguments.getStopMethod(), this.arguments.getAttributes(), this.arguments.getValues()); + stop(arguments.getNamePattern(), arguments.getPidAttribute(), arguments.getStopMethod(), + arguments.getAttributes(), arguments.getValues()); } @Override - public void checkPidSupport() {} + public void checkPidSupport() { + // nothing + } /** * Connects to the process and tells it to shut down. @@ -156,27 +161,21 @@ public class MBeanProcessController implements ProcessController { ObjectName objectName = namePattern; connect(); try { - final QueryExp constraint = buildQueryExp(pidAttribute, attributes, values); - final Set<ObjectName> mbeanNames = this.server.queryNames(namePattern, constraint); + QueryExp constraint = buildQueryExp(pidAttribute, attributes, values); + Set<ObjectName> mbeanNames = server.queryNames(namePattern, constraint); if (mbeanNames.isEmpty()) { throw new MBeanInvocationFailedException("Failed to find mbean matching '" + namePattern - + "' with attribute '" + pidAttribute + "' of value '" + this.pid + "'"); + + "' with attribute '" + pidAttribute + "' of value '" + pid + "'"); } if (mbeanNames.size() > 1) { throw new MBeanInvocationFailedException("Found more than one mbean matching '" - + namePattern + "' with attribute '" + pidAttribute + "' of value '" + this.pid + "'"); + + namePattern + "' with attribute '" + pidAttribute + "' of value '" + pid + "'"); } objectName = mbeanNames.iterator().next(); return invoke(objectName, methodName); - } catch (InstanceNotFoundException e) { - throw new MBeanInvocationFailedException( - "Failed to invoke " + methodName + " on " + objectName, e); - } catch (MBeanException e) { - throw new MBeanInvocationFailedException( - "Failed to invoke " + methodName + " on " + objectName, e); - } catch (ReflectionException e) { + } catch (InstanceNotFoundException | MBeanException | ReflectionException e) { throw new MBeanInvocationFailedException( "Failed to invoke " + methodName + " on " + objectName, e); } finally { @@ -193,11 +192,11 @@ public class MBeanProcessController implements ProcessController { */ private void connect() throws ConnectionFailedException, IOException { try { - final JMXServiceURL jmxUrl = getJMXServiceURL(); - this.jmxc = JMXConnectorFactory.connect(jmxUrl); - this.server = this.jmxc.getMBeanServerConnection(); + JMXServiceURL jmxUrl = getJMXServiceURL(); + jmxc = JMXConnectorFactory.connect(jmxUrl); + server = jmxc.getMBeanServerConnection(); } catch (AttachNotSupportedException e) { - throw new ConnectionFailedException("Failed to connect to process '" + this.pid + "'", e); + throw new ConnectionFailedException("Failed to connect to process '" + pid + "'", e); } } @@ -205,38 +204,15 @@ public class MBeanProcessController implements ProcessController { * Disconnects from the JMX agent in the local process. */ private void disconnect() { - this.server = null; - if (this.jmxc != null) { + server = null; + if (jmxc != null) { try { - this.jmxc.close(); - } catch (IOException e) { + jmxc.close(); + } catch (IOException ignored) { // ignore } } - this.jmxc = null; - } - - /** - * Ensures that the other process identifies itself by the same pid used by this stopper to - * connect to that process. NOT USED EXCEPT IN TEST. - * - * @return true if the pid matches - * - * @throws IllegalStateException if the other process identifies itself by a different pid - * @throws IOException if a communication problem occurred when accessing the - * MBeanServerConnection - * @throws PidUnavailableException if parsing the pid from the RuntimeMXBean name fails - */ - boolean checkPidMatches() throws IllegalStateException, IOException, PidUnavailableException { - final RuntimeMXBean proxy = ManagementFactory.newPlatformMXBeanProxy(this.server, - ManagementFactory.RUNTIME_MXBEAN_NAME, RuntimeMXBean.class); - final int remotePid = ProcessUtils.identifyPid(proxy.getName()); - if (remotePid != this.pid) { - throw new IllegalStateException( - "Process has different pid '" + remotePid + "' than expected pid '" + this.pid + "'"); - } else { - return true; - } + jmxc = null; } /** @@ -250,16 +226,16 @@ public class MBeanProcessController implements ProcessController { * @throws IOException if the JDK management agent cannot be found and loaded */ private JMXServiceURL getJMXServiceURL() throws AttachNotSupportedException, IOException { - String connectorAddress = null; - final VirtualMachine vm = VirtualMachine.attach(String.valueOf(this.pid)); + String connectorAddress; + VirtualMachine vm = VirtualMachine.attach(String.valueOf(pid)); try { Properties agentProps = vm.getAgentProperties(); - connectorAddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); + connectorAddress = agentProps.getProperty(PROPERTY_LOCAL_CONNECTOR_ADDRESS); if (connectorAddress == null) { // need to load the management-agent and get the address - final String javaHome = vm.getSystemProperties().getProperty("java.home"); + String javaHome = vm.getSystemProperties().getProperty("java.home"); // assume java.home is JDK and look in JRE for agent String managementAgentPath = javaHome + File.separator + "jre" + File.separator + "lib" @@ -279,19 +255,13 @@ public class MBeanProcessController implements ProcessController { managementAgentPath = managementAgent.getCanonicalPath(); try { vm.loadAgent(managementAgentPath, "com.sun.management.jmxremote"); - } catch (AgentLoadException e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; - } catch (AgentInitializationException e) { - IOException ioe = new IOException(e.getMessage()); - ioe.initCause(e); - throw ioe; + } catch (AgentLoadException | AgentInitializationException e) { + throw new IOException(e); } // get the connector address agentProps = vm.getAgentProperties(); - connectorAddress = (String) agentProps.get(LOCAL_CONNECTOR_ADDRESS_PROP); + connectorAddress = agentProps.getProperty(PROPERTY_LOCAL_CONNECTOR_ADDRESS); } } finally { vm.detach(); @@ -316,13 +286,13 @@ public class MBeanProcessController implements ProcessController { */ private QueryExp buildQueryExp(final String pidAttribute, final String[] attributes, final Object[] values) { - final QueryExp optionalAttributes = buildOptionalQueryExp(attributes, values); + QueryExp optionalAttributes = buildOptionalQueryExp(attributes, values); QueryExp constraint; if (optionalAttributes != null) { constraint = - Query.and(optionalAttributes, Query.eq(Query.attr(pidAttribute), Query.value(this.pid))); + Query.and(optionalAttributes, Query.eq(Query.attr(pidAttribute), Query.value(pid))); } else { - constraint = Query.eq(Query.attr(pidAttribute), Query.value(this.pid)); + constraint = Query.eq(Query.attr(pidAttribute), Query.value(pid)); } return constraint; } @@ -342,10 +312,10 @@ public class MBeanProcessController implements ProcessController { for (int i = 0; i < attributes.length; i++) { if (values[i] instanceof Boolean) { if (queryExp == null) { - queryExp = Query.eq(Query.attr(attributes[i]), Query.value(((Boolean) values[i]))); + queryExp = Query.eq(Query.attr(attributes[i]), Query.value((Boolean) values[i])); } else { queryExp = Query.and(queryExp, - Query.eq(Query.attr(attributes[i]), Query.value(((Boolean) values[i])))); + Query.eq(Query.attr(attributes[i]), Query.value((Boolean) values[i]))); } } else if (values[i] instanceof Number) { if (queryExp == null) { @@ -381,6 +351,6 @@ public class MBeanProcessController implements ProcessController { */ private Object invoke(final ObjectName objectName, final String method) throws InstanceNotFoundException, IOException, MBeanException, ReflectionException { - return this.server.invoke(objectName, method, new Object[] {}, new String[] {}); + return server.invoke(objectName, method, PARAMS, SIGNATURE); } } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/NativeProcessUtils.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/NativeProcessUtils.java b/geode-core/src/main/java/org/apache/geode/internal/process/NativeProcessUtils.java index 34cf81f..cea73de 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/NativeProcessUtils.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/NativeProcessUtils.java @@ -14,6 +14,8 @@ */ package org.apache.geode.internal.process; +import static org.apache.commons.lang.Validate.isTrue; + import org.apache.geode.internal.process.ProcessUtils.InternalProcessUtils; import org.apache.geode.internal.shared.NativeCalls; @@ -24,17 +26,19 @@ import org.apache.geode.internal.shared.NativeCalls; */ class NativeProcessUtils implements InternalProcessUtils { - private final static NativeCalls nativeCalls = NativeCalls.getInstance(); - - NativeProcessUtils() {} + private static final NativeCalls nativeCalls = NativeCalls.getInstance(); @Override - public boolean isProcessAlive(int pid) { + public boolean isProcessAlive(final int pid) { + isTrue(pid > 0, "Invalid pid '" + pid + "' specified"); + return nativeCalls.isProcessActive(pid); } @Override - public boolean killProcess(int pid) { + public boolean killProcess(final int pid) { + isTrue(pid > 0, "Invalid pid '" + pid + "' specified"); + return nativeCalls.killProcess(pid); } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/NonBlockingProcessStreamReader.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/NonBlockingProcessStreamReader.java b/geode-core/src/main/java/org/apache/geode/internal/process/NonBlockingProcessStreamReader.java index d5c1116..9096fae 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/NonBlockingProcessStreamReader.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/NonBlockingProcessStreamReader.java @@ -30,7 +30,7 @@ import org.apache.geode.internal.util.StopWatch; * * @since GemFire 8.2 */ -public class NonBlockingProcessStreamReader extends ProcessStreamReader { +class NonBlockingProcessStreamReader extends ProcessStreamReader { private static final Logger logger = LogService.getLogger(); /** @@ -39,61 +39,65 @@ public class NonBlockingProcessStreamReader extends ProcessStreamReader { */ private final long continueReadingMillis; - protected NonBlockingProcessStreamReader(final Builder builder) { + private final StopWatch continueReading; + + private StringBuilder stringBuilder; + private int character; + private boolean ready; + + NonBlockingProcessStreamReader(final Builder builder) { super(builder); - continueReadingMillis = builder.continueReadingMillis; + + this.continueReadingMillis = builder.continueReadingMillis; + this.continueReading = new StopWatch(); + this.stringBuilder = new StringBuilder(); + this.character = 0; + this.ready = false; } @Override public void run() { - final boolean isDebugEnabled = logger.isDebugEnabled(); - if (isDebugEnabled) { - logger.debug("Running {}", this); - } - StopWatch continueReading = new StopWatch(); - BufferedReader reader = null; - try { - reader = new BufferedReader(new InputStreamReader(inputStream)); - StringBuilder sb = new StringBuilder(); - boolean ready = false; - int ch = 0; - while (ch != -1) { - while ((ready = reader.ready()) && (ch = reader.read()) != -1) { - sb.append((char) ch); - if ((char) ch == '\n') { - this.inputListener.notifyInputLine(sb.toString()); - sb = new StringBuilder(); - } - } - if (!ready) { - if (!ProcessUtils.isProcessAlive(process)) { - if (!continueReading.isRunning()) { - continueReading.start(); - } else if (continueReading.elapsedTimeMillis() > continueReadingMillis) { - return; - } - } - Thread.sleep(10); + try (BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream))) { + while (character != -1) { + readWhileReady(reader); + if (shouldTerminate()) { + break; } } } catch (IOException e) { - if (isDebugEnabled) { + if (logger.isDebugEnabled()) { logger.debug("Failure reading from buffered input stream: {}", e.getMessage(), e); } } catch (InterruptedException e) { - if (isDebugEnabled) { + if (logger.isDebugEnabled()) { logger.debug("Interrupted reading from buffered input stream: {}", e.getMessage(), e); } - } finally { - try { - reader.close(); - } catch (IOException e) { - if (isDebugEnabled) { - logger.debug("Failure closing buffered input stream reader: {}", e.getMessage(), e); - } + } + } + + private boolean shouldTerminate() throws InterruptedException { + if (!ProcessUtils.isProcessAlive(process)) { + if (!continueReading.isRunning()) { + continueReading.start(); + } else if (continueReading.elapsedTimeMillis() > continueReadingMillis) { + return true; } - if (isDebugEnabled) { - logger.debug("Terminating {}", this); + } + Thread.sleep(10); + return false; + } + + /** + * This is a hot reader while there are characters ready to read. As soon as there are no more + * characters to read, it returns and the loop invokes shouldTerminate which has a 10 millisecond + * sleep until there are more characters ready to read. + */ + private void readWhileReady(BufferedReader reader) throws IOException { + while ((ready = reader.ready()) && (character = reader.read()) != -1) { + stringBuilder.append((char) character); + if ((char) character == '\n') { + this.inputListener.notifyInputLine(stringBuilder.toString()); + stringBuilder = new StringBuilder(); } } } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/PidFile.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/PidFile.java b/geode-core/src/main/java/org/apache/geode/internal/process/PidFile.java index 291c202..0e7adf2 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/PidFile.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/PidFile.java @@ -14,17 +14,15 @@ */ package org.apache.geode.internal.process; +import static org.apache.commons.lang.Validate.isTrue; +import static org.apache.commons.lang.Validate.notEmpty; +import static org.apache.commons.lang.Validate.notNull; + import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.FileReader; -import java.io.FilenameFilter; import java.io.IOException; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import org.apache.geode.internal.util.IOUtils; -import org.apache.geode.internal.util.StopWatch; /** * File wrapper that adds support for reading process id (pid) from a pid file written to disk by @@ -34,8 +32,6 @@ import org.apache.geode.internal.util.StopWatch; */ public class PidFile { - private static final long SLEEP_INTERVAL_MILLIS = 10; - private final File pidFile; /** @@ -43,17 +39,13 @@ public class PidFile { * * @param file the file containing the pid of the process * - * @throws FileNotFoundException if the specified file name is not found within the directory + * @throws IllegalArgumentException if the specified file is null or does not exist */ - public PidFile(final File file) throws FileNotFoundException { - if (!file.exists() || !file.isFile()) { - throw new FileNotFoundException("Unable to find PID file '" + file + "'"); - } - this.pidFile = file; - } + public PidFile(final File file) { + notNull(file, "Invalid file '" + file + "' specified"); + isTrue(file.exists(), "Nonexistent file '" + file + "' specified"); - File getFile() { - return this.pidFile; + this.pidFile = file; } /** @@ -62,19 +54,19 @@ public class PidFile { * @param directory directory containing a file of name pidFileName * @param filename name of the file containing the pid of the process to stop * - * @throws FileNotFoundException if the specified file name is not found within the directory - * @throws IllegalStateException if dir is not an existing directory + * @throws FileNotFoundException if the specified filename is not found within the directory + * @throws IllegalArgumentException if directory is null, does not exist or is not a directory */ public PidFile(final File directory, final String filename) throws FileNotFoundException { - if (!directory.isDirectory() && directory.exists()) { - throw new IllegalArgumentException( - "Argument '" + directory + "' must be an existing directory!"); - } + notNull(directory, "Invalid directory '" + directory + "' specified"); + notEmpty(filename, "Invalid filename '" + filename + "' specified"); + isTrue(directory.isDirectory() && directory.exists(), + "Nonexistent directory '" + directory + "' specified"); - final File file = new File(directory, filename); + File file = new File(directory, filename); if (!file.exists() || file.isDirectory()) { throw new FileNotFoundException( - "Unable to find PID file '" + filename + "' in directory " + directory); + "Unable to find PID file '" + filename + "' in directory '" + directory + "'"); } this.pidFile = file; @@ -89,81 +81,26 @@ public class PidFile { * @throws IOException if unable to read from the specified file */ public int readPid() throws IOException { - BufferedReader fileReader = null; String pidValue = null; - - try { - fileReader = new BufferedReader(new FileReader(this.pidFile)); + try (BufferedReader fileReader = new BufferedReader(new FileReader(pidFile))) { pidValue = fileReader.readLine(); - final int pid = Integer.parseInt(pidValue); + int pid = Integer.parseInt(pidValue); if (pid < 1) { throw new IllegalArgumentException( - "Invalid pid '" + pid + "' found in " + this.pidFile.getCanonicalPath()); + "Invalid pid '" + pid + "' found in " + pidFile.getCanonicalPath()); } return pid; - } catch (NumberFormatException e) { + } catch (NumberFormatException ignored) { throw new IllegalArgumentException( - "Invalid pid '" + pidValue + "' found in " + this.pidFile.getCanonicalPath()); - } finally { - IOUtils.close(fileReader); + "Invalid pid '" + pidValue + "' found in " + pidFile.getCanonicalPath()); } } - /** - * Reads in the pid from the specified file, retrying until the specified timeout. - * - * @param timeout the maximum time to spend trying to read the pidFile - * @param unit the unit of timeout - * - * @return the process id (pid) contained within the pidFile - * - * @throws IllegalArgumentException if the pid in the pidFile is not a positive integer - * @throws IOException if unable to read from the specified file - * @throws InterruptedException if interrupted - * @throws TimeoutException if operation times out - */ - public int readPid(final long timeout, final TimeUnit unit) - throws IOException, InterruptedException, TimeoutException { - IllegalArgumentException iae = null; - IOException ioe = null; - int pid = 0; - - final long timeoutMillis = unit.toMillis(timeout); - final StopWatch stopWatch = new StopWatch(true); - - while (pid <= 0) { - try { - pid = readPid(); - } catch (IllegalArgumentException e) { - iae = e; - } catch (IOException e) { - ioe = e; - } - if (stopWatch.elapsedTimeMillis() > timeoutMillis) { - if (iae != null) { - throw new TimeoutException(iae.getMessage()); - } - if (ioe != null) { - throw new TimeoutException(ioe.getMessage()); - } - } else { - try { - Thread.sleep(SLEEP_INTERVAL_MILLIS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - if (iae != null) { - throw new InterruptedException(iae.getMessage()); - } - if (ioe != null) { - throw new InterruptedException(ioe.getMessage()); - } - throw e; - } - } - } - return pid; + File getFile() { + return pidFile; } + } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/PidUnavailableException.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/PidUnavailableException.java b/geode-core/src/main/java/org/apache/geode/internal/process/PidUnavailableException.java index 6fa1c9f..934e65c 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/PidUnavailableException.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/PidUnavailableException.java @@ -24,23 +24,23 @@ public class PidUnavailableException extends Exception { private static final long serialVersionUID = -1660269538268828059L; /** - * Creates a new <code>PidUnavailableException</code>. + * Creates a new {@code PidUnavailableException}. */ public PidUnavailableException(final String message) { super(message); } /** - * Creates a new <code>PidUnavailableException</code> that was caused by a given exception + * Creates a new {@code PidUnavailableException} that was caused by a given exception */ - public PidUnavailableException(final String message, final Throwable thr) { - super(message, thr); + public PidUnavailableException(final String message, final Throwable cause) { + super(message, cause); } /** - * Creates a new <code>PidUnavailableException</code> that was caused by a given exception + * Creates a new {@code PidUnavailableException} that was caused by a given exception */ - public PidUnavailableException(final Throwable thr) { - super(thr.getMessage(), thr); + public PidUnavailableException(final Throwable cause) { + super(cause.getMessage(), cause); } } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/ProcessController.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessController.java b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessController.java index 2aa4732..2e5cb0c 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessController.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessController.java @@ -27,19 +27,19 @@ public interface ProcessController { /** * Returns the status of a running GemFire {@link ControllableProcess}. */ - public String status() throws UnableToControlProcessException, ConnectionFailedException, - IOException, MBeanInvocationFailedException, InterruptedException, TimeoutException; + String status() throws UnableToControlProcessException, ConnectionFailedException, IOException, + MBeanInvocationFailedException, InterruptedException, TimeoutException; /** * Stops a running GemFire {@link ControllableProcess}. */ - public void stop() throws UnableToControlProcessException, ConnectionFailedException, IOException, + void stop() throws UnableToControlProcessException, ConnectionFailedException, IOException, MBeanInvocationFailedException; /** * Returns the PID of a running GemFire {@link ControllableProcess}. */ - public int getProcessId(); + int getProcessId(); /** * Checks if {@link #status} and {@link #stop} are supported if only the PID is provided. Only the @@ -48,14 +48,15 @@ public interface ProcessController { * * @throws org.apache.geode.lang.AttachAPINotFoundException if the Attach API is not found */ - public void checkPidSupport(); + void checkPidSupport(); /** * Defines the arguments that a client must provide to the ProcessController. */ - static interface Arguments { - public int getProcessId(); + interface Arguments { - public ProcessType getProcessType(); + int getProcessId(); + + ProcessType getProcessType(); } } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerFactory.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerFactory.java b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerFactory.java index 38fdcf8..ef276c3 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerFactory.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerFactory.java @@ -14,13 +14,16 @@ */ package org.apache.geode.internal.process; -import org.apache.geode.distributed.internal.DistributionConfig; +import static org.apache.commons.lang.Validate.isTrue; +import static org.apache.commons.lang.Validate.notEmpty; +import static org.apache.commons.lang.Validate.notNull; import java.io.File; import java.io.IOException; -import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import org.apache.geode.distributed.internal.DistributionConfig; + /** * Manages which implementation of {@link ProcessController} will be used and constructs the * instance. @@ -29,6 +32,9 @@ import java.util.concurrent.TimeoutException; */ public class ProcessControllerFactory { + /** + * For testing only + */ public static final String PROPERTY_DISABLE_ATTACH_API = DistributionConfig.GEMFIRE_PREFIX + "test.ProcessControllerFactory.DisableAttachApi"; @@ -38,64 +44,44 @@ public class ProcessControllerFactory { this.disableAttachApi = Boolean.getBoolean(PROPERTY_DISABLE_ATTACH_API); } - public ProcessController createProcessController(final ProcessControllerParameters arguments, + public ProcessController createProcessController(final ProcessControllerParameters parameters, final int pid) { - if (arguments == null) { - throw new NullPointerException("ProcessControllerParameters must not be null"); - } - if (pid < 1) { - throw new IllegalArgumentException("Invalid pid '" + pid + "' specified"); - } - try { - if (isAttachAPIFound()) { - return new MBeanProcessController((MBeanControllerParameters) arguments, pid); - } else { - return new FileProcessController((FileControllerParameters) arguments, pid); + notNull(parameters, "Invalid parameters '" + parameters + "' specified"); + isTrue(pid > 0, "Invalid pid '" + pid + "' specified"); + + if (isAttachAPIFound()) { + try { + return new MBeanProcessController(parameters, pid); + } catch (ExceptionInInitializerError ignore) { } - } catch (final ExceptionInInitializerError e) { - // LOGGER.warn("Attach API class not found", e); } - return null; + return new FileProcessController(parameters, pid); } - public ProcessController createProcessController(final ProcessControllerParameters arguments, - final File pidFile, final long timeout, final TimeUnit unit) + public ProcessController createProcessController(final ProcessControllerParameters parameters, + final File directory, final String pidFileName) throws IOException, InterruptedException, TimeoutException { - if (arguments == null) { - throw new NullPointerException("ProcessControllerParameters must not be null"); - } - if (pidFile == null) { - throw new NullPointerException("Pid file must not be null"); - } - return createProcessController(arguments, new PidFile(pidFile).readPid(timeout, unit)); - } + notNull(parameters, "Invalid parameters '" + parameters + "' specified"); + notNull(directory, "Invalid directory '" + directory + "' specified"); + notEmpty(pidFileName, "Invalid pidFileName '" + pidFileName + "' specified"); - public ProcessController createProcessController(final ProcessControllerParameters arguments, - final File directory, final String pidFilename, final long timeout, final TimeUnit unit) - throws IOException, InterruptedException, TimeoutException { - if (arguments == null) { - throw new NullPointerException("ProcessControllerParameters must not be null"); - } - if (directory == null) { - throw new NullPointerException("Directory must not be null"); - } - if (pidFilename == null) { - throw new NullPointerException("Pid file name must not be null"); - } - return createProcessController(arguments, - new PidFile(directory, pidFilename).readPid(timeout, unit)); + return createProcessController(parameters, readPid(directory, pidFileName)); } public boolean isAttachAPIFound() { - if (this.disableAttachApi) { + if (disableAttachApi) { return false; } boolean found = false; try { final Class<?> virtualMachineClass = Class.forName("com.sun.tools.attach.VirtualMachine"); found = virtualMachineClass != null; - } catch (ClassNotFoundException e) { + } catch (ClassNotFoundException ignore) { } return found; } + + private int readPid(final File directory, final String pidFileName) throws IOException { + return new PidFile(directory, pidFileName).readPid(); + } } http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerParameters.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerParameters.java b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerParameters.java index eb3deb9..a20faf1 100755 --- a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerParameters.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessControllerParameters.java @@ -15,10 +15,10 @@ package org.apache.geode.internal.process; /** - * Defines the methods for providing input arguments to the <code>ProcessController</code>. + * Defines the methods for providing input arguments to the {@code ProcessController}. * - * Implementations of <code>ProcessController</code> are in this package. Classes that implement - * <code>ProcessControllerArguments</code> would typically be in a different package. + * Implementations of {@code ProcessController} are in this package. Classes that implement + * {@code ProcessController} would typically be in a different package. * * @since GemFire 8.0 */ http://git-wip-us.apache.org/repos/asf/geode/blob/894f3ee7/geode-core/src/main/java/org/apache/geode/internal/process/ProcessLauncherContext.java ---------------------------------------------------------------------- diff --git a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessLauncherContext.java b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessLauncherContext.java index 463fd18..9b10550 100644 --- a/geode-core/src/main/java/org/apache/geode/internal/process/ProcessLauncherContext.java +++ b/geode-core/src/main/java/org/apache/geode/internal/process/ProcessLauncherContext.java @@ -12,16 +12,14 @@ * or implied. See the License for the specific language governing permissions and limitations under * the License. */ - package org.apache.geode.internal.process; -import org.apache.geode.distributed.internal.DistributionConfig; -import org.apache.geode.internal.io.TeeOutputStream; -import org.apache.geode.internal.io.TeePrintStream; +import static org.apache.commons.lang.Validate.notNull; -import java.io.*; import java.util.Properties; +import org.apache.geode.distributed.internal.DistributionConfig; + /** * Thread based context for launching a process. GemFire internals can acquire optional * configuration details from a process launcher via this context. @@ -43,8 +41,11 @@ public class ProcessLauncherContext { */ private static final Properties OVERRIDDEN_DEFAULTS_DEFAULT = new Properties(); - private static final ThreadLocal<ProcessLauncherContext> DATA = - new ThreadLocal<ProcessLauncherContext>(); + private static final ThreadLocal<ProcessLauncherContext> DATA = new ThreadLocal<>(); + + private final boolean redirectOutput; + private final Properties overriddenDefaults; + private final StartupStatusListener startupListener; private static ProcessLauncherContext get() { return DATA.get(); @@ -56,7 +57,7 @@ public class ProcessLauncherContext { * @return true if this process should redirect output to the system log */ public static boolean isRedirectingOutput() { - final ProcessLauncherContext context = get(); + ProcessLauncherContext context = get(); if (context == null) { return REDIRECT_OUTPUT_DEFAULT; } @@ -71,16 +72,15 @@ public class ProcessLauncherContext { * @return the contingent gemfire properties values to be used as an alternative default value */ public static Properties getOverriddenDefaults() { - final ProcessLauncherContext context = get(); + ProcessLauncherContext context = get(); if (context == null) { return OVERRIDDEN_DEFAULTS_DEFAULT; } return context.overriddenDefaults(); } - public static StartupStatusListener getStartupListener() { - final ProcessLauncherContext context = get(); + ProcessLauncherContext context = get(); if (context == null) { return null; } @@ -91,9 +91,12 @@ public class ProcessLauncherContext { /** * Sets the ProcessLauncherContext data for the calling thread. */ - public static void set(final boolean redirectOutput, final Properties contingentProperties, + public static void set(final boolean redirectOutput, final Properties overriddenDefaults, final StartupStatusListener startupListener) { - DATA.set(new ProcessLauncherContext(redirectOutput, contingentProperties, startupListener)); + notNull(overriddenDefaults, + "Invalid overriddenDefaults '" + overriddenDefaults + "' specified"); + + DATA.set(new ProcessLauncherContext(redirectOutput, overriddenDefaults, startupListener)); installLogListener(startupListener); } @@ -101,12 +104,11 @@ public class ProcessLauncherContext { * Clears the current ProcessLauncherContext for the calling thread. */ public static void remove() { - // DATA.get().restoreErrorStream(); DATA.remove(); clearLogListener(); } - private static void installLogListener(StartupStatusListener startupListener) { + private static void installLogListener(final StartupStatusListener startupListener) { if (startupListener != null) { StartupStatus.setListener(startupListener); } @@ -116,11 +118,6 @@ public class ProcessLauncherContext { StartupStatus.clearListener(); } - private final boolean redirectOutput; - private final Properties overriddenDefaults; - private final StartupStatusListener startupListener; - private PrintStream err; - private ProcessLauncherContext(final boolean redirectOutput, final Properties overriddenDefaults, final StartupStatusListener startupListener) { this.redirectOutput = redirectOutput; @@ -129,39 +126,14 @@ public class ProcessLauncherContext { } private boolean redirectOutput() { - return this.redirectOutput; + return redirectOutput; } private Properties overriddenDefaults() { - return this.overriddenDefaults; + return overriddenDefaults; } private StartupStatusListener startupListener() { - return this.startupListener; - } - - @SuppressWarnings("unused") - private void teeErrorStream() { - final FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err); - this.err = new PrintStream(new BufferedOutputStream(fdErr, 128), true); - System.setErr(new TeePrintStream(new TeeOutputStream(new BufferedOutputStream(fdErr, 128)))); - } - - @SuppressWarnings("unused") - private void restoreErrorStream() { - if (System.err instanceof TeePrintStream) { - final TeePrintStream tee = ((TeePrintStream) System.err); - final OutputStream branch = tee.getTeeOutputStream().getBranchOutputStream(); - - PrintStream newStdErr = null; - if (branch == null) { - newStdErr = this.err; - } else if (branch instanceof PrintStream) { - newStdErr = (PrintStream) branch; - } else { - newStdErr = new PrintStream(new BufferedOutputStream(branch, 128), true); - } - System.setErr(newStdErr); - } + return startupListener; } }