From bbca1b8de515423e6fd0d7e93e0d2f49e82dd7ba Mon Sep 17 00:00:00 2001 From: Simon Brandhof Date: Sun, 21 Sep 2014 21:22:59 +0200 Subject: [PATCH] SONAR-4898 drop RMI and autokill --- .../sonar/process/monitor/JavaCommand.java | 39 ++--- .../process/monitor/JavaProcessLauncher.java | 41 ++--- .../sonar/process/monitor/JmxConnector.java | 44 ------ .../org/sonar/process/monitor/Monitor.java | 62 ++------ .../sonar/process/monitor/PingerThread.java | 60 ------- .../org/sonar/process/monitor/ProcessRef.java | 79 ++++++---- .../process/monitor/RmiJmxConnector.java | 145 ----------------- .../sonar/process/monitor/StreamGobbler.java | 4 +- .../process/monitor/TerminatorThread.java | 23 +-- .../org/sonar/process/monitor/Timeouts.java | 75 --------- .../sonar/process/monitor/WatcherThread.java | 12 +- .../monitor/CallVerifierJmxConnector.java | 34 ---- .../ImpossibleToConnectJmxConnector.java | 42 ----- .../process/monitor/JavaCommandTest.java | 17 +- .../monitor/JavaProcessLauncherTest.java | 3 +- .../sonar/process/monitor/MonitorTest.java | 106 ++----------- .../process/monitor/RmiJmxConnectorTest.java | 71 --------- .../TerminationFailureRmiConnector.java | 27 ---- .../sonar/process/monitor/TimeoutsTest.java | 16 -- .../process/monitor/WatcherThreadTest.java | 2 +- .../main/java/org/sonar/process/JmxUtils.java | 81 ---------- .../java/org/sonar/process/Lifecycle.java | 6 +- .../org/sonar/process/MessageException.java | 3 + .../{MonitoredProcess.java => Monitored.java} | 5 +- .../java/org/sonar/process/NetworkUtils.java | 14 +- .../org/sonar/process/ProcessEntryPoint.java | 86 +++------- .../org/sonar/process/ProcessLogging.java | 1 - .../java/org/sonar/process/ProcessMXBean.java | 28 ---- .../java/org/sonar/process/ProcessUtils.java | 21 ++- .../main/java/org/sonar/process/Props.java | 2 +- .../java/org/sonar/process/SharedStatus.java} | 50 +++--- .../main/java/org/sonar/process/State.java | 26 --- .../java/org/sonar/process/StopperThread.java | 18 ++- .../java/org/sonar/process/Terminable.java | 28 ---- .../java/org/sonar/process/JmxUtilsTest.java | 127 --------------- .../java/org/sonar/process/LifecycleTest.java | 13 ++ .../org/sonar/process/NetworkUtilsTest.java | 5 +- .../sonar/process/ProcessEntryPointTest.java | 69 ++------ .../org/sonar/process/ProcessUtilsTest.java | 28 ---- .../org/sonar/process/SharedStatusTest.java | 101 ++++++++++++ .../org/sonar/process/StopperThreadTest.java | 79 ++++++++++ .../org/sonar/process/test/HttpProcess.java | 15 +- .../test/InfiniteTerminationProcess.java | 85 ++++++++++ .../sonar/process/test/StandardProcess.java | 10 +- .../java/org/sonar/search/SearchServer.java | 149 +++++++++--------- .../org/sonar/server/app/EmbeddedTomcat.java | 5 + .../java/org/sonar/server/app/WebServer.java | 8 +- .../sonar/server/db/EmbeddedDatabaseTest.java | 12 +- .../sonar/server/search/BaseIndexTest.java | 3 +- .../org/sonar/server/tester/ServerTester.java | 2 +- .../src/main/assembly/conf/sonar.properties | 9 -- .../main/java/org/sonar/application/App.java | 27 +--- .../sonar/application/DefaultSettings.java | 4 - .../application/DefaultSettingsTest.java | 6 +- 54 files changed, 605 insertions(+), 1423 deletions(-) delete mode 100644 server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JmxConnector.java delete mode 100644 server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/PingerThread.java delete mode 100644 server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/RmiJmxConnector.java delete mode 100644 server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/CallVerifierJmxConnector.java delete mode 100644 server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/ImpossibleToConnectJmxConnector.java delete mode 100644 server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/RmiJmxConnectorTest.java delete mode 100644 server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TerminationFailureRmiConnector.java delete mode 100644 server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java rename server/sonar-process/src/main/java/org/sonar/process/{MonitoredProcess.java => Monitored.java} (94%) delete mode 100644 server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java rename server/sonar-process/src/{test/java/org/sonar/process/BaseProcessTest.java => main/java/org/sonar/process/SharedStatus.java} (53%) delete mode 100644 server/sonar-process/src/main/java/org/sonar/process/State.java delete mode 100644 server/sonar-process/src/main/java/org/sonar/process/Terminable.java delete mode 100644 server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java delete mode 100644 server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java create mode 100644 server/sonar-process/src/test/java/org/sonar/process/SharedStatusTest.java create mode 100644 server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java create mode 100644 server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java index e950c92c58c..7920581a5a2 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java @@ -37,9 +37,6 @@ public class JavaCommand { private File workDir; - // any available port by default - private int jmxPort = -1; - // for example -Xmx1G private final List javaOptions = new ArrayList(); @@ -54,6 +51,8 @@ public class JavaCommand { private final Map envVariables = new HashMap(System.getenv()); + private File tempDir = null; + public JavaCommand(String key) { this.key = key; } @@ -71,24 +70,20 @@ public class JavaCommand { return this; } - /** - * Shortcut to set the java option -Djava.io.tmpdir - */ - public JavaCommand setTempDir(File tempDir) { - this.javaOptions.add("-Djava.io.tmpdir=" + tempDir.getAbsolutePath()); - return this; + public File getTempDir() { + return tempDir; } - public int getJmxPort() { - return jmxPort; + public JavaCommand setTempDir(File tempDir) { + this.tempDir = tempDir; + return this; } - /** - * Current mandatory to be set - */ - public JavaCommand setJmxPort(int jmxPort) { - this.jmxPort = jmxPort; - return this; + public File getReadyFile() { + if (tempDir == null) { + throw new IllegalStateException("Temp directory not set"); + } + return new File(tempDir, key + ".ready"); } public List getJavaOptions() { @@ -156,20 +151,10 @@ public class JavaCommand { return this; } - public boolean isDebugMode() { - for (String javaOption : javaOptions) { - if (javaOption.contains("-agentlib:jdwp")) { - return true; - } - } - return false; - } - @Override public String toString() { final StringBuilder sb = new StringBuilder("JavaCommand{"); sb.append("workDir=").append(workDir); - sb.append(", jmxPort=").append(jmxPort); sb.append(", javaOptions=").append(javaOptions); sb.append(", className='").append(className).append('\''); sb.append(", classpath=").append(classpath); diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java index ff8ef88e8f7..0fa33bc9c37 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java @@ -21,9 +21,9 @@ package org.sonar.process.monitor; import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; -import org.sonar.process.LoopbackAddress; import org.sonar.process.ProcessEntryPoint; import org.sonar.process.ProcessUtils; +import org.sonar.process.SharedStatus; import java.io.File; import java.io.FileOutputStream; @@ -44,20 +44,27 @@ public class JavaProcessLauncher { ProcessRef launch(JavaCommand command) { Process process = null; try { + // cleanup existing monitor file. Child process creates it when ready. + // TODO fail if impossible to delete + SharedStatus sharedStatus = new SharedStatus(command.getReadyFile()); + sharedStatus.prepare(); + ProcessBuilder processBuilder = create(command); LoggerFactory.getLogger(getClass()).info("Launch {}: {}", command.getKey(), StringUtils.join(processBuilder.command(), " ")); + + long startedAt = System.currentTimeMillis(); process = processBuilder.start(); - StreamGobbler errorGobbler = new StreamGobbler(process.getErrorStream(), command.getKey()); StreamGobbler inputGobbler = new StreamGobbler(process.getInputStream(), command.getKey()); inputGobbler.start(); - errorGobbler.start(); - return new ProcessRef(command.getKey(), process, errorGobbler, inputGobbler); + ProcessRef ref = new ProcessRef(command.getKey(), sharedStatus, process, inputGobbler); + ref.setLaunchedAt(startedAt); + return ref; } catch (Exception e) { // just in case - ProcessUtils.destroyQuietly(process); + ProcessUtils.sendKillSignal(process); throw new IllegalStateException("Fail to launch " + command.getKey(), e); } } @@ -66,17 +73,17 @@ public class JavaProcessLauncher { List commands = new ArrayList(); commands.add(buildJavaPath()); commands.addAll(javaCommand.getJavaOptions()); - commands.addAll(buildJmxOptions(javaCommand)); + // TODO warning - does it work if temp dir contains a whitespace ? + commands.add(String.format("-Djava.io.tmpdir=%s", javaCommand.getTempDir().getAbsolutePath())); commands.addAll(buildClasspath(javaCommand)); commands.add(javaCommand.getClassName()); - - // TODO warning - does it work if temp dir contains a whitespace ? commands.add(buildPropertiesFile(javaCommand).getAbsolutePath()); ProcessBuilder processBuilder = new ProcessBuilder(); processBuilder.command(commands); processBuilder.directory(javaCommand.getWorkDir()); processBuilder.environment().putAll(javaCommand.getEnvVariables()); + processBuilder.redirectErrorStream(true); return processBuilder; } @@ -86,18 +93,6 @@ public class JavaProcessLauncher { "bin" + separator + "java").getAbsolutePath(); } - private List buildJmxOptions(JavaCommand javaCommand) { - if (javaCommand.getJmxPort() < 1) { - throw new IllegalStateException("JMX port is not set"); - } - return Arrays.asList( - "-Dcom.sun.management.jmxremote", - "-Dcom.sun.management.jmxremote.port=" + javaCommand.getJmxPort(), - "-Dcom.sun.management.jmxremote.authenticate=false", - "-Dcom.sun.management.jmxremote.ssl=false", - "-Djava.rmi.server.hostname=" + LoopbackAddress.get().getHostAddress()); - } - private List buildClasspath(JavaCommand javaCommand) { return Arrays.asList("-cp", StringUtils.join(javaCommand.getClasspath(), System.getProperty("path.separator"))); } @@ -105,14 +100,12 @@ public class JavaProcessLauncher { private File buildPropertiesFile(JavaCommand javaCommand) { File propertiesFile = null; try { - propertiesFile = File.createTempFile("sq-conf", "properties"); + propertiesFile = File.createTempFile("sq-process", "properties"); Properties props = new Properties(); props.putAll(javaCommand.getArguments()); props.setProperty(ProcessEntryPoint.PROPERTY_PROCESS_KEY, javaCommand.getKey()); - props.setProperty(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, String.valueOf(javaCommand.isDebugMode())); - props.setProperty(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_TIMEOUT, String.valueOf(timeouts.getAutokillPingTimeout())); - props.setProperty(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_INTERVAL, String.valueOf(timeouts.getAutokillPingInterval())); props.setProperty(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, String.valueOf(timeouts.getTerminationTimeout())); + props.setProperty(ProcessEntryPoint.PROPERTY_STATUS_PATH, javaCommand.getReadyFile().getAbsolutePath()); OutputStream out = new FileOutputStream(propertiesFile); props.store(out, String.format("Temporary properties file for command [%s]", javaCommand.getKey())); out.close(); diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JmxConnector.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JmxConnector.java deleted file mode 100644 index c996595ba73..00000000000 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JmxConnector.java +++ /dev/null @@ -1,44 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process.monitor; - -/** - * Interactions with monitored process - */ -public interface JmxConnector { - - /** - * Throws an exception if timeout reached - */ - void connect(JavaCommand command, ProcessRef processRef, long timeoutMs); - - void ping(ProcessRef process); - - /** - * Throws an exception if timeout reached - */ - boolean isReady(ProcessRef process, long timeoutMs); - - /** - * Throws an exception if timeout reached - */ - void terminate(ProcessRef process, long timeoutMs); - -} diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java index 518d2c354cd..86e47ca429e 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java @@ -21,8 +21,7 @@ package org.sonar.process.monitor; import org.slf4j.LoggerFactory; import org.sonar.process.Lifecycle; -import org.sonar.process.MessageException; -import org.sonar.process.State; +import org.sonar.process.Lifecycle.State; import org.sonar.process.SystemExit; import java.util.List; @@ -33,28 +32,23 @@ public class Monitor { private final List processes = new CopyOnWriteArrayList(); private final TerminatorThread terminator; private final JavaProcessLauncher launcher; - private final JmxConnector jmxConnector; private final Lifecycle lifecycle = new Lifecycle(); - private final Timeouts timeouts; private final SystemExit systemExit; private Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook"); - // used by awaitTermination() to block until all processes are shutdown + // used by awaitStop() to block until all processes are shutdown private final List watcherThreads = new CopyOnWriteArrayList(); - Monitor(JavaProcessLauncher launcher, JmxConnector jmxConnector, Timeouts timeouts, SystemExit exit) { + Monitor(JavaProcessLauncher launcher, SystemExit exit) { this.launcher = launcher; - this.jmxConnector = jmxConnector; - this.timeouts = timeouts; - this.terminator = new TerminatorThread(processes, jmxConnector, timeouts); + this.terminator = new TerminatorThread(processes); this.systemExit = exit; } public static Monitor create() { Timeouts timeouts = new Timeouts(); - return new Monitor(new JavaProcessLauncher(timeouts), new RmiJmxConnector(), - timeouts, new SystemExit()); + return new Monitor(new JavaProcessLauncher(timeouts), new SystemExit()); } /** @@ -103,44 +97,14 @@ public class Monitor { watcherThread.start(); watcherThreads.add(watcherThread); - // add to list of monitored processes only when successfully connected to it - jmxConnector.connect(command, processRef, timeouts.getJmxConnectionTimeout()); processes.add(processRef); - // ping process on a regular basis - processRef.setPingEnabled(!command.isDebugMode()); - if (processRef.isPingEnabled()) { - PingerThread.startPinging(processRef, jmxConnector, timeouts); - } - // wait for process to be ready (accept requests or so on) - waitForReady(processRef); + processRef.waitForReady(); LoggerFactory.getLogger(getClass()).info(String.format("%s is up", processRef)); } - private void waitForReady(ProcessRef processRef) { - boolean ready = false; - while (!ready) { - if (processRef.isTerminated()) { - throw new MessageException(String.format("%s failed to start", processRef)); - } - try { - ready = jmxConnector.isReady(processRef, timeouts.getMonitorIsReadyTimeout()); - } catch (Exception ignored) { - // failed to send request, probably because RMI server is still not alive - // trying again, as long as process is alive - // TODO could be improved to have a STARTING timeout (to be implemented in monitor or - // in child process ?) - } - try { - Thread.sleep(300L); - } catch (InterruptedException e) { - throw new IllegalStateException("Interrupted while waiting for " + processRef + " to be ready", e); - } - } - } - /** * Blocks until all processes are terminated */ @@ -160,14 +124,13 @@ public class Monitor { * Blocks until all processes are terminated. */ public void stop() { - terminateAsync(); + stopAsync(); try { terminator.join(); } catch (InterruptedException ignored) { - // ignore, stop blocking + // stop blocking and exiting } // safeguard if TerminatorThread is buggy - hardKillAll(); lifecycle.tryToMoveTo(State.STOPPED); systemExit.exit(0); } @@ -176,7 +139,7 @@ public class Monitor { * Asks for processes termination and returns without blocking until termination. * @return true if termination was requested, false if it was already being terminated */ - boolean terminateAsync() { + boolean stopAsync() { boolean requested = false; if (lifecycle.tryToMoveTo(State.STOPPING)) { requested = true; @@ -185,13 +148,6 @@ public class Monitor { return requested; } - private void hardKillAll() { - // no specific order, kill'em all!!! - for (ProcessRef process : processes) { - process.hardKill(); - } - } - public State getState() { return lifecycle.getState(); } diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/PingerThread.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/PingerThread.java deleted file mode 100644 index 65df8546e33..00000000000 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/PingerThread.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process.monitor; - -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -/** - * This thread pings a process - through RMI - at fixed delay - */ -class PingerThread extends Thread { - - private final ProcessRef processRef; - private final JmxConnector jmxConnector; - - private PingerThread(ProcessRef process, JmxConnector jmxConnector) { - // it's important to give a name for traceability in profiling tools like visualVM - super(String.format("Ping[%s]", process.getKey())); - setDaemon(true); - this.processRef = process; - this.jmxConnector = jmxConnector; - } - - @Override - public void run() { - if (!processRef.isTerminated() && processRef.isPingEnabled()) { - try { - jmxConnector.ping(processRef); - } catch (Exception ignored) { - // failed to ping - } - } else { - interrupt(); - } - } - - static void startPinging(ProcessRef processRef, JmxConnector jmxConnector, Timeouts timeouts) { - ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1); - PingerThread pinger = new PingerThread(processRef, jmxConnector); - scheduler.scheduleAtFixedRate(pinger, 0L, timeouts.getMonitorPingInterval(), TimeUnit.MILLISECONDS); - } -} diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java index 8d926c39d72..f49a75d79a8 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java @@ -19,21 +19,26 @@ */ package org.sonar.process.monitor; +import org.slf4j.LoggerFactory; +import org.sonar.process.MessageException; import org.sonar.process.ProcessUtils; +import org.sonar.process.SharedStatus; class ProcessRef { private final String key; + private final SharedStatus sharedStatus; private final Process process; - private final StreamGobbler[] gobblers; - private volatile boolean terminated = false; - private volatile boolean pingEnabled = true; + private final StreamGobbler gobbler; + private long launchedAt; + private volatile boolean stopped = false; - ProcessRef(String key, Process process, StreamGobbler... gobblers) { + ProcessRef(String key, SharedStatus sharedStatus, Process process, StreamGobbler gobbler) { this.key = key; + this.sharedStatus = sharedStatus; this.process = process; - this.terminated = !ProcessUtils.isAlive(process); - this.gobblers = gobblers; + this.stopped = !ProcessUtils.isAlive(process); + this.gobbler = gobbler; } /** @@ -50,45 +55,53 @@ class ProcessRef { return process; } - /** - * Almost real-time status - */ - boolean isTerminated() { - return terminated; + void waitForReady() { + boolean ready = false; + while (!ready) { + if (isStopped()) { + throw new MessageException(String.format("%s failed to start", this)); + } + ready = sharedStatus.wasStartedAfter(launchedAt); + try { + Thread.sleep(200L); + } catch (InterruptedException e) { + throw new IllegalStateException(String.format("Interrupted while waiting for %s to be ready", this), e); + } + } } - /** - * Sending pings can be disabled when requesting for termination or when process is on debug mode (JDWP) - */ - void setPingEnabled(boolean b) { - this.pingEnabled = b; + void setLaunchedAt(long launchedAt) { + this.launchedAt = launchedAt; } - boolean isPingEnabled() { - return pingEnabled; + /** + * Almost real-time status + */ + boolean isStopped() { + return stopped; } /** - * Destroy the process without gracefully asking it to terminate (kill -9). - * @return true if the process was killed, false if process is already terminated + * Sends kill signal and awaits termination. */ - boolean hardKill() { - boolean killed = false; - terminated = true; - pingEnabled = false; + void kill() { if (ProcessUtils.isAlive(process)) { - ProcessUtils.destroyQuietly(process); - killed = true; - } - for (StreamGobbler gobbler : gobblers) { - StreamGobbler.waitUntilFinish(gobbler); + LoggerFactory.getLogger(getClass()).info(String.format("%s is stopping", this)); + ProcessUtils.sendKillSignal(process); + try { + // signal is sent, waiting for shutdown hooks to be executed + process.waitFor(); + StreamGobbler.waitUntilFinish(gobbler); + ProcessUtils.closeStreams(process); + } catch (InterruptedException ignored) { + // can't wait for the termination of process. Let's assume it's down. + } } - ProcessUtils.closeStreams(process); - return killed; + stopped = true; } - void setTerminated(boolean b) { - this.terminated = b; + void setStopped(boolean b) { + this.stopped = b; } @Override diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/RmiJmxConnector.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/RmiJmxConnector.java deleted file mode 100644 index 68dfc34182b..00000000000 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/RmiJmxConnector.java +++ /dev/null @@ -1,145 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process.monitor; - -import org.slf4j.LoggerFactory; -import org.sonar.process.JmxUtils; -import org.sonar.process.LoopbackAddress; -import org.sonar.process.ProcessMXBean; -import org.sonar.process.ProcessUtils; - -import javax.annotation.CheckForNull; -import javax.management.JMX; -import javax.management.MBeanServerConnection; -import javax.management.remote.JMXConnector; -import javax.management.remote.JMXConnectorFactory; -import javax.management.remote.JMXServiceURL; - -import java.util.IdentityHashMap; -import java.util.Map; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; - -class RmiJmxConnector implements JmxConnector { - - static { - /* - * Prevents such warnings : - * - * WARNING: Failed to restart: java.io.IOException: Failed to get a RMI stub: javax.naming.ServiceUnavailableException [Root exception - * is java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: - * java.net.ConnectException: Connection refused] - * Sep 11, 2014 7:32:32 PM RMIConnector RMIClientCommunicatorAdmin-doStop - * WARNING: Failed to call the method close():java.rmi.ConnectException: Connection refused to host: 127.0.0.1; nested exception is: - * java.net.ConnectException: Connection refused - * Sep 11, 2014 7:32:32 PM ClientCommunicatorAdmin Checker-run - * WARNING: Failed to check connection: java.net.ConnectException: Connection refused - * Sep 11, 2014 7:32:32 PM ClientCommunicatorAdmin Checker-run - * WARNING: stopping - */ - System.setProperty("sun.rmi.transport.tcp.logLevel", "SEVERE"); - } - - private final Map mbeans = new IdentityHashMap(); - - @Override - public synchronized void connect(final JavaCommand command, ProcessRef processRef, long timeoutMs) { - ConnectorCallable callable = new ConnectorCallable(command, processRef.getProcess()); - ProcessMXBean mxBean = execute(callable, timeoutMs); - if (mxBean != null) { - register(processRef, mxBean); - } - } - - @Override - public void ping(ProcessRef processRef) { - mbeans.get(processRef).ping(); - } - - @Override - public boolean isReady(final ProcessRef processRef, long timeoutMs) { - return execute(new Callable() { - @Override - public Boolean call() throws Exception { - return mbeans.get(processRef).isReady(); - } - }, timeoutMs); - } - - @Override - public void terminate(final ProcessRef processRef, long timeoutMs) { - execute(new Callable() { - @Override - public Void call() throws Exception { - LoggerFactory.getLogger(getClass()).info("Request termination of " + processRef); - mbeans.get(processRef).terminate(); - return null; - } - }, timeoutMs); - } - - void register(ProcessRef processRef, ProcessMXBean mxBean) { - mbeans.put(processRef, mxBean); - } - - private T execute(Callable callable, long timeoutMs) { - ExecutorService executor = Executors.newSingleThreadExecutor(); - try { - Future future = executor.submit(callable); - return future.get(timeoutMs, TimeUnit.MILLISECONDS); - } catch (Exception e) { - throw new IllegalStateException("Fail send JMX request", e); - } finally { - executor.shutdownNow(); - } - } - - private static class ConnectorCallable implements Callable { - private final JavaCommand command; - private final Process process; - - private ConnectorCallable(JavaCommand command, Process process) { - this.command = command; - this.process = process; - } - - @Override - @CheckForNull - public ProcessMXBean call() throws Exception { - JMXServiceURL jmxUrl = JmxUtils.serviceUrl(LoopbackAddress.get(), command.getJmxPort()); - while (ProcessUtils.isAlive(process)) { - try { - JMXConnector jmxConnector = JMXConnectorFactory.connect(jmxUrl, null); - MBeanServerConnection mBeanServer = jmxConnector.getMBeanServerConnection(); - return JMX.newMBeanProxy(mBeanServer, JmxUtils.objectName(command.getKey()), ProcessMXBean.class); - } catch (Exception ignored) { - // ignored, RMI server is probably not started yet - } - Thread.sleep(300L); - } - - // process went down, no need to connect - return null; - } - } -} diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java index cb412965f55..d5f54e33248 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java @@ -55,8 +55,8 @@ class StreamGobbler extends Thread { while ((line = br.readLine()) != null) { logger.info(line); } - } catch (Exception e) { - logger.error("Fail to read process logs", e); + } catch (Exception ignored) { + // ignored } finally { IOUtils.closeQuietly(br); } diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/TerminatorThread.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/TerminatorThread.java index f5b11b3148f..493be826ae7 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/TerminatorThread.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/TerminatorThread.java @@ -19,8 +19,6 @@ */ package org.sonar.process.monitor; -import org.slf4j.LoggerFactory; - import java.util.List; /** @@ -31,33 +29,18 @@ import java.util.List; class TerminatorThread extends Thread { private final List processes; - private final JmxConnector jmxConnector; - private final Timeouts timeouts; - TerminatorThread(List processes, JmxConnector jmxConnector, Timeouts timeouts) { + TerminatorThread(List processes) { super("Terminator"); this.processes = processes; - this.jmxConnector = jmxConnector; - this.timeouts = timeouts; } @Override public void run() { // terminate in reverse order of startup (dependency order) for (int index = processes.size() - 1; index >= 0; index--) { - final ProcessRef processRef = processes.get(index); - if (!processRef.isTerminated()) { - processRef.setPingEnabled(false); - try { - jmxConnector.terminate(processRef, timeouts.getTerminationTimeout()); - } catch (Exception ignored) { - // failed to gracefully stop in a timely fashion - LoggerFactory.getLogger(getClass()).info(String.format("Kill %s", processRef)); - } finally { - // kill even if graceful termination was done, just to be sure that physical process is really down - processRef.hardKill(); - } - } + ProcessRef processRef = processes.get(index); + processRef.kill(); } } } diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java index a3f7210f37f..d9b0b4f6e3e 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java @@ -25,81 +25,6 @@ package org.sonar.process.monitor; class Timeouts { private long terminationTimeout = 120000L; - private long jmxConnectionTimeout = 15000L; - private long monitorPingInterval = 3000L; - private long monitorIsReadyTimeout = 10000L; - private long autokillPingTimeout = 60000L; - private long autokillPingInterval = 3000L; - - /** - * [monitor] Timeout to get connected to RMI MXBean while process is alive - */ - long getJmxConnectionTimeout() { - return jmxConnectionTimeout; - } - - /** - * @see #getJmxConnectionTimeout() - */ - void setJmxConnectionTimeout(long l) { - this.jmxConnectionTimeout = l; - } - - /** - * [monitor] Delay between each ping request - */ - long getMonitorPingInterval() { - return monitorPingInterval; - } - - /** - * @see #getMonitorPingInterval() - */ - void setMonitorPingInterval(long l) { - this.monitorPingInterval = l; - } - - /** - * [monitor] Timeout of isReady request - */ - long getMonitorIsReadyTimeout() { - return monitorIsReadyTimeout; - } - - /** - * @see #getMonitorIsReadyTimeout() - */ - void setMonitorIsReadyTimeout(long l) { - this.monitorIsReadyTimeout = l; - } - - /** - * [monitored process] maximum age of last received ping before process autokills - */ - long getAutokillPingTimeout() { - return autokillPingTimeout; - } - - /** - * @see #getAutokillPingTimeout() - */ - void setAutokillPingTimeout(long l) { - this.autokillPingTimeout = l; - } - - /** - * [monitored process] delay between checks of freshness of received pings - */ - long getAutokillPingInterval() { - return autokillPingInterval; - } - - /** - * @see #getAutokillPingInterval() - */ - void setAutokillPingInterval(long l) { - this.autokillPingInterval = l; - } /** * [both monitor and monitored process] timeout of graceful termination before hard killing diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java index 55880a989a7..4275b6b98a1 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java @@ -46,16 +46,16 @@ class WatcherThread extends Thread { @Override public void run() { - boolean alive = true; - while (alive) { + boolean stopped = false; + while (!stopped) { try { processRef.getProcess().waitFor(); - processRef.setTerminated(true); - alive = false; - LoggerFactory.getLogger(getClass()).info(String.format("%s is down", processRef)); + processRef.setStopped(true); + stopped = true; + LoggerFactory.getLogger(getClass()).info(String.format("%s is stopped", processRef)); // terminate all other processes, but in another thread - monitor.stop(); + monitor.stopAsync(); } catch (InterruptedException ignored) { // continue to watch process } diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/CallVerifierJmxConnector.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/CallVerifierJmxConnector.java deleted file mode 100644 index 2010e19aa87..00000000000 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/CallVerifierJmxConnector.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process.monitor; - -/** - * Used to verify that pings were sent or not. - */ -public class CallVerifierJmxConnector extends RmiJmxConnector { - - boolean askedPing = false; - - @Override - public void ping(ProcessRef process) { - askedPing = true; - super.ping(process); - } -} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/ImpossibleToConnectJmxConnector.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/ImpossibleToConnectJmxConnector.java deleted file mode 100644 index 055e3c8e50b..00000000000 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/ImpossibleToConnectJmxConnector.java +++ /dev/null @@ -1,42 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process.monitor; - -public class ImpossibleToConnectJmxConnector implements JmxConnector { - @Override - public void connect(JavaCommand command, ProcessRef processRef, long timeoutMs) { - throw new IllegalStateException("Test - Impossible to connect to JMX"); - } - - @Override - public void ping(ProcessRef process) { - - } - - @Override - public boolean isReady(ProcessRef process, long timeoutMs) { - return false; - } - - @Override - public void terminate(ProcessRef process, long timeoutMs) { - - } -} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java index 8744fb73ff3..fbfbad11b54 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java @@ -42,7 +42,6 @@ public class JavaCommandTest { args.setProperty("second_arg", "val2"); command.setArguments(args); - command.setJmxPort(1234); command.setClassName("org.sonar.ElasticSearch"); command.setEnvVariable("JAVA_COMMAND_TEST", "1000"); File tempDir = temp.newFolder(); @@ -55,9 +54,9 @@ public class JavaCommandTest { assertThat(command.toString()).isNotNull(); assertThat(command.getClasspath()).containsOnly("lib/*.jar", "conf/*.xml"); - assertThat(command.getJavaOptions()).containsOnly("-Xmx128m", "-Djava.io.tmpdir=" + tempDir.getAbsolutePath()); + assertThat(command.getJavaOptions()).containsOnly("-Xmx128m"); assertThat(command.getWorkDir()).isSameAs(workDir); - assertThat(command.getJmxPort()).isEqualTo(1234); + assertThat(command.getTempDir()).isSameAs(tempDir); assertThat(command.getClassName()).isEqualTo("org.sonar.ElasticSearch"); // copy current env variables @@ -65,18 +64,6 @@ public class JavaCommandTest { assertThat(command.getEnvVariables().size()).isEqualTo(System.getenv().size() + 1); } - @Test - public void test_debug_mode() throws Exception { - JavaCommand command = new JavaCommand("es"); - assertThat(command.isDebugMode()).isFalse(); - - command.addJavaOption("-Xmx512m"); - assertThat(command.isDebugMode()).isFalse(); - - command.addJavaOption("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005"); - assertThat(command.isDebugMode()).isTrue(); - } - @Test public void split_java_options() throws Exception { JavaCommand command = new JavaCommand("foo"); diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java index 80a02cd39c6..80def695fb1 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java @@ -20,7 +20,6 @@ package org.sonar.process.monitor; import org.junit.Test; -import org.sonar.process.NetworkUtils; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; @@ -29,7 +28,7 @@ public class JavaProcessLauncherTest { @Test public void fail_to_launch() throws Exception { - JavaCommand command = new JavaCommand("test").setJmxPort(NetworkUtils.freePort()); + JavaCommand command = new JavaCommand("test"); JavaProcessLauncher launcher = new JavaProcessLauncher(new Timeouts()); try { // command is not correct (missing options), java.lang.ProcessBuilder#start() diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java index 171f5669fbc..b458254d351 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java @@ -28,7 +28,7 @@ import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.rules.Timeout; import org.sonar.process.NetworkUtils; -import org.sonar.process.State; +import org.sonar.process.Lifecycle.State; import org.sonar.process.SystemExit; import java.io.File; @@ -159,24 +159,7 @@ public class MonitorTest { } @Test - public void fail_to_connect_to_jmx() throws Exception { - Timeouts timeouts = new Timeouts(); - monitor = new Monitor(new JavaProcessLauncher(timeouts), - new ImpossibleToConnectJmxConnector(), timeouts, exit); - - HttpProcessClient p1 = new HttpProcessClient("p1"); - try { - monitor.start(Arrays.asList(p1.newCommand())); - fail(); - } catch (Exception e) { - // process was correctly launched, but there was a problem with RMI - assertThat(p1.isReady()).isFalse(); - assertThat(p1.wasGracefullyTerminated()).isFalse(); - } - } - - @Test - public void terminate_all_processes_if_monitor_shutdowns() throws Exception { + public void stop_all_processes_if_monitor_shutdowns() throws Exception { monitor = newDefaultMonitor(); HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2"); monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); @@ -192,7 +175,7 @@ public class MonitorTest { } @Test - public void terminate_all_processes_if_one_monitored_process_shutdowns() throws Exception { + public void stop_all_processes_if_one_shutdowns() throws Exception { monitor = newDefaultMonitor(); HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2"); monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); @@ -210,9 +193,9 @@ public class MonitorTest { } @Test - public void terminate_all_processes_if_one_fails_to_start() throws Exception { + public void stop_all_processes_if_one_fails_to_start() throws Exception { monitor = newDefaultMonitor(); - HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2", -1, NetworkUtils.freePort()); + HttpProcessClient p1 = new HttpProcessClient("p1"), p2 = new HttpProcessClient("p2", -1); try { monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); fail(); @@ -226,18 +209,8 @@ public class MonitorTest { } @Test - public void kill_process_if_fail_to_request_gracefully_termination() throws Exception { - Timeouts timeouts = new Timeouts(); - timeouts.setTerminationTimeout(100L); - monitor = new Monitor(new JavaProcessLauncher(timeouts), - new TerminationFailureRmiConnector(), timeouts, exit); - - HttpProcessClient p1 = new HttpProcessClient("p1"); - monitor.start(Arrays.asList(p1.newCommand())); - assertThat(p1.isReady()).isTrue(); - - monitor.stop(); - assertThat(p1.isReady()).isFalse(); + public void force_stop_if_too_long() throws Exception { + // TODO } @Test @@ -246,7 +219,6 @@ public class MonitorTest { JavaCommand command = new JavaCommand("test") .addClasspath(testJar.getAbsolutePath()) .setClassName("org.sonar.process.test.Unknown") - .setJmxPort(NetworkUtils.freePort()) .setTempDir(temp.newFolder()); try { @@ -258,63 +230,9 @@ public class MonitorTest { } } - @Test - public void terminate_all_if_one_monitored_process_shutdowns() throws Exception { - monitor = newDefaultMonitor(); - HttpProcessClient client = new HttpProcessClient("test"); - // blocks until started - monitor.start(Arrays.asList(client.newCommand())); - assertThat(client.isReady()).isTrue(); - - client.kill(); - assertThat(client.isReady()).isFalse(); - - // does not wait, already terminated - monitor.awaitTermination(); - - // TODO check logs - } - - @Test - public void fail_if_jmx_port_is_not_available() throws Exception { - monitor = newDefaultMonitor(); - // c1 and c2 have same JMX port - int jmxPort = NetworkUtils.freePort(); - HttpProcessClient p1 = new HttpProcessClient("p1", NetworkUtils.freePort(), jmxPort); - HttpProcessClient p2 = new HttpProcessClient("p2", NetworkUtils.freePort(), jmxPort); - try { - monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand())); - fail(); - } catch (Exception expected) { - assertThat(p1.wasReady()).isTrue(); - assertThat(p2.wasReady()).isFalse(); - assertThat(p1.isReady()).isFalse(); - assertThat(p2.isReady()).isFalse(); - } - } - - @Test - public void disable_autokill_on_jvm_debug_mode() throws Exception { - Timeouts timeouts = new Timeouts(); - timeouts.setMonitorPingInterval(10L); - timeouts.setAutokillPingInterval(10L); - timeouts.setAutokillPingTimeout(10L); - CallVerifierJmxConnector jmxConnector = new CallVerifierJmxConnector(); - monitor = new Monitor(new JavaProcessLauncher(timeouts), jmxConnector, timeouts, exit); - - JavaCommand command = newStandardProcessCommand() - .addJavaOption("-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=" + NetworkUtils.freePort()); - monitor.start(Arrays.asList(command)); - - Thread.sleep(20L); - assertThat(jmxConnector.askedPing).isFalse(); - - monitor.stop(); - } - private Monitor newDefaultMonitor() { Timeouts timeouts = new Timeouts(); - return new Monitor(new JavaProcessLauncher(timeouts), new RmiJmxConnector(), timeouts, exit); + return new Monitor(new JavaProcessLauncher(timeouts), exit); } /** @@ -324,27 +242,24 @@ public class MonitorTest { private final int httpPort; private final String commandKey; private final File tempDir; - private int jmxPort; private HttpProcessClient(String commandKey) throws IOException { - this(commandKey, NetworkUtils.freePort(), NetworkUtils.freePort()); + this(commandKey, NetworkUtils.freePort()); } /** * Use httpPort=-1 to make server fail to start */ - private HttpProcessClient(String commandKey, int httpPort, int jmxPort) throws IOException { + private HttpProcessClient(String commandKey, int httpPort) throws IOException { this.commandKey = commandKey; this.tempDir = temp.newFolder(commandKey); this.httpPort = httpPort; - this.jmxPort = jmxPort; } JavaCommand newCommand() throws IOException { return new JavaCommand(commandKey) .addClasspath(testJar.getAbsolutePath()) .setClassName("org.sonar.process.test.HttpProcess") - .setJmxPort(jmxPort) .setArgument("httpPort", String.valueOf(httpPort)) .setTempDir(tempDir); } @@ -416,7 +331,6 @@ public class MonitorTest { return new JavaCommand("standard") .addClasspath(testJar.getAbsolutePath()) .setClassName("org.sonar.process.test.StandardProcess") - .setJmxPort(NetworkUtils.freePort()) .setTempDir(temp.newFolder()); } diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/RmiJmxConnectorTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/RmiJmxConnectorTest.java deleted file mode 100644 index 2289c446566..00000000000 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/RmiJmxConnectorTest.java +++ /dev/null @@ -1,71 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process.monitor; - -import org.junit.Test; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; -import org.sonar.process.NetworkUtils; -import org.sonar.process.ProcessMXBean; - -import static org.fest.assertions.Assertions.assertThat; -import static org.fest.assertions.Fail.fail; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.when; - -public class RmiJmxConnectorTest { - - @Test - public void fail_to_connect_in_timely_fashion() throws Exception { - RmiJmxConnector connector = new RmiJmxConnector(); - ProcessRef ref = mock(ProcessRef.class); - - JavaCommand command = new JavaCommand("foo").setJmxPort(NetworkUtils.freePort()); - try { - connector.connect(command, ref, 0L); - fail(); - } catch (IllegalStateException e) { - // ok - } - } - - @Test - public void throw_exception_on_timeout() throws Exception { - RmiJmxConnector connector = new RmiJmxConnector(); - ProcessRef ref = mock(ProcessRef.class); - ProcessMXBean mxBean = mock(ProcessMXBean.class); - connector.register(ref, mxBean); - - when(mxBean.isReady()).thenAnswer(new Answer() { - @Override - public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable { - Thread.sleep(Long.MAX_VALUE); - return null; - } - }); - - try { - connector.isReady(ref, 5L); - fail(); - } catch (IllegalStateException e) { - assertThat(e).hasMessage("Fail send JMX request"); - } - } -} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TerminationFailureRmiConnector.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TerminationFailureRmiConnector.java deleted file mode 100644 index 0f9f5702666..00000000000 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TerminationFailureRmiConnector.java +++ /dev/null @@ -1,27 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process.monitor; - -public class TerminationFailureRmiConnector extends RmiJmxConnector { - @Override - public void terminate(ProcessRef processRef, long timeoutMs) { - throw new IllegalStateException("Test - fail to send termination request"); - } -} diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java index 620a59929fc..dc5e63055ae 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java @@ -28,29 +28,13 @@ public class TimeoutsTest { @Test public void test_default_values() throws Exception { Timeouts timeouts = new Timeouts(); - assertThat(timeouts.getMonitorPingInterval()).isGreaterThan(1000L); - assertThat(timeouts.getAutokillPingInterval()).isGreaterThan(1000L); - assertThat(timeouts.getAutokillPingTimeout()).isGreaterThan(1000L); assertThat(timeouts.getTerminationTimeout()).isGreaterThan(1000L); - assertThat(timeouts.getJmxConnectionTimeout()).isGreaterThan(1000L); - assertThat(timeouts.getMonitorIsReadyTimeout()).isGreaterThan(1000L); } @Test public void test_values() throws Exception { Timeouts timeouts = new Timeouts(); - timeouts.setAutokillPingInterval(1L); - timeouts.setAutokillPingTimeout(2L); timeouts.setTerminationTimeout(3L); - timeouts.setJmxConnectionTimeout(4L); - timeouts.setMonitorPingInterval(5L); - timeouts.setMonitorIsReadyTimeout(6L); - - assertThat(timeouts.getAutokillPingInterval()).isEqualTo(1L); - assertThat(timeouts.getAutokillPingTimeout()).isEqualTo(2L); assertThat(timeouts.getTerminationTimeout()).isEqualTo(3L); - assertThat(timeouts.getJmxConnectionTimeout()).isEqualTo(4L); - assertThat(timeouts.getMonitorPingInterval()).isEqualTo(5L); - assertThat(timeouts.getMonitorIsReadyTimeout()).isEqualTo(6L); } } diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java index 45febd53d73..2eb455f18d3 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java @@ -34,6 +34,6 @@ public class WatcherThreadTest { WatcherThread watcher = new WatcherThread(ref, monitor); watcher.start(); watcher.join(); - verify(monitor).stop(); + verify(monitor).stopAsync(); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java b/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java deleted file mode 100644 index c0cf02bcc89..00000000000 --- a/server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process; - -import javax.management.MBeanServer; -import javax.management.MalformedObjectNameException; -import javax.management.ObjectName; -import javax.management.remote.JMXServiceURL; - -import java.lang.management.ManagementFactory; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.MalformedURLException; - -public class JmxUtils { - - private JmxUtils() { - // only static stuff - } - - public static final String DOMAIN = "org.sonar"; - public static final String NAME_PROPERTY = "name"; - - public static final String WEB_SERVER_NAME = "web"; - public static final String SEARCH_SERVER_NAME = "search"; - - public static ObjectName objectName(String name) { - try { - return new ObjectName(DOMAIN, NAME_PROPERTY, name); - } catch (MalformedObjectNameException e) { - throw new IllegalStateException("Cannot create ObjectName for " + name, e); - } - } - - public static void registerMBean(Object mbean, String name) { - try { - MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); - ObjectName oName = objectName(name); - // Check if already registered in JVM (might run multiple instance in JUnits) - if (mbeanServer.isRegistered(oName)) { - mbeanServer.unregisterMBean(oName); - } - mbeanServer.registerMBean(mbean, oName); - } catch (RuntimeException re) { - throw re; - } catch (Exception e) { - throw new IllegalStateException("Fail to register JMX MBean named " + name, e); - } - } - - public static JMXServiceURL serviceUrl(InetAddress host, int port) { - String address = host.getHostAddress(); - if (host instanceof Inet6Address) { - // See http://docs.oracle.com/javase/7/docs/api/javax/management/remote/JMXServiceURL.html - // "The host is a host name, an IPv4 numeric host address, or an IPv6 numeric address enclosed in square brackets." - address = String.format("[%s]", address); - } - try { - return new JMXServiceURL("rmi", address, port, String.format("/jndi/rmi://%s:%d/jmxrmi", address, port)); - } catch (MalformedURLException e) { - throw new IllegalStateException("JMX url does not look well formed", e); - } - } -} diff --git a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java index a492ac751ca..a28d7032410 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java @@ -21,6 +21,10 @@ package org.sonar.process; public class Lifecycle { + public static enum State { + INIT, STARTING, STARTED, STOPPING, STOPPED + } + private State state = State.INIT; public State getState() { @@ -35,7 +39,6 @@ public class Lifecycle { return false; } - @Override public boolean equals(Object o) { if (this == o) { @@ -44,7 +47,6 @@ public class Lifecycle { if (o == null || getClass() != o.getClass()) { return false; } - Lifecycle lifecycle = (Lifecycle) o; return state == lifecycle.state; } diff --git a/server/sonar-process/src/main/java/org/sonar/process/MessageException.java b/server/sonar-process/src/main/java/org/sonar/process/MessageException.java index 5b86ef66c64..6037e35a282 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/MessageException.java +++ b/server/sonar-process/src/main/java/org/sonar/process/MessageException.java @@ -19,6 +19,9 @@ */ package org.sonar.process; +/** + * Unchecked exception that is displayed without stacktrace + */ public class MessageException extends RuntimeException { public MessageException(String message) { super(message); diff --git a/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java b/server/sonar-process/src/main/java/org/sonar/process/Monitored.java similarity index 94% rename from server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java rename to server/sonar-process/src/main/java/org/sonar/process/Monitored.java index 11ddf54beec..4d5b4bcde51 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Monitored.java @@ -19,7 +19,7 @@ */ package org.sonar.process; -public interface MonitoredProcess extends Terminable { +public interface Monitored { /** * Starts process. No need to block until fully started and operational. @@ -36,6 +36,7 @@ public interface MonitoredProcess extends Terminable { /** * Blocks until the process is terminated */ - void awaitTermination(); + void awaitStop(); + void stop(); } diff --git a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java index 074cb8cf5c2..0443d5148be 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java +++ b/server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java @@ -19,6 +19,8 @@ */ package org.sonar.process; +import org.apache.commons.io.IOUtils; + import java.net.ServerSocket; public class NetworkUtils { @@ -27,14 +29,18 @@ public class NetworkUtils { // only static stuff } + /** + * Get an unused port + */ public static int freePort() { + ServerSocket s = null; try { - ServerSocket s = new ServerSocket(0); - int port = s.getLocalPort(); - s.close(); - return port; + s = new ServerSocket(0); + return s.getLocalPort(); } catch (Exception e) { throw new IllegalStateException("Can not find an open network port", e); + } finally { + IOUtils.closeQuietly(s); } } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java index 7137a41fa12..78d3e280802 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java @@ -21,24 +21,19 @@ package org.sonar.process; import org.slf4j.LoggerFactory; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; - -public class ProcessEntryPoint implements ProcessMXBean { +public class ProcessEntryPoint { public static final String PROPERTY_PROCESS_KEY = "process.key"; - public static final String PROPERTY_AUTOKILL_DISABLED = "process.autokill.disabled"; - public static final String PROPERTY_AUTOKILL_PING_TIMEOUT = "process.autokill.pingTimeout"; - public static final String PROPERTY_AUTOKILL_PING_INTERVAL = "process.autokill.pingInterval"; public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout"; + public static final String PROPERTY_STATUS_PATH = "process.statusPath"; private final Props props; private final Lifecycle lifecycle = new Lifecycle(); - private volatile MonitoredProcess monitoredProcess; - private volatile long lastPing = 0L; + private final SharedStatus sharedStatus; + private volatile Monitored monitored; private volatile StopperThread stopperThread; private final SystemExit exit; + private Thread shutdownHook = new Thread(new Runnable() { @Override public void run() { @@ -47,9 +42,10 @@ public class ProcessEntryPoint implements ProcessMXBean { } }); - ProcessEntryPoint(Props props, SystemExit exit) { + ProcessEntryPoint(Props props, SystemExit exit, SharedStatus sharedStatus) { this.props = props; this.exit = exit; + this.sharedStatus = sharedStatus; } public Props getProps() { @@ -59,29 +55,25 @@ public class ProcessEntryPoint implements ProcessMXBean { /** * Launch process and waits until it's down */ - public void launch(MonitoredProcess mp) { - if (!lifecycle.tryToMoveTo(State.STARTING)) { + public void launch(Monitored mp) { + if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) { throw new IllegalStateException("Already started"); } - monitoredProcess = mp; - - // TODO check if these properties are available in System Info - JmxUtils.registerMBean(this, props.nonNullValue(PROPERTY_PROCESS_KEY)); - Runtime.getRuntime().addShutdownHook(shutdownHook); - if (!props.valueAsBoolean(PROPERTY_AUTOKILL_DISABLED, false)) { - // mainly for Java Debugger - scheduleAutokill(); - } + monitored = mp; try { - monitoredProcess.start(); + Runtime.getRuntime().addShutdownHook(shutdownHook); + monitored.start(); boolean ready = false; while (!ready) { - ready = monitoredProcess.isReady(); + ready = monitored.isReady(); Thread.sleep(200L); } - if (lifecycle.tryToMoveTo(State.STARTED)) { - monitoredProcess.awaitTermination(); + + sharedStatus.setReady(); + + if (lifecycle.tryToMoveTo(Lifecycle.State.STARTED)) { + monitored.awaitStop(); } } catch (Exception e) { LoggerFactory.getLogger(getClass()).warn("Fail to start", e); @@ -91,56 +83,30 @@ public class ProcessEntryPoint implements ProcessMXBean { } } - @Override - public boolean isReady() { - return lifecycle.getState() == State.STARTED; - } - - @Override - public void ping() { - lastPing = System.currentTimeMillis(); + boolean isStarted() { + return lifecycle.getState() == Lifecycle.State.STARTED; } /** * Blocks until stopped in a timely fashion (see {@link org.sonar.process.StopperThread}) */ - @Override - public void terminate() { - if (lifecycle.tryToMoveTo(State.STOPPING)) { - stopperThread = new StopperThread(monitoredProcess, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT))); + void terminate() { + if (lifecycle.tryToMoveTo(Lifecycle.State.STOPPING)) { + stopperThread = new StopperThread(monitored, sharedStatus, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT))); stopperThread.start(); } try { // stopperThread is not null for sure // join() does nothing if thread already finished stopperThread.join(); - lifecycle.tryToMoveTo(State.STOPPED); + lifecycle.tryToMoveTo(Lifecycle.State.STOPPED); } catch (InterruptedException e) { // nothing to do, the process is going to be exited } exit.exit(0); } - private void scheduleAutokill() { - final long autokillPingTimeoutMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_TIMEOUT); - long autokillPingIntervalMs = props.valueAsInt(PROPERTY_AUTOKILL_PING_INTERVAL); - Runnable autokiller = new Runnable() { - @Override - public void run() { - long time = System.currentTimeMillis(); - if (time - lastPing > autokillPingTimeoutMs) { - LoggerFactory.getLogger(getClass()).info(String.format( - "Did not receive any ping during %d seconds. Shutting down.", autokillPingTimeoutMs / 1000)); - terminate(); - } - } - }; - lastPing = System.currentTimeMillis(); - ScheduledExecutorService monitor = Executors.newScheduledThreadPool(1); - monitor.scheduleWithFixedDelay(autokiller, autokillPingIntervalMs, autokillPingIntervalMs, TimeUnit.MILLISECONDS); - } - - State getState() { + Lifecycle.State getState() { return lifecycle.getState(); } @@ -150,6 +116,6 @@ public class ProcessEntryPoint implements ProcessMXBean { public static ProcessEntryPoint createForArguments(String[] args) { Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); - return new ProcessEntryPoint(props, new SystemExit()); + return new ProcessEntryPoint(props, new SystemExit(), new SharedStatus(props.nonNullValueAsFile(PROPERTY_STATUS_PATH))); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java index dacddd91847..c0909a93fb5 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java @@ -41,7 +41,6 @@ public class ProcessLogging { // StatusPrinter will handle this } StatusPrinter.printInCaseOfErrorsOrWarnings(context); - } /** diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java deleted file mode 100644 index 3d024d420eb..00000000000 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process; - -public interface ProcessMXBean extends Terminable { - - boolean isReady(); - - void ping(); - -} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java index bdefa116949..5548cc5d5de 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java @@ -47,28 +47,25 @@ public class ProcessUtils { } /** - * Destroys process (equivalent to kill -9) if alive - * @return true if the process was destroyed, false if process is null or already destroyed. + * Send kill signal to stop process. Shutdown hooks are executed. It's the equivalent of SIGTERM on Linux. + * Correctly tested on Java 6 and 7 on both Mac/MSWindows + * @return true if the signal is sent, false if process is already down */ - public static boolean destroyQuietly(@Nullable Process process) { - boolean destroyed = false; + public static boolean sendKillSignal(@Nullable Process process) { + boolean sentSignal = false; if (isAlive(process)) { try { process.destroy(); - while (isAlive(process)) { - // destroy() sends the signal, it does not wait for the process to be down - Thread.sleep(100L); - } - destroyed = true; + sentSignal = true; } catch (Exception e) { - LoggerFactory.getLogger(ProcessUtils.class).error("Fail to destroy " + process); + LoggerFactory.getLogger(ProcessUtils.class).error("Fail to kill " + process); } } - return destroyed; + return sentSignal; } public static void closeStreams(@Nullable Process process) { - if (process != null) { + if (process!=null) { IOUtils.closeQuietly(process.getInputStream()); IOUtils.closeQuietly(process.getOutputStream()); IOUtils.closeQuietly(process.getErrorStream()); diff --git a/server/sonar-process/src/main/java/org/sonar/process/Props.java b/server/sonar-process/src/main/java/org/sonar/process/Props.java index b868702eafc..852c2780158 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/Props.java +++ b/server/sonar-process/src/main/java/org/sonar/process/Props.java @@ -77,7 +77,7 @@ public class Props { public File nonNullValueAsFile(String key) { String s = value(key); if (s == null) { - throw new IllegalArgumentException("Property " + key + " is missing"); + throw new IllegalArgumentException("Property " + key + " is not set"); } return new File(s); } diff --git a/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java b/server/sonar-process/src/main/java/org/sonar/process/SharedStatus.java similarity index 53% rename from server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java rename to server/sonar-process/src/main/java/org/sonar/process/SharedStatus.java index 2045cd4516d..bbc132044ca 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java +++ b/server/sonar-process/src/main/java/org/sonar/process/SharedStatus.java @@ -20,40 +20,44 @@ package org.sonar.process; import org.apache.commons.io.FileUtils; -import org.junit.After; -import org.junit.Before; -import org.junit.Rule; -import org.junit.rules.TemporaryFolder; import java.io.File; import java.io.IOException; -import java.net.ServerSocket; -public abstract class BaseProcessTest { +public class SharedStatus { - @Rule - public TemporaryFolder temp = new TemporaryFolder(); + private final File file; - public static final String DUMMY_OK_APP = "org.sonar.application.DummyOkProcess"; - - int freePort; - File dummyAppJar; - Process proc; + public SharedStatus(File file) { + this.file = file; + } - @Before - public void setup() throws IOException { - ServerSocket socket = new ServerSocket(0); - freePort = socket.getLocalPort(); - socket.close(); + /** + * Executed by monitor - remove existing shared file before starting child process + */ + public void prepare() { + if (file.exists()) { + if (!file.delete()) { + throw new MessageException(String.format( + "Fail to delete file %s. Please check that no SonarQube process is alive", file)); + } + } + } - dummyAppJar = FileUtils.toFile(getClass().getResource("/sonar-dummy-app.jar")); + public boolean wasStartedAfter(long launchedAt) { + // File#lastModified() can have second precision on some OS + return file.exists() && file.lastModified() / 1000 >= launchedAt / 1000; } - @After - public void tearDown() { - if (proc != null) { - ProcessUtils.destroyQuietly(proc); + public void setReady() { + try { + FileUtils.touch(file); + } catch (IOException e) { + throw new IllegalStateException("Fail to create file " + file, e); } } + public void setStopped() { + FileUtils.deleteQuietly(file); + } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/State.java b/server/sonar-process/src/main/java/org/sonar/process/State.java deleted file mode 100644 index 9d773d2bc23..00000000000 --- a/server/sonar-process/src/main/java/org/sonar/process/State.java +++ /dev/null @@ -1,26 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process; - -public enum State { - - INIT, STARTING, STARTED, STOPPING, STOPPED - -} diff --git a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java index 97e2183e985..ae047363e08 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java +++ b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java @@ -27,17 +27,19 @@ import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; /** - * Gracefully stops process, but exits JVM if too long + * Gracefully stops process in a timely fashion */ class StopperThread extends Thread { - private final Terminable terminable; + private final Monitored monitored; private final long terminationTimeout; + private final SharedStatus sharedStatus; - StopperThread(Terminable terminable, long terminationTimeout) { + StopperThread(Monitored monitored, SharedStatus sharedStatus, long terminationTimeout) { super("Stopper"); - this.terminable = terminable; + this.monitored = monitored; this.terminationTimeout = terminationTimeout; + this.sharedStatus = sharedStatus; } @Override @@ -46,15 +48,15 @@ class StopperThread extends Thread { Future future = executor.submit(new Runnable() { @Override public void run() { - terminable.terminate(); + monitored.stop(); } }); try { future.get(terminationTimeout, TimeUnit.MILLISECONDS); } catch (Exception e) { - LoggerFactory.getLogger(getClass()).error("Can not terminate in " + terminationTimeout + "ms", e); - } finally { - executor.shutdownNow(); + LoggerFactory.getLogger(getClass()).error(String.format("Can not stop in %dms", terminationTimeout), e); } + executor.shutdownNow(); + sharedStatus.setStopped(); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/Terminable.java b/server/sonar-process/src/main/java/org/sonar/process/Terminable.java deleted file mode 100644 index a8670609fe2..00000000000 --- a/server/sonar-process/src/main/java/org/sonar/process/Terminable.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process; - -/** - * This term "terminate" is used in order to not conflict with {@link Thread#stop()}. - */ -public interface Terminable { - - void terminate(); -} diff --git a/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java deleted file mode 100644 index 0e9d320cd74..00000000000 --- a/server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java +++ /dev/null @@ -1,127 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process; - -import org.junit.Test; - -import javax.management.MBeanServer; -import javax.management.ObjectName; -import javax.management.remote.JMXServiceURL; - -import java.lang.management.ManagementFactory; -import java.net.Inet4Address; -import java.net.Inet6Address; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; -import java.util.Enumeration; - -import static org.fest.assertions.Assertions.assertThat; -import static org.fest.assertions.Fail.fail; - -public class JmxUtilsTest { - - class MyBean implements ProcessMXBean { - - - @Override - public void terminate() { - - } - - @Override - public void ping() { - - } - - @Override - public boolean isReady() { - return true; - } - } - - @Test - public void construct_jmx_objectName() throws Exception { - MyBean mxBean = new MyBean(); - ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName()); - assertThat(objectName).isNotNull(); - assertThat(objectName.getDomain()).isEqualTo(JmxUtils.DOMAIN); - assertThat(objectName.getKeyProperty(JmxUtils.NAME_PROPERTY)).isEqualTo(mxBean.getClass().getSimpleName()); - } - - @Test - public void fail_jmx_objectName() throws Exception { - try { - JmxUtils.objectName(":"); - fail(); - } catch (Exception e) { - assertThat(e.getMessage()).isEqualTo("Cannot create ObjectName for :"); - } - } - - @Test - public void register_mbean() throws Exception { - // 0 Get mbServer and create out test MXBean - MBeanServer mbeanServer = ManagementFactory.getPlatformMBeanServer(); - MyBean mxBean = new MyBean(); - ObjectName objectName = JmxUtils.objectName(mxBean.getClass().getSimpleName()); - - // 1 assert that mxBean gets registered - assertThat(mbeanServer.isRegistered(objectName)).isFalse(); - JmxUtils.registerMBean(mxBean, mxBean.getClass().getSimpleName()); - assertThat(mbeanServer.isRegistered(objectName)).isTrue(); - - try { - JmxUtils.registerMBean(new Object(), ""); - fail(); - } catch (IllegalStateException e) { - // ok - } - } - - @Test - public void serviceUrl_ipv4() throws Exception { - JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet4Address.class), 1234); - assertThat(url).isNotNull(); - assertThat(url.getPort()).isEqualTo(1234); - } - - @Test - public void serviceUrl_ipv6() throws Exception { - JMXServiceURL url = JmxUtils.serviceUrl(ip(Inet6Address.class), 1234); - assertThat(url).isNotNull(); - assertThat(url.getPort()).isEqualTo(1234); - } - - private static InetAddress ip(Class inetAddressClass) throws SocketException { - Enumeration ifaces = NetworkInterface.getNetworkInterfaces(); - while (ifaces.hasMoreElements()) { - NetworkInterface iface = ifaces.nextElement(); - Enumeration addresses = iface.getInetAddresses(); - while (addresses.hasMoreElements()) { - InetAddress addr = addresses.nextElement(); - if (addr.getClass().isAssignableFrom(inetAddressClass)) { - return addr; - } - } - } - throw new IllegalStateException("no ipv4 address"); - } -} diff --git a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java index 23886125212..0ab9f3a29ba 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java @@ -22,6 +22,7 @@ package org.sonar.process; import org.junit.Test; import static org.fest.assertions.Assertions.assertThat; +import static org.sonar.process.Lifecycle.State; public class LifecycleTest { @@ -39,4 +40,16 @@ public class LifecycleTest { stopping.tryToMoveTo(State.STOPPING); assertThat(stopping).isNotEqualTo(init); } + + @Test + public void try_to_move() throws Exception { + Lifecycle lifecycle = new Lifecycle(); + assertThat(lifecycle.getState()).isEqualTo(State.INIT); + + assertThat(lifecycle.tryToMoveTo(State.STARTED)).isTrue(); + assertThat(lifecycle.getState()).isEqualTo(State.STARTED); + + assertThat(lifecycle.tryToMoveTo(State.STARTING)).isFalse(); + assertThat(lifecycle.getState()).isEqualTo(State.STARTED); + } } diff --git a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java index 09f6a597209..9eb41dbf6ac 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java @@ -27,11 +27,10 @@ import static org.fest.assertions.Assertions.assertThat; public class NetworkUtilsTest { - @Test public void find_free_port() throws Exception { int port = NetworkUtils.freePort(); - assertThat(port).isGreaterThan(1024); + assertThat(port).isGreaterThan(0); } @Test @@ -58,4 +57,4 @@ public class NetworkUtilsTest { assertThat(port1).isNotSameAs(port2); } -} \ No newline at end of file +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java index 03cd7409a49..7e51a0d6ac9 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java @@ -32,6 +32,7 @@ import java.util.Properties; import static org.fest.assertions.Assertions.assertThat; import static org.fest.assertions.Fail.fail; import static org.mockito.Mockito.mock; +import static org.sonar.process.Lifecycle.State; public class ProcessEntryPointTest { @@ -49,47 +50,29 @@ public class ProcessEntryPointTest { @Test public void load_properties_from_file() throws Exception { File propsFile = temp.newFile(); - FileUtils.write(propsFile, "sonar.foo=bar"); + FileUtils.write(propsFile, "sonar.foo=bar\nprocess.key=web\nprocess.statusPath=status.temp"); - ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[]{propsFile.getAbsolutePath()}); + ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(new String[] {propsFile.getAbsolutePath()}); assertThat(entryPoint.getProps().value("sonar.foo")).isEqualTo("bar"); + assertThat(entryPoint.getProps().value("process.key")).isEqualTo("web"); } @Test public void test_initial_state() throws Exception { Props props = new Props(new Properties()); - ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class)); assertThat(entryPoint.getProps()).isSameAs(props); - assertThat(entryPoint.isReady()).isFalse(); + assertThat(entryPoint.isStarted()).isFalse(); assertThat(entryPoint.getState()).isEqualTo(State.INIT); - - // do not fail - entryPoint.ping(); - } - - @Test - public void fail_to_launch_if_missing_monitor_properties() throws Exception { - Props props = new Props(new Properties()); - ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); - - StandardProcess process = new StandardProcess(); - try { - entryPoint.launch(process); - fail(); - } catch (IllegalArgumentException e) { - assertThat(e).hasMessage("Missing property: process.key"); - assertThat(process.getState()).isEqualTo(State.INIT); - } } @Test public void fail_to_launch_multiple_times() throws Exception { Props props = new Props(new Properties()); props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test"); - props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); - ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class)); entryPoint.launch(new NoopProcess()); try { @@ -104,9 +87,8 @@ public class ProcessEntryPointTest { public void launch_then_request_graceful_termination() throws Exception { Props props = new Props(new Properties()); props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test"); - props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); - final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class)); final StandardProcess process = new StandardProcess(); Thread runner = new Thread() { @@ -129,28 +111,12 @@ public class ProcessEntryPointTest { assertThat(process.getState()).isEqualTo(State.STOPPED); } - @Test - public void autokill_if_no_pings() throws Exception { - Props props = new Props(new Properties()); - props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "test"); - props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); - props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_INTERVAL, "5"); - props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_PING_TIMEOUT, "1"); - final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); - final StandardProcess process = new StandardProcess(); - - entryPoint.launch(process); - - assertThat(process.getState()).isEqualTo(State.STOPPED); - } - @Test public void terminate_if_unexpected_shutdown() throws Exception { Props props = new Props(new Properties()); props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo"); - props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); - final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); + final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class)); final StandardProcess process = new StandardProcess(); Thread runner = new Thread() { @@ -177,16 +143,15 @@ public class ProcessEntryPointTest { public void terminate_if_startup_error() throws Exception { Props props = new Props(new Properties()); props.set(ProcessEntryPoint.PROPERTY_PROCESS_KEY, "foo"); - props.set(ProcessEntryPoint.PROPERTY_AUTOKILL_DISABLED, "true"); props.set(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, "30000"); - final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit); - final MonitoredProcess process = new StartupErrorProcess(); + final ProcessEntryPoint entryPoint = new ProcessEntryPoint(props, exit, mock(SharedStatus.class)); + final Monitored process = new StartupErrorProcess(); entryPoint.launch(process); assertThat(entryPoint.getState()).isEqualTo(State.STOPPED); } - private static class NoopProcess implements MonitoredProcess { + private static class NoopProcess implements Monitored { @Override public void start() { @@ -199,17 +164,17 @@ public class ProcessEntryPointTest { } @Override - public void awaitTermination() { + public void awaitStop() { } @Override - public void terminate() { + public void stop() { } } - private static class StartupErrorProcess implements MonitoredProcess { + private static class StartupErrorProcess implements Monitored { @Override public void start() { @@ -222,12 +187,12 @@ public class ProcessEntryPointTest { } @Override - public void awaitTermination() { + public void awaitStop() { } @Override - public void terminate() { + public void stop() { } } diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java deleted file mode 100644 index 6f93126516e..00000000000 --- a/server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java +++ /dev/null @@ -1,28 +0,0 @@ -/* - * SonarQube, open source software quality management tool. - * Copyright (C) 2008-2014 SonarSource - * mailto:contact AT sonarsource DOT com - * - * SonarQube is free software; you can redistribute it and/or - * modify it under the terms of the GNU Lesser General Public - * License as published by the Free Software Foundation; either - * version 3 of the License, or (at your option) any later version. - * - * SonarQube is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with this program; if not, write to the Free Software Foundation, - * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - */ -package org.sonar.process; - -import org.junit.Test; - -import static org.fest.assertions.Assertions.assertThat; - -public class ProcessUtilsTest { - -} diff --git a/server/sonar-process/src/test/java/org/sonar/process/SharedStatusTest.java b/server/sonar-process/src/test/java/org/sonar/process/SharedStatusTest.java new file mode 100644 index 00000000000..c6703a9c237 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/SharedStatusTest.java @@ -0,0 +1,101 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; + +import java.io.File; + +import static org.fest.assertions.Assertions.assertThat; +import static org.fest.assertions.Fail.fail; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public class SharedStatusTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + @Test + public void prepare() throws Exception { + File file = temp.newFile(); + assertThat(file).exists(); + + SharedStatus sharedStatus = new SharedStatus(file); + sharedStatus.prepare(); + assertThat(file).doesNotExist(); + } + + @Test + public void fail_to_prepare_if_file_is_locked() throws Exception { + File file = mock(File.class); + when(file.exists()).thenReturn(true); + when(file.delete()).thenReturn(false); + + SharedStatus sharedStatus = new SharedStatus(file); + try { + sharedStatus.prepare(); + fail(); + } catch (MessageException e) { + // ok + } + } + + @Test + public void create_file_when_ready_then_delete_when_stopped() throws Exception { + File file = new File(temp.newFolder(), "foo.txt"); + assertThat(file).doesNotExist(); + + SharedStatus sharedStatus = new SharedStatus(file); + sharedStatus.setReady(); + assertThat(file).exists(); + + sharedStatus.setStopped(); + assertThat(file).doesNotExist(); + } + + @Test + public void was_started_after() throws Exception { + File file = mock(File.class); + SharedStatus sharedStatus = new SharedStatus(file); + + // does not exist + when(file.exists()).thenReturn(false); + when(file.lastModified()).thenReturn(123456L); + assertThat(sharedStatus.wasStartedAfter(122000L)).isFalse(); + + // file created before + when(file.exists()).thenReturn(true); + when(file.lastModified()).thenReturn(123456L); + assertThat(sharedStatus.wasStartedAfter(124000L)).isFalse(); + + // file created after + when(file.exists()).thenReturn(true); + when(file.lastModified()).thenReturn(123456L); + assertThat(sharedStatus.wasStartedAfter(123123L)).isTrue(); + + // file created after, but can be truncated to second on some OS + when(file.exists()).thenReturn(true); + when(file.lastModified()).thenReturn(123000L); + assertThat(sharedStatus.wasStartedAfter(123456L)).isTrue(); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java b/server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java new file mode 100644 index 00000000000..9e45fe7f080 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java @@ -0,0 +1,79 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.TemporaryFolder; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.io.File; + +import static org.fest.assertions.Assertions.assertThat; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; + +public class StopperThreadTest { + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + + + @Test(timeout = 3000L) + public void stop_in_a_timely_fashion() throws Exception { + File file = temp.newFile(); + SharedStatus sharedStatus = new SharedStatus(file); + assertThat(file).exists(); + Monitored monitored = mock(Monitored.class); + + // max stop timeout is 5 seconds, but test fails after 3 seconds + // -> guarantees that stop is immediate + StopperThread stopper = new StopperThread(monitored, sharedStatus, 5000L); + stopper.start(); + stopper.join(); + + verify(monitored).stop(); + assertThat(file).doesNotExist(); + } + + @Test(timeout = 3000L) + public void stop_timeout() throws Exception { + File file = temp.newFile(); + SharedStatus sharedStatus = new SharedStatus(file); + assertThat(file).exists(); + Monitored monitored = mock(Monitored.class); + doAnswer(new Answer() { + @Override + public Object answer(InvocationOnMock invocationOnMock) throws Throwable { + Thread.sleep(10000L); + return null; + } + }).when(monitored).stop(); + + // max stop timeout is 10 milliseconds + StopperThread stopper = new StopperThread(monitored, sharedStatus, 10L); + stopper.start(); + stopper.join(); + + verify(monitored).stop(); + assertThat(file).doesNotExist(); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java index f6ca587a588..0b2139ab693 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java +++ b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java @@ -24,7 +24,7 @@ import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.eclipse.jetty.server.handler.ContextHandler; -import org.sonar.process.MonitoredProcess; +import org.sonar.process.Monitored; import org.sonar.process.ProcessEntryPoint; import javax.servlet.ServletException; @@ -39,7 +39,7 @@ import java.io.IOException; * It also pushes status to temp files, so test can verify what was really done (when server went ready state and * if it was gracefully terminated) */ -public class HttpProcess implements MonitoredProcess { +public class HttpProcess implements Monitored { private final Server server; private boolean ready = false; @@ -78,18 +78,22 @@ public class HttpProcess implements MonitoredProcess { @Override public boolean isReady() { + System.out.println("received isReady()"); if (ready) { return true; } + System.out.println("checking server.isStarted()"); if (server.isStarted()) { + System.out.println("moving to ready"); ready = true; writeTimeToFile("readyAt"); } - return false; + System.out.println("ready: " + ready); + return ready; } @Override - public void awaitTermination() { + public void awaitStop() { try { server.join(); } catch (InterruptedException ignore) { @@ -98,9 +102,10 @@ public class HttpProcess implements MonitoredProcess { } @Override - public void terminate() { + public void stop() { try { if (!server.isStopped()) { + System.out.println("HttpProcess stopping"); server.stop(); writeTimeToFile("terminatedAt"); } diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java new file mode 100644 index 00000000000..55131045507 --- /dev/null +++ b/server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java @@ -0,0 +1,85 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process.test; + +import org.sonar.process.Monitored; +import org.sonar.process.ProcessEntryPoint; +import org.sonar.process.Lifecycle.State; + +public class InfiniteTerminationProcess implements Monitored { + + private State state = State.INIT; + + private final Thread daemon = new Thread() { + @Override + public void run() { + try { + while (true) { + Thread.sleep(100L); + } + } catch (InterruptedException e) { + // return + } + } + }; + + /** + * Blocks until started() + */ + @Override + public void start() { + state = State.STARTING; + daemon.start(); + state = State.STARTED; + } + + @Override + public boolean isReady() { + return state == State.STARTED; + } + + @Override + public void awaitStop() { + try { + daemon.join(); + } catch (InterruptedException e) { + // interrupted by call to terminate() + } + } + + /** + * Blocks until stopped + */ + @Override + public void stop() { + state = State.STOPPING; + try { + daemon.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + } + state = State.STOPPED; + } + + public static void main(String[] args) { + ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); + entryPoint.launch(new InfiniteTerminationProcess()); + } +} diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java index 4c1a33221c6..cdfe6d76af1 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java +++ b/server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java @@ -19,11 +19,11 @@ */ package org.sonar.process.test; -import org.sonar.process.MonitoredProcess; +import org.sonar.process.Monitored; import org.sonar.process.ProcessEntryPoint; -import org.sonar.process.State; +import org.sonar.process.Lifecycle.State; -public class StandardProcess implements MonitoredProcess { +public class StandardProcess implements Monitored { private State state = State.INIT; @@ -56,7 +56,7 @@ public class StandardProcess implements MonitoredProcess { } @Override - public void awaitTermination() { + public void awaitStop() { try { daemon.join(); } catch (InterruptedException e) { @@ -68,7 +68,7 @@ public class StandardProcess implements MonitoredProcess { * Blocks until stopped */ @Override - public void terminate() { + public void stop() { state = State.STOPPING; daemon.interrupt(); state = State.STOPPED; diff --git a/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java b/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java index d2aa111e7e5..cb039b55162 100644 --- a/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java +++ b/server/sonar-search/src/main/java/org/sonar/search/SearchServer.java @@ -27,7 +27,7 @@ import org.elasticsearch.node.Node; import org.elasticsearch.node.NodeBuilder; import org.slf4j.LoggerFactory; import org.sonar.process.MinimumViableSystem; -import org.sonar.process.MonitoredProcess; +import org.sonar.process.Monitored; import org.sonar.process.ProcessEntryPoint; import org.sonar.process.ProcessLogging; import org.sonar.process.Props; @@ -39,7 +39,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.Set; -public class SearchServer implements MonitoredProcess { +public class SearchServer implements Monitored { public static final String SONAR_NODE_NAME = "sonar.node.name"; public static final String ES_PORT_PROPERTY = "sonar.search.port"; @@ -55,7 +55,6 @@ public class SearchServer implements MonitoredProcess { private final Set nodes = new HashSet(); private final Props props; - private final Object lock = new Object(); private Node node; @@ -71,79 +70,77 @@ public class SearchServer implements MonitoredProcess { @Override public void start() { - synchronized (lock) { - Integer port = props.valueAsInt(ES_PORT_PROPERTY); - String clusterName = props.value(ES_CLUSTER_PROPERTY); - - LoggerFactory.getLogger(SearchServer.class).info("Starting ES[{}] on port: {}", clusterName, port); - - ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder() - - // Disable MCast - .put("discovery.zen.ping.multicast.enabled", "false") - - // Index storage policies - .put("index.merge.policy.max_merge_at_once", "200") - .put("index.merge.policy.segments_per_tier", "200") - .put("index.number_of_shards", "1") - .put("index.number_of_replicas", MINIMUM_INDEX_REPLICATION) - .put("index.store.type", "mmapfs") - .put("indices.store.throttle.type", "merge") - .put("indices.store.throttle.max_bytes_per_sec", "200mb") - - // Install our own listUpdate scripts - .put("script.default_lang", "native") - .put("script.native." + ListUpdate.NAME + ".type", ListUpdate.UpdateListScriptFactory.class.getName()) - - // Node is pure transport - .put("transport.tcp.port", port) - .put("http.enabled", false) - - // Setting up ES paths - .put("path.data", esDataDir().getAbsolutePath()) - .put("path.work", esWorkDir().getAbsolutePath()) - .put("path.logs", esLogDir().getAbsolutePath()); - - if (!nodes.isEmpty()) { - - LoggerFactory.getLogger(SearchServer.class).info("Joining ES cluster with master: {}", nodes); - esSettings.put("discovery.zen.ping.unicast.hosts", StringUtils.join(nodes, ",")); - esSettings.put("node.master", false); - // Enforce a N/2+1 number of masters in cluster - esSettings.put("discovery.zen.minimum_master_nodes", 1); - // Change master pool requirement when in distributed mode - // esSettings.put("discovery.zen.minimum_master_nodes", (int) Math.floor(nodes.size() / 2.0) + 1); - } + Integer port = props.valueAsInt(ES_PORT_PROPERTY); + String clusterName = props.value(ES_CLUSTER_PROPERTY); + + LoggerFactory.getLogger(SearchServer.class).info("Starting ES[{}] on port: {}", clusterName, port); + + ImmutableSettings.Builder esSettings = ImmutableSettings.settingsBuilder() + + // Disable MCast + .put("discovery.zen.ping.multicast.enabled", "false") + + // Index storage policies + .put("index.merge.policy.max_merge_at_once", "200") + .put("index.merge.policy.segments_per_tier", "200") + .put("index.number_of_shards", "1") + .put("index.number_of_replicas", MINIMUM_INDEX_REPLICATION) + .put("index.store.type", "mmapfs") + .put("indices.store.throttle.type", "merge") + .put("indices.store.throttle.max_bytes_per_sec", "200mb") + + // Install our own listUpdate scripts + .put("script.default_lang", "native") + .put("script.native." + ListUpdate.NAME + ".type", ListUpdate.UpdateListScriptFactory.class.getName()) + + // Node is pure transport + .put("transport.tcp.port", port) + .put("http.enabled", false) + + // Setting up ES paths + .put("path.data", esDataDir().getAbsolutePath()) + .put("path.work", esWorkDir().getAbsolutePath()) + .put("path.logs", esLogDir().getAbsolutePath()); + + if (!nodes.isEmpty()) { + + LoggerFactory.getLogger(SearchServer.class).info("Joining ES cluster with master: {}", nodes); + esSettings.put("discovery.zen.ping.unicast.hosts", StringUtils.join(nodes, ",")); + esSettings.put("node.master", false); + // Enforce a N/2+1 number of masters in cluster + esSettings.put("discovery.zen.minimum_master_nodes", 1); + // Change master pool requirement when in distributed mode + // esSettings.put("discovery.zen.minimum_master_nodes", (int) Math.floor(nodes.size() / 2.0) + 1); + } - // Set cluster coordinates - esSettings.put("cluster.name", clusterName); - esSettings.put("node.rack_id", props.value(SONAR_NODE_NAME, "unknown")); - esSettings.put("cluster.routing.allocation.awareness.attributes", "rack_id"); - if (props.contains(SONAR_NODE_NAME)) { - esSettings.put("node.name", props.value(SONAR_NODE_NAME)); - } else { - try { - esSettings.put("node.name", InetAddress.getLocalHost().getHostName()); - } catch (Exception e) { - LoggerFactory.getLogger(SearchServer.class).warn("Could not determine hostname", e); - esSettings.put("node.name", "sq-" + System.currentTimeMillis()); - } + // Set cluster coordinates + esSettings.put("cluster.name", clusterName); + esSettings.put("node.rack_id", props.value(SONAR_NODE_NAME, "unknown")); + esSettings.put("cluster.routing.allocation.awareness.attributes", "rack_id"); + if (props.contains(SONAR_NODE_NAME)) { + esSettings.put("node.name", props.value(SONAR_NODE_NAME)); + } else { + try { + esSettings.put("node.name", InetAddress.getLocalHost().getHostName()); + } catch (Exception e) { + LoggerFactory.getLogger(SearchServer.class).warn("Could not determine hostname", e); + esSettings.put("node.name", "sq-" + System.currentTimeMillis()); } + } - // Make sure the index settings are up to date. - initAnalysis(esSettings); + // Make sure the index settings are up to date. + initAnalysis(esSettings); - // And building our ES Node - node = NodeBuilder.nodeBuilder() - .settings(esSettings) - .build().start(); + // And building our ES Node + node = NodeBuilder.nodeBuilder() + .settings(esSettings) + .build().start(); - node.client().admin().indices() - .preparePutTemplate("default") - .setTemplate("*") - .addMapping("_default_", "{\"dynamic\": \"strict\"}") - .get(); - } + node.client().admin().indices() + .preparePutTemplate("default") + .setTemplate("*") + .addMapping("_default_", "{\"dynamic\": \"strict\"}") + .get(); } @Override @@ -156,7 +153,7 @@ public class SearchServer implements MonitoredProcess { } @Override - public void awaitTermination() { + public void awaitStop() { while (node != null && !node.isClosed()) { try { Thread.sleep(200L); @@ -251,11 +248,9 @@ public class SearchServer implements MonitoredProcess { } @Override - public void terminate() { - synchronized (lock) { - if (!node.isClosed()) { - node.close(); - } + public synchronized void stop() { + if (!node.isClosed()) { + node.close(); } } diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java index 01a5305c53d..a4c6ebc59ad 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java @@ -85,10 +85,15 @@ class EmbeddedTomcat { } void terminate() { + LoggerFactory.getLogger(getClass()).info("--- RECEIVED TERMINATE TOMCAT"); if (tomcat.getServer().getState().isAvailable()) { try { + LoggerFactory.getLogger(getClass()).info("--- STOP TOMCAT"); tomcat.stop(); tomcat.destroy(); + LoggerFactory.getLogger(getClass()).info("--- TOMCAT TERMINATION - WAIT 10s"); + Thread.sleep(10000L); + LoggerFactory.getLogger(getClass()).info("--- TOMCAT TERMINATED AFTER 10s"); } catch (Exception e) { LoggerFactory.getLogger(EmbeddedTomcat.class).error("Fail to stop web server", e); } diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java index 87ffa5c5bee..24919d9d6e2 100644 --- a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java +++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java @@ -20,11 +20,11 @@ package org.sonar.server.app; import org.sonar.process.MinimumViableSystem; -import org.sonar.process.MonitoredProcess; +import org.sonar.process.Monitored; import org.sonar.process.ProcessEntryPoint; import org.sonar.process.Props; -public class WebServer implements MonitoredProcess { +public class WebServer implements Monitored { private final EmbeddedTomcat tomcat; @@ -46,12 +46,12 @@ public class WebServer implements MonitoredProcess { } @Override - public void terminate() { + public void stop() { tomcat.terminate(); } @Override - public void awaitTermination() { + public void awaitStop() { tomcat.awaitTermination(); } diff --git a/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java b/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java index 20689769478..555fea545c3 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java @@ -25,10 +25,10 @@ import org.junit.Test; import org.junit.rules.ExpectedException; import org.sonar.api.config.Settings; import org.sonar.api.database.DatabaseProperties; +import org.sonar.process.NetworkUtils; import java.io.File; import java.io.IOException; -import java.net.ServerSocket; import java.sql.DriverManager; import static junit.framework.Assert.fail; @@ -41,7 +41,7 @@ public class EmbeddedDatabaseTest { @Test(timeout = 10000) public void should_start_and_stop() throws IOException { - int port = freeServerPort(); + int port = NetworkUtils.freePort(); EmbeddedDatabase database = new EmbeddedDatabase(testSettings(port)); database.start(); @@ -59,7 +59,7 @@ public class EmbeddedDatabaseTest { @Test(timeout = 10000) public void should_support_memory_database() throws IOException { - int port = freeServerPort(); + int port = NetworkUtils.freePort(); EmbeddedDatabase database = new EmbeddedDatabase(testSettings(port) .setProperty(DatabaseProperties.PROP_URL, "jdbc:h2:tcp://localhost:" + port + "/mem:sonarIT;USER=sonar;PASSWORD=sonar")); @@ -94,10 +94,4 @@ public class EmbeddedDatabaseTest { .setProperty(DatabaseProperties.PROP_EMBEDDED_PORT, "" + port) .setProperty("sonar.path.data", "./target/testDB"); } - - static int freeServerPort() throws IOException { - ServerSocket srv = new ServerSocket(0); - srv.close(); - return srv.getLocalPort(); - } } diff --git a/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java b/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java index 8270a46f06e..2520f83c2ab 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java +++ b/server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java @@ -29,7 +29,6 @@ import org.junit.ClassRule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.sonar.api.config.Settings; -import org.sonar.process.MonitoredProcess; import org.sonar.process.NetworkUtils; import org.sonar.process.Props; import org.sonar.search.SearchServer; @@ -72,7 +71,7 @@ public class BaseIndexTest { @AfterClass public static void teardownSearchEngine() { - searchServer.terminate(); + searchServer.stop(); } @Before diff --git a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java index 1a935cb2394..43ef301e1f6 100644 --- a/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java +++ b/server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java @@ -139,7 +139,7 @@ public class ServerTester extends ExternalResource { */ public void stop() { platform.doStop(); - searchServer.terminate(); + searchServer.stop(); FileUtils.deleteQuietly(homeDir); } diff --git a/sonar-application/src/main/assembly/conf/sonar.properties b/sonar-application/src/main/assembly/conf/sonar.properties index 8cae11e3cca..f14700b3592 100644 --- a/sonar-application/src/main/assembly/conf/sonar.properties +++ b/sonar-application/src/main/assembly/conf/sonar.properties @@ -69,11 +69,6 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000 #sonar.web.javaOpts=-Xmx768m -XX:MaxPermSize=160m -XX:+HeapDumpOnOutOfMemoryError \ # -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djruby.management.enabled=false -# Web server requires a JMX RMI port to be open. Default is 9003. A free port is -# dynamically used is value is 0. -# This JMX port must be private and must not be exposed to the Internet. -#sonar.web.jmxPort=9003 - # Binding IP address. For servers with more than one IP address, this property specifies which # address will be used for listening on the specified ports. # By default, ports will be used on all IP addresses associated with the server. @@ -182,10 +177,6 @@ sonar.jdbc.timeBetweenEvictionRunsMillis=30000 # This port must be private and must not be exposed to the Internet. #sonar.search.port=9001 -# JMX RMI port to monitor Elasticsearch process. Default is 9002. Use 0 to get a free port. -# It must be private and must not be exposed to the Internet. -#sonar.search.jmxPort=9002 - #-------------------------------------------------------------------------------------------------- # UPDATE CENTER diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java index 600828ec1db..db1f9fcb5f6 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -21,12 +21,9 @@ package org.sonar.application; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; -import org.sonar.process.JmxUtils; import org.sonar.process.MinimumViableSystem; import org.sonar.process.ProcessLogging; -import org.sonar.process.ProcessMXBean; import org.sonar.process.Props; -import org.sonar.process.State; import org.sonar.process.monitor.JavaCommand; import org.sonar.process.monitor.Monitor; @@ -38,7 +35,7 @@ import java.util.Properties; /** * Entry-point of process that starts and monitors elasticsearch and web servers */ -public class App implements ProcessMXBean { +public class App { private final Monitor monitor; @@ -48,7 +45,6 @@ public class App implements ProcessMXBean { App(Monitor monitor) { this.monitor = monitor; - JmxUtils.registerMBean(this, "SonarQube"); } public void start(Props props) { @@ -60,10 +56,9 @@ public class App implements ProcessMXBean { List commands = new ArrayList(); File homeDir = props.nonNullValueAsFile("sonar.path.home"); File tempDir = props.nonNullValueAsFile("sonar.path.temp"); - JavaCommand elasticsearch = new JavaCommand(JmxUtils.SEARCH_SERVER_NAME); + JavaCommand elasticsearch = new JavaCommand("search"); elasticsearch .setWorkDir(homeDir) - .setJmxPort(props.valueAsInt(DefaultSettings.SEARCH_JMX_PORT)) .addJavaOptions(props.value(DefaultSettings.SEARCH_JAVA_OPTS)) .setTempDir(tempDir.getAbsoluteFile()) .setClassName("org.sonar.search.SearchServer") @@ -74,9 +69,8 @@ public class App implements ProcessMXBean { // do not yet start SQ in cluster mode. See SONAR-5483 & SONAR-5391 if (StringUtils.isEmpty(props.value(DefaultSettings.CLUSTER_MASTER))) { - JavaCommand webServer = new JavaCommand(JmxUtils.WEB_SERVER_NAME) + JavaCommand webServer = new JavaCommand("web") .setWorkDir(homeDir) - .setJmxPort(props.valueAsInt(DefaultSettings.WEB_JMX_PORT)) .addJavaOptions(props.nonNullValue(DefaultSettings.WEB_JAVA_OPTS)) .setTempDir(tempDir.getAbsoluteFile()) // required for logback tomcat valve @@ -94,21 +88,6 @@ public class App implements ProcessMXBean { return commands; } - @Override - public void terminate() { - monitor.stop(); - } - - @Override - public boolean isReady() { - return monitor.getState() == State.STARTED; - } - - @Override - public void ping() { - - } - static String starPath(File homeDir, String relativePath) { File dir = new File(homeDir, relativePath); return FilenameUtils.concat(dir.getAbsolutePath(), "*"); diff --git a/sonar-application/src/main/java/org/sonar/application/DefaultSettings.java b/sonar-application/src/main/java/org/sonar/application/DefaultSettings.java index b18d0b633b2..701249f5f63 100644 --- a/sonar-application/src/main/java/org/sonar/application/DefaultSettings.java +++ b/sonar-application/src/main/java/org/sonar/application/DefaultSettings.java @@ -35,9 +35,7 @@ class DefaultSettings { static final String CLUSTER_NAME = "sonar.cluster.name"; static final String CLUSTER_NODE_NAME = "sonar.node.name"; static final String SEARCH_PORT = "sonar.search.port"; - static final String SEARCH_JMX_PORT = "sonar.search.jmxPort"; static final String SEARCH_JAVA_OPTS = "sonar.search.javaOpts"; - static final String WEB_JMX_PORT = "sonar.web.jmxPort"; static final String WEB_JAVA_OPTS = "sonar.web.javaOpts"; static final String JDBC_URL = "sonar.jdbc.url"; static final String JDBC_LOGIN = "sonar.jdbc.username"; @@ -84,8 +82,6 @@ class DefaultSettings { private static Map defaultPorts() { Map defaults = new HashMap(); defaults.put(SEARCH_PORT, 9001); - defaults.put(SEARCH_JMX_PORT, 9002); - defaults.put(WEB_JMX_PORT, 9003); return defaults; } } diff --git a/sonar-application/src/test/java/org/sonar/application/DefaultSettingsTest.java b/sonar-application/src/test/java/org/sonar/application/DefaultSettingsTest.java index 16a9cc9f1f8..11bcb2f5631 100644 --- a/sonar-application/src/test/java/org/sonar/application/DefaultSettingsTest.java +++ b/sonar-application/src/test/java/org/sonar/application/DefaultSettingsTest.java @@ -34,8 +34,6 @@ public class DefaultSettingsTest { DefaultSettings.init(props); assertThat(props.value("sonar.search.javaOpts")).contains("-Xmx"); - assertThat(props.valueAsInt("sonar.web.jmxPort")).isEqualTo(9003); - assertThat(props.valueAsInt("sonar.search.jmxPort")).isEqualTo(9002); assertThat(props.value("sonar.jdbc.username")).isEqualTo("sonar"); } @@ -52,10 +50,10 @@ public class DefaultSettingsTest { @Test public void use_random_port_if_zero() throws Exception { Properties p = new Properties(); - p.setProperty("sonar.search.jmxPort", "0"); + p.setProperty("sonar.search.port", "0"); Props props = new Props(p); DefaultSettings.init(props); - assertThat(props.valueAsInt("sonar.web.jmxPort")).isGreaterThan(0); + assertThat(props.valueAsInt("sonar.search.port")).isGreaterThan(0); } } -- 2.39.5