summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java11
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java30
-rw-r--r--server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java78
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaCommandTest.java3
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java12
-rw-r--r--server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java92
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java224
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java127
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java10
-rw-r--r--server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java2
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java152
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java51
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java6
-rw-r--r--server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java6
-rw-r--r--server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java15
-rw-r--r--sonar-application/src/main/java/org/sonar/application/App.java37
-rw-r--r--sonar-application/src/test/java/org/sonar/application/AppTest.java24
17 files changed, 603 insertions, 277 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 9187e0305ea..8a5551039d5 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
@@ -52,8 +52,6 @@ public class JavaCommand {
private final Map<String, String> envVariables = new HashMap<>(System.getenv());
- private File tempDir = null;
-
private int processIndex = -1;
public JavaCommand(String key) {
@@ -78,15 +76,6 @@ public class JavaCommand {
return this;
}
- public File getTempDir() {
- return tempDir;
- }
-
- public JavaCommand setTempDir(File tempDir) {
- this.tempDir = tempDir;
- return this;
- }
-
public List<String> getJavaOptions() {
return javaOptions;
}
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 6a2eb83bb44..9bf969cd944 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
@@ -19,13 +19,6 @@
*/
package org.sonar.process.monitor;
-import org.apache.commons.lang.StringUtils;
-import org.slf4j.LoggerFactory;
-import org.sonar.process.DefaultProcessCommands;
-import org.sonar.process.ProcessCommands;
-import org.sonar.process.ProcessEntryPoint;
-import org.sonar.process.ProcessUtils;
-
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
@@ -33,20 +26,33 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
+import org.apache.commons.lang.StringUtils;
+import org.slf4j.LoggerFactory;
+import org.sonar.process.AllProcessesCommands;
+import org.sonar.process.ProcessCommands;
+import org.sonar.process.ProcessEntryPoint;
+import org.sonar.process.ProcessUtils;
public class JavaProcessLauncher {
private final Timeouts timeouts;
+ private final File tempDir;
+ private final AllProcessesCommands allProcessesCommands;
- public JavaProcessLauncher(Timeouts timeouts) {
+ public JavaProcessLauncher(Timeouts timeouts, File tempDir, AllProcessesCommands allProcessesCommands) {
this.timeouts = timeouts;
+ this.tempDir = tempDir;
+ this.allProcessesCommands = allProcessesCommands;
+ }
+
+ public void close() {
+ allProcessesCommands.close();
}
ProcessRef launch(JavaCommand command) {
Process process = null;
try {
- // cleanup existing monitor files
- ProcessCommands commands = new DefaultProcessCommands(command.getTempDir(), command.getProcessIndex());
+ ProcessCommands commands = allProcessesCommands.getProcessCommand(command.getProcessIndex(), true);
ProcessBuilder processBuilder = create(command);
LoggerFactory.getLogger(getClass()).info("Launch process[{}]: {}",
@@ -69,7 +75,7 @@ public class JavaProcessLauncher {
commands.add(buildJavaPath());
commands.addAll(javaCommand.getJavaOptions());
// TODO warning - does it work if temp dir contains a whitespace ?
- commands.add(String.format("-Djava.io.tmpdir=%s", javaCommand.getTempDir().getAbsolutePath()));
+ commands.add(String.format("-Djava.io.tmpdir=%s", tempDir.getAbsolutePath()));
commands.addAll(buildClasspath(javaCommand));
commands.add(javaCommand.getClassName());
commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());
@@ -101,7 +107,7 @@ public class JavaProcessLauncher {
props.setProperty(ProcessEntryPoint.PROPERTY_PROCESS_KEY, javaCommand.getKey());
props.setProperty(ProcessEntryPoint.PROPERTY_PROCESS_INDEX, Integer.toString(javaCommand.getProcessIndex()));
props.setProperty(ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT, String.valueOf(timeouts.getTerminationTimeout()));
- props.setProperty(ProcessEntryPoint.PROPERTY_SHARED_PATH, javaCommand.getTempDir().getAbsolutePath());
+ props.setProperty(ProcessEntryPoint.PROPERTY_SHARED_PATH, tempDir.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/Monitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java
index 749389d1054..7dcf8ede0d0 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
@@ -19,6 +19,7 @@
*/
package org.sonar.process.monitor;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@@ -27,6 +28,7 @@ import javax.annotation.CheckForNull;
import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.sonar.process.AllProcessesCommands;
import org.sonar.process.Lifecycle;
import org.sonar.process.Lifecycle.State;
import org.sonar.process.ProcessCommands;
@@ -37,9 +39,11 @@ public class Monitor {
private static final Logger LOG = LoggerFactory.getLogger(Monitor.class);
private static final Timeouts TIMEOUTS = new Timeouts();
private static final long WATCH_DELAY_MS = 500L;
+ private static final int CURRENT_PROCESS_NUMBER = 0;
private static int restartorInstanceCounter = 0;
+ private final AllProcessesCommands allProcessesCommands;
private final JavaProcessLauncher launcher;
private final SystemExit systemExit;
@@ -53,15 +57,18 @@ public class Monitor {
private List<JavaCommand> javaCommands;
@CheckForNull
private RestartorThread restartor;
+ @CheckForNull
+ HardStopWatcherThread hardStopWatcher;
static int nextProcessId = 1;
- Monitor(JavaProcessLauncher launcher, SystemExit exit) {
- this.launcher = launcher;
+ Monitor(File tempDir, SystemExit exit) {
+ this.allProcessesCommands = new AllProcessesCommands(tempDir);
+ this.launcher = new JavaProcessLauncher(TIMEOUTS, tempDir, allProcessesCommands);
this.systemExit = exit;
}
- public static Monitor create() {
- return new Monitor(new JavaProcessLauncher(TIMEOUTS), new SystemExit());
+ public static Monitor create(File tempDir) {
+ return new Monitor(tempDir, new SystemExit());
}
/**
@@ -108,6 +115,12 @@ public class Monitor {
}
}
+ public void watchForHardStop() {
+ ProcessCommands processCommand = this.allProcessesCommands.getProcessCommand(CURRENT_PROCESS_NUMBER, true);
+ this.hardStopWatcher = new HardStopWatcherThread(processCommand);
+ this.hardStopWatcher.start();
+ }
+
private void monitor(ProcessRef processRef) {
// physically watch if process is alive
WatcherThread watcherThread = new WatcherThread(processRef, this);
@@ -145,7 +158,9 @@ public class Monitor {
boolean waitForOneRestart() {
boolean restartRequested = awaitChildProcessesTermination();
+ trace("finished waiting, restartRequested=" + restartRequested);
if (restartRequested) {
+ trace("awaitTermination(restartor)=" + restartor);
awaitTermination(restartor);
}
return restartRequested;
@@ -176,8 +191,8 @@ public class Monitor {
* Blocks until all processes are terminated.
*/
public void stop() {
- trace("start stop async...");
- stopAsync();
+ trace("start hard stop async...");
+ stopAsync(State.HARD_STOPPING);
trace("await termination of terminator...");
awaitTermination(terminator);
cleanAfterTermination();
@@ -187,17 +202,18 @@ public class Monitor {
private void cleanAfterTermination() {
trace("go to STOPPED...");
- // safeguard if TerminatorThread is buggy and stop restartWatcher
if (lifecycle.tryToMoveTo(State.STOPPED)) {
trace("await termination of restartWatcher...");
- // wait for restartWatcher to cleanly stop
- awaitTermination(restartWatcher);
+ // wait for restartWatcher and hardStopWatcher to cleanly stop
+ awaitTermination(restartWatcher, hardStopWatcher);
trace("restartWatcher done");
// removing shutdown hook to avoid called stop() unnecessarily unless already in shutdownHook
if (!systemExit.isInShutdownHook()) {
trace("removing shutdown hook...");
Runtime.getRuntime().removeShutdownHook(shutdownHook);
}
+ // cleanly close JavaLauncher
+ launcher.close();
}
}
@@ -207,7 +223,11 @@ public class Monitor {
* this call will be blocking until the previous request finishes.
*/
public void stopAsync() {
- if (lifecycle.tryToMoveTo(State.STOPPING)) {
+ stopAsync(State.STOPPING);
+ }
+
+ private void stopAsync(State stoppingState) {
+ if (lifecycle.tryToMoveTo(stoppingState)) {
terminator.start();
}
}
@@ -288,12 +308,38 @@ public class Monitor {
}
+ public class HardStopWatcherThread extends Thread {
+ private final ProcessCommands processCommands;
+
+ public HardStopWatcherThread(ProcessCommands processCommands) {
+ super("Hard stop watcher");
+ this.processCommands = processCommands;
+ }
+
+ @Override
+ public void run() {
+ while (lifecycle.getState() != Lifecycle.State.STOPPED) {
+ if (processCommands.askedForStop()) {
+ trace("Stopping process");
+ Monitor.this.stop();
+ } else {
+ try {
+ Thread.sleep(WATCH_DELAY_MS);
+ } catch (InterruptedException ignored) {
+ // keep watching
+ }
+ }
+ }
+ }
+
+ }
+
private void stopProcesses() {
- List<WatcherThread> watcherThreads = new ArrayList<>(this.watcherThreads);
+ List<WatcherThread> watcherThreadsCopy = new ArrayList<>(this.watcherThreads);
// create a copy and reverse it to terminate in reverse order of startup (dependency order)
- Collections.reverse(watcherThreads);
+ Collections.reverse(watcherThreadsCopy);
- for (WatcherThread watcherThread : watcherThreads) {
+ for (WatcherThread watcherThread : watcherThreadsCopy) {
ProcessRef ref = watcherThread.getProcessRef();
if (!ref.isStopped()) {
LOG.info("{} is stopping", ref);
@@ -339,6 +385,12 @@ public class Monitor {
}
}
+ private static void awaitTermination(Thread... threads) {
+ for (Thread thread : threads) {
+ awaitTermination(thread);
+ }
+ }
+
private static void awaitTermination(@Nullable Thread t) {
if (t == null) {
return;
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 cc376db2d35..5e36ef6c797 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
@@ -44,8 +44,6 @@ public class JavaCommandTest {
command.setClassName("org.sonar.ElasticSearch");
command.setEnvVariable("JAVA_COMMAND_TEST", "1000");
- File tempDir = temp.newFolder();
- command.setTempDir(tempDir);
File workDir = temp.newFolder();
command.setWorkDir(workDir);
command.addClasspath("lib/*.jar");
@@ -56,7 +54,6 @@ public class JavaCommandTest {
assertThat(command.getClasspath()).containsOnly("lib/*.jar", "conf/*.xml");
assertThat(command.getJavaOptions()).containsOnly("-Xmx128m");
assertThat(command.getWorkDir()).isSameAs(workDir);
- assertThat(command.getTempDir()).isSameAs(tempDir);
assertThat(command.getClassName()).isEqualTo("org.sonar.ElasticSearch");
// copy current env variables
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 a4c2c82ca64..cdd723c6fb4 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
@@ -19,17 +19,25 @@
*/
package org.sonar.process.monitor;
+import java.io.File;
+import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.sonar.process.AllProcessesCommands;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.fail;
public class JavaProcessLauncherTest {
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+
@Test
- public void fail_to_launch() {
+ public void fail_to_launch() throws Exception {
+ File tempDir = temp.newFolder();
JavaCommand command = new JavaCommand("test");
- JavaProcessLauncher launcher = new JavaProcessLauncher(new Timeouts());
+ JavaProcessLauncher launcher = new JavaProcessLauncher(new Timeouts(), tempDir, new AllProcessesCommands(tempDir));
try {
// command is not correct (missing options), java.lang.ProcessBuilder#start()
// throws an exception
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 a9937311e1e..038d9aa64e1 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
@@ -32,6 +32,7 @@ import org.apache.commons.lang.StringUtils;
import org.assertj.core.api.AbstractAssert;
import org.assertj.core.internal.Longs;
import org.junit.After;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.Test;
@@ -90,6 +91,14 @@ public class MonitorTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
+ private File tempDir;
+
+ @Before
+ public void setUp() throws Exception {
+ tempDir = temp.newFolder();
+
+ }
+
/**
* Safeguard
*/
@@ -104,8 +113,8 @@ public class MonitorTest {
}
@Test
- public void fail_to_start_if_no_commands() {
- monitor = newDefaultMonitor();
+ public void fail_to_start_if_no_commands() throws Exception {
+ monitor = newDefaultMonitor(tempDir);
try {
monitor.start(Collections.<JavaCommand>emptyList());
fail();
@@ -116,7 +125,7 @@ public class MonitorTest {
@Test
public void fail_to_start_multiple_times() throws Exception {
- monitor = newDefaultMonitor();
+ monitor = newDefaultMonitor(tempDir);
monitor.start(Arrays.asList(newStandardProcessCommand()));
boolean failed = false;
try {
@@ -130,8 +139,8 @@ public class MonitorTest {
@Test
public void start_then_stop_gracefully() throws Exception {
- monitor = newDefaultMonitor();
- HttpProcessClient client = new HttpProcessClient("test");
+ monitor = newDefaultMonitor(tempDir);
+ HttpProcessClient client = new HttpProcessClient(tempDir, "test");
// blocks until started
monitor.start(Arrays.asList(client.newCommand()));
@@ -148,9 +157,9 @@ public class MonitorTest {
@Test
public void start_then_stop_sequence_of_commands() throws Exception {
- monitor = newDefaultMonitor();
- HttpProcessClient p1 = new HttpProcessClient("p1");
- HttpProcessClient p2 = new HttpProcessClient("p2");
+ monitor = newDefaultMonitor(tempDir);
+ HttpProcessClient p1 = new HttpProcessClient(tempDir, "p1");
+ HttpProcessClient p2 = new HttpProcessClient(tempDir, "p2");
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
// start p2 when p1 is fully started (ready)
@@ -173,9 +182,9 @@ public class MonitorTest {
@Test
public void stop_all_processes_if_monitor_shutdowns() throws Exception {
- monitor = newDefaultMonitor();
- HttpProcessClient p1 = new HttpProcessClient("p1");
- HttpProcessClient p2 = new HttpProcessClient("p2");
+ monitor = newDefaultMonitor(tempDir);
+ HttpProcessClient p1 = new HttpProcessClient(tempDir, "p1");
+ HttpProcessClient p2 = new HttpProcessClient(tempDir, "p2");
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
assertThat(p1).isReady();
assertThat(p2).isReady();
@@ -190,9 +199,9 @@ public class MonitorTest {
@Test
public void restart_all_processes_if_one_asks_for_restart() throws Exception {
- monitor = newDefaultMonitor();
- HttpProcessClient p1 = new HttpProcessClient("p1");
- HttpProcessClient p2 = new HttpProcessClient("p2");
+ monitor = newDefaultMonitor(tempDir);
+ HttpProcessClient p1 = new HttpProcessClient(tempDir, "p1");
+ HttpProcessClient p2 = new HttpProcessClient(tempDir, "p2");
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
assertThat(p1).isReady();
@@ -221,9 +230,9 @@ public class MonitorTest {
@Test
public void stop_all_processes_if_one_shutdowns() throws Exception {
- monitor = newDefaultMonitor();
- HttpProcessClient p1 = new HttpProcessClient("p1");
- HttpProcessClient p2 = new HttpProcessClient("p2");
+ monitor = newDefaultMonitor(tempDir);
+ HttpProcessClient p1 = new HttpProcessClient(tempDir, "p1");
+ HttpProcessClient p2 = new HttpProcessClient(tempDir, "p2");
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
assertThat(p1.isReady()).isTrue();
assertThat(p2.isReady()).isTrue();
@@ -242,9 +251,9 @@ public class MonitorTest {
@Test
public void stop_all_processes_if_one_fails_to_start() throws Exception {
- monitor = newDefaultMonitor();
- HttpProcessClient p1 = new HttpProcessClient("p1");
- HttpProcessClient p2 = new HttpProcessClient("p2", -1);
+ monitor = newDefaultMonitor(tempDir);
+ HttpProcessClient p1 = new HttpProcessClient(tempDir, "p1");
+ HttpProcessClient p2 = new HttpProcessClient(tempDir, "p2", -1);
try {
monitor.start(Arrays.asList(p1.newCommand(), p2.newCommand()));
fail();
@@ -260,11 +269,11 @@ public class MonitorTest {
}
@Test
- public void test_too_many_processes() {
+ public void test_too_many_processes() throws Exception {
while (Monitor.getNextProcessId() < ProcessCommands.MAX_PROCESSES - 1) {
}
try {
- newDefaultMonitor();
+ newDefaultMonitor(tempDir);
} catch (IllegalStateException e) {
assertThat(e).hasMessageStartingWith("The maximum number of processes launched has been reached ");
} finally {
@@ -274,11 +283,10 @@ public class MonitorTest {
@Test
public void fail_to_start_if_bad_class_name() throws Exception {
- monitor = newDefaultMonitor();
+ monitor = newDefaultMonitor(tempDir);
JavaCommand command = new JavaCommand("test")
.addClasspath(testJar.getAbsolutePath())
- .setClassName("org.sonar.process.test.Unknown")
- .setTempDir(temp.newFolder());
+ .setClassName("org.sonar.process.test.Unknown");
try {
monitor.start(Arrays.asList(command));
@@ -289,9 +297,19 @@ public class MonitorTest {
}
}
- private Monitor newDefaultMonitor() {
- Timeouts timeouts = new Timeouts();
- return new Monitor(new JavaProcessLauncher(timeouts), exit);
+ @Test
+ public void watchForHardStop_adds_a_hardStopWatcher_thread_and_starts_it() throws Exception {
+ Monitor monitor = newDefaultMonitor(tempDir);
+ assertThat(monitor.hardStopWatcher).isNull();
+
+ monitor.watchForHardStop();
+
+ assertThat(monitor.hardStopWatcher).isNotNull();
+ assertThat(monitor.hardStopWatcher.isAlive()).isTrue();
+ }
+
+ private Monitor newDefaultMonitor(File tempDir) throws IOException {
+ return new Monitor(tempDir, exit);
}
/**
@@ -302,16 +320,16 @@ public class MonitorTest {
private final String commandKey;
private final File tempDir;
- private HttpProcessClient(String commandKey) throws IOException {
- this(commandKey, NetworkUtils.freePort());
+ private HttpProcessClient(File tempDir, String commandKey) throws IOException {
+ this(tempDir, commandKey, NetworkUtils.freePort());
}
/**
* Use httpPort=-1 to make server fail to start
*/
- private HttpProcessClient(String commandKey, int httpPort) throws IOException {
+ private HttpProcessClient(File tempDir, String commandKey, int httpPort) throws IOException {
+ this.tempDir = tempDir;
this.commandKey = commandKey;
- this.tempDir = temp.newFolder(commandKey);
this.httpPort = httpPort;
}
@@ -319,8 +337,7 @@ public class MonitorTest {
return new JavaCommand(commandKey)
.addClasspath(testJar.getAbsolutePath())
.setClassName("org.sonar.process.test.HttpProcess")
- .setArgument("httpPort", String.valueOf(httpPort))
- .setTempDir(tempDir);
+ .setArgument("httpPort", String.valueOf(httpPort));
}
/**
@@ -386,7 +403,7 @@ public class MonitorTest {
private List<Long> readTimeFromFile(String filename) {
try {
- File file = new File(tempDir, filename);
+ File file = new File(tempDir, httpPort + "-" + filename);
if (file.isFile() && file.exists()) {
String[] split = StringUtils.split(FileUtils.readFileToString(file), ',');
List<Long> res = new ArrayList<>(split.length);
@@ -402,7 +419,7 @@ public class MonitorTest {
}
private boolean fileExists(String filename) {
- File file = new File(tempDir, filename);
+ File file = new File(tempDir, httpPort + "-" + filename);
return file.isFile() && file.exists();
}
}
@@ -542,8 +559,7 @@ public class MonitorTest {
private JavaCommand newStandardProcessCommand() throws IOException {
return new JavaCommand("standard")
.addClasspath(testJar.getAbsolutePath())
- .setClassName("org.sonar.process.test.StandardProcess")
- .setTempDir(temp.newFolder());
+ .setClassName("org.sonar.process.test.StandardProcess");
}
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java b/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java
new file mode 100644
index 00000000000..d6c1ee4666d
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java
@@ -0,0 +1,224 @@
+/*
+ * SonarQube :: Process
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import org.apache.commons.io.IOUtils;
+
+import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
+
+/**
+ * Process inter-communication to :
+ * <ul>
+ * <li>share status of child process</li>
+ * <li>stop child process</li>
+ * </ul>
+ *
+ * <p/>
+ * It relies on files shared by both processes. Following alternatives were considered but not selected :
+ * <ul>
+ * <li>JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port</li>
+ * <li>simple socket protocol: same drawbacks are RMI connection</li>
+ * <li>java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)</li>
+ * <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li>
+ * </ul>
+ */
+public class AllProcessesCommands {
+
+ /**
+ * The ByteBuffer will contains :
+ * <ul>
+ * <li>First byte will contains 0x00 until stop command is issued = 0xFF</li>
+ * <li>Then each 10 bytes will be reserved for each process</li>
+ * </ul>
+ *
+ * Description of ten bytes of each process :
+ * <ul>
+ * <li>First byte will contains the state 0x00 until READY 0x01</li>
+ * <li>The second byte will contains the request for stopping 0x00 or STOP (0xFF)</li>
+ * <li>The second byte will contains the request for restarting 0x00 or RESTART (0xAA)</li>
+ * <li>The next 8 bytes contains a long (System.currentTimeInMillis for ping)</li>
+ * </ul>
+ */
+ final MappedByteBuffer mappedByteBuffer;
+ private final RandomAccessFile sharedMemory;
+ private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 1 + 8;
+
+ // With this shared memory we can handle up to MAX_PROCESSES processes
+ private static final int MAX_SHARED_MEMORY = BYTE_LENGTH_FOR_ONE_PROCESS * MAX_PROCESSES;
+
+ public static final byte STOP = (byte) 0xFF;
+ public static final byte RESTART = (byte) 0xAA;
+ public static final byte READY = (byte) 0x01;
+ public static final byte EMPTY = (byte) 0x00;
+
+ public AllProcessesCommands(File directory) {
+ if (!directory.isDirectory() || !directory.exists()) {
+ throw new IllegalArgumentException("Not a valid directory: " + directory);
+ }
+
+ try {
+ sharedMemory = new RandomAccessFile(new File(directory, "sharedmemory"), "rw");
+ mappedByteBuffer = sharedMemory.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, MAX_SHARED_MEMORY);
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Unable to create shared memory : ", e);
+ }
+ }
+
+ public ProcessCommands getProcessCommand(int processNumber, boolean clean) {
+ checkProcessNumber(processNumber);
+ ProcessCommands processCommands = new ProcessCommandsImpl(processNumber);
+ if (clean) {
+ cleanData(processNumber);
+ }
+ return processCommands;
+ }
+
+ boolean isReady(int processNumber) {
+ return mappedByteBuffer.get(offset(processNumber)) == READY;
+ }
+
+ /**
+ * To be executed by child process to declare that it's ready
+ */
+ void setReady(int processNumber) {
+ mappedByteBuffer.put(offset(processNumber), READY);
+ }
+
+ void ping(int processNumber) {
+ mappedByteBuffer.putLong(2 + offset(processNumber), System.currentTimeMillis());
+ }
+
+ long getLastPing(int processNumber) {
+ return mappedByteBuffer.getLong(2 + offset(processNumber));
+ }
+
+ /**
+ * To be executed by monitor process to ask for child process termination
+ */
+ void askForStop(int processNumber) {
+ mappedByteBuffer.put(offset(processNumber) + 1, STOP);
+ }
+
+ boolean askedForStop(int processNumber) {
+ return mappedByteBuffer.get(offset(processNumber) + 1) == STOP;
+ }
+
+ void askForRestart(int processNumber) {
+ mappedByteBuffer.put(offset(processNumber) + 3, RESTART);
+ }
+
+ boolean askedForRestart(int processNumber) {
+ return mappedByteBuffer.get(offset(processNumber) + 3) == RESTART;
+ }
+
+ void acknowledgeAskForRestart(int processNumber) {
+ mappedByteBuffer.put(offset(processNumber) + 3, EMPTY);
+ }
+
+ public void close() {
+ IOUtils.closeQuietly(sharedMemory);
+ }
+
+ int offset(int processNumber) {
+ return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber;
+ }
+
+ public void checkProcessNumber(int processNumber) {
+ boolean result = processNumber >= 0 && processNumber < MAX_PROCESSES;
+ if (!result) {
+ throw new IllegalArgumentException(String.format("Process number %s is not valid", processNumber));
+ }
+ }
+
+ private void cleanData(int processNumber) {
+ for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) {
+ mappedByteBuffer.put(offset(processNumber) + i, EMPTY);
+ }
+ }
+
+ private class ProcessCommandsImpl implements ProcessCommands {
+
+ private final int processNumber;
+
+ public ProcessCommandsImpl(int processNumber) {
+ this.processNumber = processNumber;
+ }
+
+ @Override
+ public boolean isReady() {
+ return AllProcessesCommands.this.isReady(processNumber);
+ }
+
+ @Override
+ public void setReady() {
+ AllProcessesCommands.this.setReady(processNumber);
+ }
+
+ @Override
+ public void ping() {
+ AllProcessesCommands.this.ping(processNumber);
+ }
+
+ @Override
+ public long getLastPing() {
+ return AllProcessesCommands.this.getLastPing(processNumber);
+ }
+
+ @Override
+ public void askForStop() {
+ AllProcessesCommands.this.askForStop(processNumber);
+ }
+
+ @Override
+ public boolean askedForStop() {
+ return AllProcessesCommands.this.askedForStop(processNumber);
+ }
+
+ @Override
+ public void askForRestart() {
+ AllProcessesCommands.this.askForRestart(processNumber);
+ }
+
+ @Override
+ public boolean askedForRestart() {
+ return AllProcessesCommands.this.askedForRestart(processNumber);
+ }
+
+ @Override
+ public void acknowledgeAskForRestart() {
+ AllProcessesCommands.this.acknowledgeAskForRestart(processNumber);
+ }
+
+ @Override
+ public void endWatch() {
+ throw new UnsupportedOperationException("ProcessCommands created from AllProcessesCommands can not be closed directly. Close AllProcessesCommands instead");
+ }
+
+ @Override
+ public void close() throws Exception {
+ endWatch();
+ }
+ }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
index b00a9bb91fa..6f8286b5d7d 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
@@ -20,164 +20,81 @@
package org.sonar.process;
import java.io.File;
-import java.io.IOException;
-import java.io.RandomAccessFile;
-import java.nio.MappedByteBuffer;
-import java.nio.channels.FileChannel;
-import org.apache.commons.io.IOUtils;
import org.slf4j.LoggerFactory;
/**
- * Process inter-communication to :
- * <ul>
- * <li>share status of child process</li>
- * <li>stop child process</li>
- * </ul>
- *
- * <p/>
- * It relies on files shared by both processes. Following alternatives were considered but not selected :
- * <ul>
- * <li>JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port</li>
- * <li>simple socket protocol: same drawbacks are RMI connection</li>
- * <li>java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)</li>
- * <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li>
- * </ul>
+ * Default implementation of {@link ProcessCommands} based on a {@link AllProcessesCommands} of which will request a
+ * single {@link ProcessCommands} to use as delegate for the specified processNumber.
*/
public class DefaultProcessCommands implements ProcessCommands {
-
- /**
- * The ByteBuffer will contains :
- * <ul>
- * <li>First byte will contains 0x00 until stop command is issued = 0xFF</li>
- * <li>Then each 10 bytes will be reserved for each process</li>
- * </ul>
- *
- * Description of ten bytes of each process :
- * <ul>
- * <li>First byte will contains the state 0x00 until READY 0x01</li>
- * <li>The second byte will contains the request for stopping 0x00 or STOP (0xFF)</li>
- * <li>The second byte will contains the request for restarting 0x00 or RESTART (0xAA)</li>
- * <li>The next 8 bytes contains a long (System.currentTimeInMillis for ping)</li>
- * </ul>
- */
- final MappedByteBuffer mappedByteBuffer;
- private final RandomAccessFile sharedMemory;
- private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 1 + 8;
-
- // With this shared memory we can handle up to MAX_PROCESSES processes
- private static final int MAX_SHARED_MEMORY = BYTE_LENGTH_FOR_ONE_PROCESS * MAX_PROCESSES;
-
- public static final byte STOP = (byte) 0xFF;
- public static final byte RESTART = (byte) 0xAA;
- public static final byte READY = (byte) 0x01;
- public static final byte EMPTY = (byte) 0x00;
-
- private int processNumber;
+ private final AllProcessesCommands allProcessesCommands;
+ private final ProcessCommands delegate;
public DefaultProcessCommands(File directory, int processNumber) {
this(directory, processNumber, true);
}
public DefaultProcessCommands(File directory, int processNumber, boolean clean) {
- // processNumber should not excess MAX_PROCESSES and must not be below -1
- assert processNumber <= MAX_PROCESSES : "Incorrect process number";
- assert processNumber >= -1 : "Incorrect process number";
-
- this.processNumber = processNumber;
- if (!directory.isDirectory() || !directory.exists()) {
- throw new IllegalArgumentException("Not a valid directory: " + directory);
- }
-
- try {
- sharedMemory = new RandomAccessFile(new File(directory, "sharedmemory"), "rw");
- mappedByteBuffer = sharedMemory.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, MAX_SHARED_MEMORY);
- if (clean) {
- cleanData();
- }
- } catch (IOException e) {
- throw new IllegalArgumentException("Unable to create shared memory : ", e);
- }
+ this.allProcessesCommands = new AllProcessesCommands(directory);
+ this.delegate = allProcessesCommands.getProcessCommand(processNumber, clean);
}
@Override
public boolean isReady() {
- return canBeMonitored() && mappedByteBuffer.get(offset()) == READY;
+ return delegate.isReady();
}
- /**
- * To be executed by child process to declare that it's ready
- */
@Override
public void setReady() {
- if (canBeMonitored()) {
- mappedByteBuffer.put(offset(), READY);
- }
+ delegate.setReady();
}
@Override
public void ping() {
- if (canBeMonitored()) {
- mappedByteBuffer.putLong(2 + offset(), System.currentTimeMillis());
- }
+ delegate.ping();
}
@Override
public long getLastPing() {
- if (canBeMonitored()) {
- return mappedByteBuffer.getLong(2 + offset());
- } else {
- return -1;
- }
+ return delegate.getLastPing();
}
- /**
- * To be executed by monitor process to ask for child process termination
- */
@Override
public void askForStop() {
- mappedByteBuffer.put(offset() + 1, STOP);
+ delegate.askForStop();
}
@Override
public boolean askedForStop() {
- return mappedByteBuffer.get(offset() + 1) == STOP;
+ return delegate.askedForStop();
}
@Override
public void askForRestart() {
- mappedByteBuffer.put(offset() + 3, RESTART);
+ delegate.askForRestart();
}
@Override
public boolean askedForRestart() {
- return mappedByteBuffer.get(offset() + 3) == RESTART;
+ return delegate.askedForRestart();
}
@Override
public void acknowledgeAskForRestart() {
- mappedByteBuffer.put(offset() + 3, EMPTY);
+ delegate.acknowledgeAskForRestart();
}
@Override
public void endWatch() {
- IOUtils.closeQuietly(sharedMemory);
- }
-
- int offset() {
- return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber;
- }
-
- private boolean canBeMonitored() {
- boolean result = processNumber >= 0 && processNumber < MAX_PROCESSES;
- if (!result) {
- LoggerFactory.getLogger(getClass()).info("This process cannot be monitored. Process Id : [{}]", processNumber);
+ try {
+ close();
+ } catch (Exception e) {
+ LoggerFactory.getLogger(getClass()).error("Failed to close DefaultProcessCommands", e);
}
- return result;
}
- private void cleanData() {
- for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) {
- mappedByteBuffer.put(offset() + i, EMPTY);
- }
+ @Override
+ public void close() throws Exception {
+ allProcessesCommands.close();
}
}
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 6aa8e1d932b..18a747e14e4 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
@@ -28,6 +28,7 @@ import javax.annotation.Nullable;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import static org.sonar.process.Lifecycle.State.HARD_STOPPING;
import static org.sonar.process.Lifecycle.State.INIT;
import static org.sonar.process.Lifecycle.State.RESTARTING;
import static org.sonar.process.Lifecycle.State.STARTED;
@@ -39,7 +40,7 @@ public class Lifecycle {
private static final Logger LOG = LoggerFactory.getLogger(Lifecycle.class);
public enum State {
- INIT, STARTING, STARTED, RESTARTING, STOPPING, STOPPED
+ INIT, STARTING, STARTED, RESTARTING, STOPPING, HARD_STOPPING, STOPPED
}
private static final Map<State, Set<State>> TRANSITIONS = buildTransitions();
@@ -47,10 +48,11 @@ public class Lifecycle {
private static Map<State, Set<State>> buildTransitions() {
Map<State, Set<State>> res = new EnumMap<>(State.class);
res.put(INIT, toSet(STARTING));
- res.put(STARTING, toSet(STARTED, STOPPING));
- res.put(STARTED, toSet(RESTARTING, STOPPING));
- res.put(RESTARTING, toSet(STARTING, STOPPING));
+ res.put(STARTING, toSet(STARTED, STOPPING, HARD_STOPPING));
+ res.put(STARTED, toSet(RESTARTING, STOPPING, HARD_STOPPING));
+ res.put(RESTARTING, toSet(STARTING, HARD_STOPPING));
res.put(STOPPING, toSet(STOPPED));
+ res.put(HARD_STOPPING, toSet(STOPPED));
res.put(STOPPED, toSet());
return res;
}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
index 789a4b8cefc..8c0fe5ed008 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
@@ -36,7 +36,7 @@ package org.sonar.process;
* <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li>
* </ul>
*/
-public interface ProcessCommands {
+public interface ProcessCommands extends AutoCloseable {
int MAX_PROCESSES = 50;
diff --git a/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java
new file mode 100644
index 00000000000..97a8a6d19da
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java
@@ -0,0 +1,152 @@
+/*
+ * SonarQube :: Process
+ * Copyright (C) 2009-2016 SonarSource SA
+ * mailto:contact AT sonarsource DOT com
+ *
+ * This program 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.
+ *
+ * This program 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 java.io.File;
+import org.apache.commons.io.FileUtils;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.ExpectedException;
+import org.junit.rules.TemporaryFolder;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.junit.Assert.fail;
+import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
+
+public class AllProcessesCommandsTest {
+
+ private static final int PROCESS_NUMBER = 1;
+
+ @Rule
+ public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
+
+ @Test
+ public void fail_to_init_if_dir_does_not_exist() throws Exception {
+ File dir = temp.newFolder();
+ FileUtils.deleteQuietly(dir);
+
+ try {
+ new AllProcessesCommands(dir);
+ fail();
+ } catch (IllegalArgumentException e) {
+ assertThat(e).hasMessage("Not a valid directory: " + dir.getAbsolutePath());
+ }
+ }
+
+ @Test
+ public void child_process_update_the_mapped_memory() throws Exception {
+ File dir = temp.newFolder();
+
+ AllProcessesCommands commands = new AllProcessesCommands(dir);
+ assertThat(commands.isReady(PROCESS_NUMBER)).isFalse();
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER))).isEqualTo(AllProcessesCommands.EMPTY);
+ assertThat(commands.mappedByteBuffer.getLong(2 + commands.offset(PROCESS_NUMBER))).isEqualTo(0L);
+
+ commands.setReady(PROCESS_NUMBER);
+ assertThat(commands.isReady(PROCESS_NUMBER)).isTrue();
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER))).isEqualTo(AllProcessesCommands.READY);
+
+ long currentTime = System.currentTimeMillis();
+ commands.ping(PROCESS_NUMBER);
+ assertThat(commands.mappedByteBuffer.getLong(2 + commands.offset(PROCESS_NUMBER))).isGreaterThanOrEqualTo(currentTime);
+ }
+
+ @Test
+ public void ask_for_stop() throws Exception {
+ File dir = temp.newFolder();
+
+ AllProcessesCommands commands = new AllProcessesCommands(dir);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + PROCESS_NUMBER)).isNotEqualTo(AllProcessesCommands.STOP);
+ assertThat(commands.askedForStop(PROCESS_NUMBER)).isFalse();
+
+ commands.askForStop(PROCESS_NUMBER);
+ assertThat(commands.askedForStop(PROCESS_NUMBER)).isTrue();
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + PROCESS_NUMBER)).isEqualTo(AllProcessesCommands.STOP);
+ }
+
+ @Test
+ public void ask_for_restart() throws Exception {
+ File dir = temp.newFolder();
+
+ AllProcessesCommands commands = new AllProcessesCommands(dir);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+
+ commands.askForRestart(PROCESS_NUMBER);
+ assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isEqualTo(AllProcessesCommands.RESTART);
+ }
+
+ @Test
+ public void acknowledgeAskForRestart_has_no_effect_when_no_restart_asked() throws Exception {
+ File dir = temp.newFolder();
+
+ AllProcessesCommands commands = new AllProcessesCommands(dir);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+
+ commands.acknowledgeAskForRestart(PROCESS_NUMBER);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+ }
+
+ @Test
+ public void acknowledgeAskForRestart_resets_askForRestart_has_no_effect_when_no_restart_asked() throws Exception {
+ File dir = temp.newFolder();
+
+ AllProcessesCommands commands = new AllProcessesCommands(dir);
+
+ commands.askForRestart(PROCESS_NUMBER);
+ assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isEqualTo(AllProcessesCommands.RESTART);
+
+ commands.acknowledgeAskForRestart(PROCESS_NUMBER);
+ assertThat(commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + 3)).isNotEqualTo(AllProcessesCommands.RESTART);
+ assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+ }
+
+ @Test
+ public void getProcessCommands_fails_if_processNumber_is_less_than_0() throws Exception {
+ File dir = temp.newFolder();
+ int processNumber = -2;
+
+ AllProcessesCommands allProcessesCommands = new AllProcessesCommands(dir);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Process number " + processNumber + " is not valid");
+
+ allProcessesCommands.getProcessCommand(processNumber, true);
+ }
+
+ @Test
+ public void getProcessCommands_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
+ File dir = temp.newFolder();
+ int processNumber = MAX_PROCESSES + 1;
+
+ AllProcessesCommands allProcessesCommands = new AllProcessesCommands(dir);
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Process number " + processNumber + " is not valid");
+
+ allProcessesCommands.getProcessCommand(processNumber, true);
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
index ee5f2f21243..2da77889377 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
@@ -23,11 +23,12 @@ import java.io.File;
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.Test;
+import org.junit.rules.ExpectedException;
import org.junit.rules.TemporaryFolder;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
import static org.junit.Assert.fail;
+import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
public class DefaultProcessCommandsTest {
@@ -35,6 +36,8 @@ public class DefaultProcessCommandsTest {
@Rule
public TemporaryFolder temp = new TemporaryFolder();
+ @Rule
+ public ExpectedException expectedException = ExpectedException.none();
@Test
public void fail_to_init_if_dir_does_not_exist() throws Exception {
@@ -55,16 +58,9 @@ public class DefaultProcessCommandsTest {
DefaultProcessCommands commands = new DefaultProcessCommands(dir, PROCESS_NUMBER);
assertThat(commands.isReady()).isFalse();
- assertThat(commands.mappedByteBuffer.get(commands.offset())).isEqualTo(DefaultProcessCommands.EMPTY);
- assertThat(commands.mappedByteBuffer.getLong(2 + commands.offset())).isEqualTo(0L);
commands.setReady();
assertThat(commands.isReady()).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset())).isEqualTo(DefaultProcessCommands.READY);
-
- long currentTime = System.currentTimeMillis();
- commands.ping();
- assertThat(commands.mappedByteBuffer.getLong(2 + commands.offset())).isGreaterThanOrEqualTo(currentTime);
}
@Test
@@ -72,12 +68,10 @@ public class DefaultProcessCommandsTest {
File dir = temp.newFolder();
DefaultProcessCommands commands = new DefaultProcessCommands(dir, PROCESS_NUMBER);
- assertThat(commands.mappedByteBuffer.get(commands.offset() + PROCESS_NUMBER)).isNotEqualTo(DefaultProcessCommands.STOP);
assertThat(commands.askedForStop()).isFalse();
commands.askForStop();
assertThat(commands.askedForStop()).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset() + PROCESS_NUMBER)).isEqualTo(DefaultProcessCommands.STOP);
}
@Test
@@ -85,12 +79,10 @@ public class DefaultProcessCommandsTest {
File dir = temp.newFolder();
DefaultProcessCommands commands = new DefaultProcessCommands(dir, PROCESS_NUMBER);
- assertThat(commands.mappedByteBuffer.get(commands.offset() + 3)).isNotEqualTo(DefaultProcessCommands.RESTART);
assertThat(commands.askedForRestart()).isFalse();
commands.askForRestart();
assertThat(commands.askedForRestart()).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset() + 3)).isEqualTo(DefaultProcessCommands.RESTART);
}
@Test
@@ -98,11 +90,9 @@ public class DefaultProcessCommandsTest {
File dir = temp.newFolder();
DefaultProcessCommands commands = new DefaultProcessCommands(dir, PROCESS_NUMBER);
- assertThat(commands.mappedByteBuffer.get(commands.offset() + 3)).isNotEqualTo(DefaultProcessCommands.RESTART);
assertThat(commands.askedForRestart()).isFalse();
commands.acknowledgeAskForRestart();
- assertThat(commands.mappedByteBuffer.get(commands.offset() + 3)).isNotEqualTo(DefaultProcessCommands.RESTART);
assertThat(commands.askedForRestart()).isFalse();
}
@@ -114,27 +104,30 @@ public class DefaultProcessCommandsTest {
commands.askForRestart();
assertThat(commands.askedForRestart()).isTrue();
- assertThat(commands.mappedByteBuffer.get(commands.offset() + 3)).isEqualTo(DefaultProcessCommands.RESTART);
commands.acknowledgeAskForRestart();
- assertThat(commands.mappedByteBuffer.get(commands.offset() + 3)).isNotEqualTo(DefaultProcessCommands.RESTART);
assertThat(commands.askedForRestart()).isFalse();
}
@Test
- public void test_max_processes() throws Exception {
+ public void constructor_fails_if_processNumber_is_less_than_0() throws Exception {
File dir = temp.newFolder();
- try {
- new DefaultProcessCommands(dir, -2);
- failBecauseExceptionWasNotThrown(AssertionError.class);
- } catch (AssertionError e) {
- assertThat(e).hasMessage("Incorrect process number");
- }
- try {
- new DefaultProcessCommands(dir, ProcessCommands.MAX_PROCESSES + 1);
- failBecauseExceptionWasNotThrown(AssertionError.class);
- } catch (AssertionError e) {
- assertThat(e).hasMessage("Incorrect process number");
- }
+ int processNumber = -2;
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Process number " + processNumber + " is not valid");
+
+ new DefaultProcessCommands(dir, processNumber);
+ }
+
+ @Test
+ public void getProcessCommands_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
+ File dir = temp.newFolder();
+ int processNumber = MAX_PROCESSES + 1;
+
+ expectedException.expect(IllegalArgumentException.class);
+ expectedException.expectMessage("Process number " + processNumber + " is not valid");
+
+ new DefaultProcessCommands(dir, processNumber);
}
}
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 6337fe60e74..b91c84754ef 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
@@ -62,10 +62,10 @@ public class LifecycleTest {
}
@Test
- public void can_move_to_STOPPING_from_any_state_but_STARTING_and_STARTED_only() {
+ public void can_move_to_STOPPING_from_STARTING_and_STARTED_only() {
for (State state : values()) {
boolean tryToMoveTo = newLifeCycle(state).tryToMoveTo(STOPPING);
- if (state == STARTING || state == STARTED || state == RESTARTING) {
+ if (state == STARTING || state == STARTED) {
assertThat(tryToMoveTo).describedAs("from state " + state).isTrue();
} else {
assertThat(tryToMoveTo).describedAs("from state " + state).isFalse();
@@ -90,6 +90,8 @@ public class LifecycleTest {
return newLifeCycle(STARTED, state);
case STOPPING:
return newLifeCycle(STARTED, state);
+ case HARD_STOPPING:
+ return newLifeCycle(STARTING, state);
case STOPPED:
return newLifeCycle(STOPPING, state);
default:
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 86f4ccb4b37..73a6b27a788 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
@@ -43,6 +43,7 @@ import java.io.IOException;
*/
public class HttpProcess implements Monitored {
+ private final int httpPort;
private final Server server;
private boolean ready = false;
// temp dir is specific to this process
@@ -50,8 +51,9 @@ public class HttpProcess implements Monitored {
private final ProcessCommands processCommands;
public HttpProcess(int httpPort, ProcessCommands processCommands) {
+ this.httpPort = httpPort;
this.processCommands = processCommands;
- server = new Server(httpPort);
+ this.server = new Server(httpPort);
}
@Override
@@ -120,7 +122,7 @@ public class HttpProcess implements Monitored {
private void writeTimeToFile(String filename) {
try {
- FileUtils.write(new File(tempDir, filename), System.currentTimeMillis() + ",", true);
+ FileUtils.write(new File(tempDir, httpPort + "-" + filename), System.currentTimeMillis() + ",", true);
} catch (IOException e) {
throw new IllegalStateException(e);
}
diff --git a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java
index 0d395e89199..d50a751725c 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java
@@ -68,15 +68,20 @@ public class RestartAction implements SystemWsAction {
@Override
public void handle(Request request, Response response) {
if (settings.getBoolean("sonar.web.dev")) {
- LOGGER.info("Restart server");
+ LOGGER.info("Fast restarting WebServer...");
platform.restart();
- LOGGER.info("Server restarted");
+ LOGGER.info("WebServer restarted");
} else {
LOGGER.info("Requesting SonarQube restart");
userSession.checkPermission(UserRole.ADMIN);
- ProcessCommands commands = new DefaultProcessCommands(
- nonNullValueAsFile(PROPERTY_SHARED_PATH), nonNullAsInt(PROPERTY_PROCESS_INDEX), false);
- commands.askForRestart();
+
+ File shareDir = nonNullValueAsFile(PROPERTY_SHARED_PATH);
+ int processNumber = nonNullAsInt(PROPERTY_PROCESS_INDEX);
+ try (ProcessCommands commands = new DefaultProcessCommands(shareDir, processNumber, false)) {
+ commands.askForRestart();
+ } catch (Exception e) {
+ LOGGER.warn("Failed to close ProcessCommands", e);
+ }
}
response.noContent();
}
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 db78e1bf0c8..f704fcc41a7 100644
--- a/sonar-application/src/main/java/org/sonar/application/App.java
+++ b/sonar-application/src/main/java/org/sonar/application/App.java
@@ -19,35 +19,28 @@
*/
package org.sonar.application;
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Properties;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
-import org.sonar.process.DefaultProcessCommands;
import org.sonar.process.MinimumViableSystem;
-import org.sonar.process.ProcessCommands;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
-import org.sonar.process.StopWatcher;
import org.sonar.process.Stoppable;
import org.sonar.process.monitor.JavaCommand;
import org.sonar.process.monitor.Monitor;
-import java.io.File;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Properties;
-
/**
* Entry-point of process that starts and monitors elasticsearch and web servers
*/
public class App implements Stoppable {
- private static final int APP_PROCESS_NUMBER = 0;
-
private final Monitor monitor;
- private StopWatcher stopWatcher = null;
- public App() {
- this(Monitor.create());
+ public App(File tempDir) {
+ this(Monitor.create(tempDir));
}
App(Monitor monitor) {
@@ -56,10 +49,7 @@ public class App implements Stoppable {
public void start(Props props) {
if (props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false)) {
- File tempDir = props.nonNullValueAsFile(ProcessProperties.PATH_TEMP);
- ProcessCommands commands = new DefaultProcessCommands(tempDir, APP_PROCESS_NUMBER);
- stopWatcher = new StopWatcher(commands, this);
- stopWatcher.start();
+ monitor.watchForHardStop();
}
monitor.start(createCommands(props));
monitor.awaitTermination();
@@ -68,14 +58,12 @@ public class App implements Stoppable {
private List<JavaCommand> createCommands(Props props) {
List<JavaCommand> commands = new ArrayList<>();
File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
- File tempDir = props.nonNullValueAsFile(ProcessProperties.PATH_TEMP);
JavaCommand elasticsearch = new JavaCommand("search");
elasticsearch
.setWorkDir(homeDir)
.addJavaOptions("-Djava.awt.headless=true")
.addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS))
.addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
- .setTempDir(tempDir.getAbsoluteFile())
.setClassName("org.sonar.search.SearchServer")
.setArguments(props.rawProperties())
.addClasspath("./lib/common/*")
@@ -89,7 +77,6 @@ public class App implements Stoppable {
.addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS)
.addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS))
.addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS))
- .setTempDir(tempDir.getAbsoluteFile())
// required for logback tomcat valve
.setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS))
.setClassName("org.sonar.server.app.WebServer")
@@ -118,16 +105,14 @@ public class App implements Stoppable {
AppLogging logging = new AppLogging();
logging.configure(props);
- App app = new App();
+ File tempDir = props.nonNullValueAsFile(ProcessProperties.PATH_TEMP);
+ App app = new App(tempDir);
app.start(props);
}
- StopWatcher getStopWatcher() {
- return stopWatcher;
- }
-
@Override
public void stopAsync() {
- monitor.stopAsync();
+ monitor.stop();
}
+
}
diff --git a/sonar-application/src/test/java/org/sonar/application/AppTest.java b/sonar-application/src/test/java/org/sonar/application/AppTest.java
index 8b468bb2b52..03b33e8111b 100644
--- a/sonar-application/src/test/java/org/sonar/application/AppTest.java
+++ b/sonar-application/src/test/java/org/sonar/application/AppTest.java
@@ -53,30 +53,6 @@ public class AppTest {
}
@Test
- public void do_not_watch_stop_file_by_default() throws Exception {
- Monitor monitor = mock(Monitor.class);
- App app = new App(monitor);
- app.start(initDefaultProps());
-
- assertThat(app.getStopWatcher()).isNull();
- }
-
- @Test
- public void watch_stop_file() throws Exception {
- Monitor monitor = mock(Monitor.class);
- App app = new App(monitor);
- Props props = initDefaultProps();
- props.set("sonar.enableStopCommand", "true");
- app.start(props);
-
- assertThat(app.getStopWatcher()).isNotNull();
- assertThat(app.getStopWatcher().isAlive()).isTrue();
-
- app.getStopWatcher().stopWatching();
- app.getStopWatcher().interrupt();
- }
-
- @Test
public void start_elasticsearch_and_tomcat_by_default() throws Exception {
Monitor monitor = mock(Monitor.class);
App app = new App(monitor);