aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorSimon Brandhof <simon.brandhof@sonarsource.com>2014-09-21 21:22:59 +0200
committerSimon Brandhof <simon.brandhof@sonarsource.com>2014-09-21 21:27:19 +0200
commitbbca1b8de515423e6fd0d7e93e0d2f49e82dd7ba (patch)
treee07194d1fa98865db4ffff2cc86e5a552ad74f53
parent9999a96f4b0cbb345cd4690ebc37452ff624e1d5 (diff)
downloadsonarqube-bbca1b8de515423e6fd0d7e93e0d2f49e82dd7ba.tar.gz
sonarqube-bbca1b8de515423e6fd0d7e93e0d2f49e82dd7ba.zip
SONAR-4898 drop RMI and autokill
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java39
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java41
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JmxConnector.java44
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java62
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/PingerThread.java60
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java79
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/RmiJmxConnector.java145
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/StreamGobbler.java4
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/TerminatorThread.java23
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Timeouts.java75
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/WatcherThread.java12
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/CallVerifierJmxConnector.java34
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/ImpossibleToConnectJmxConnector.java42
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java17
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java3
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java106
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/RmiJmxConnectorTest.java71
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TerminationFailureRmiConnector.java27
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/TimeoutsTest.java16
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/WatcherThreadTest.java2
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/JmxUtils.java81
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java6
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/MessageException.java3
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Monitored.java (renamed from server/sonar-process/src/main/java/org/sonar/process/MonitoredProcess.java)5
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/NetworkUtils.java14
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java86
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessLogging.java1
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessMXBean.java28
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessUtils.java21
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Props.java2
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/SharedStatus.java (renamed from server/sonar-process/src/test/java/org/sonar/process/BaseProcessTest.java)50
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/State.java26
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/StopperThread.java18
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Terminable.java28
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/JmxUtilsTest.java127
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java13
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/NetworkUtilsTest.java5
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java69
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/ProcessUtilsTest.java28
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/SharedStatusTest.java101
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java79
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java15
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/InfiniteTerminationProcess.java85
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/StandardProcess.java10
-rw-r--r--server/sonar-search/src/main/java/org/sonar/search/SearchServer.java149
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/EmbeddedTomcat.java5
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java8
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/db/EmbeddedDatabaseTest.java12
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/search/BaseIndexTest.java3
-rw-r--r--server/sonar-server/src/test/java/org/sonar/server/tester/ServerTester.java2
-rw-r--r--sonar-application/src/main/assembly/conf/sonar.properties9
-rw-r--r--sonar-application/src/main/java/org/sonar/application/App.java27
-rw-r--r--sonar-application/src/main/java/org/sonar/application/DefaultSettings.java4
-rw-r--r--sonar-application/src/test/java/org/sonar/application/DefaultSettingsTest.java6
54 files changed, 605 insertions, 1423 deletions
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<String> javaOptions = new ArrayList<String>();
@@ -54,6 +51,8 @@ public class JavaCommand {
private final Map<String, String> envVariables = new HashMap<String, String>(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<String> 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<String> commands = new ArrayList<String>();
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<String> 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<String> 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<ProcessRef> processes = new CopyOnWriteArrayList<ProcessRef>();
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<WatcherThread> watcherThreads = new CopyOnWriteArrayList<WatcherThread>();
- 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<ProcessRef, ProcessMXBean> mbeans = new IdentityHashMap<ProcessRef, ProcessMXBean>();
-
- @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<Boolean>() {
- @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> T execute(Callable<T> callable, long timeoutMs) {
- ExecutorService executor = Executors.newSingleThreadExecutor();
- try {
- Future<T> 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<ProcessMXBean> {
- 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<ProcessRef> processes;
- private final JmxConnector jmxConnector;
- private final Timeouts timeouts;
- TerminatorThread(List<ProcessRef> processes, JmxConnector jmxConnector, Timeouts timeouts) {
+ TerminatorThread(List<ProcessRef> 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
@@ -66,18 +65,6 @@ public class JavaCommandTest {
}
@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");
command.addJavaOptions("-Xmx512m -Xms256m -Dfoo");
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<Boolean>() {
- @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
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
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<NetworkInterface> ifaces = NetworkInterface.getNetworkInterfaces();
- while (ifaces.hasMoreElements()) {
- NetworkInterface iface = ifaces.nextElement();
- Enumeration<InetAddress> 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() {
@@ -130,27 +112,11 @@ public class ProcessEntryPointTest {
}
@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<String> nodes = new HashSet<String>();
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<JavaCommand> commands = new ArrayList<JavaCommand>();
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<String, Integer> defaultPorts() {
Map<String, Integer> defaults = new HashMap<String, Integer>();
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);
}
}