lifeCycle transition from RESTARTING to STOPPING should actually _not_ be allowed because it can occur when restarting child processes (the WatcherThreads detects that stop and tries to shutdown all other processes), this fixes the issue by adding a HARD_STOPPING state, representing a not gracefull stop, to which transition from RESTARTING is allowed
adds class AllProcessesCommands which implements access to sharedMemory for any process and is now used as the underlying implementation of DefaultProcessCommands. This class allows using a single IO to access sharedMemory from App
private final Map<String, String> envVariables = new HashMap<>(System.getenv());
- private File tempDir = null;
-
private int processIndex = -1;
public JavaCommand(String key) {
return this;
}
- public File getTempDir() {
- return tempDir;
- }
-
- public JavaCommand setTempDir(File tempDir) {
- this.tempDir = tempDir;
- return this;
- }
-
public List<String> getJavaOptions() {
return javaOptions;
}
*/
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;
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[{}]: {}",
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());
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();
*/
package org.sonar.process.monitor;
+import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
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;
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;
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());
}
/**
}
}
+ 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);
boolean waitForOneRestart() {
boolean restartRequested = awaitChildProcessesTermination();
+ trace("finished waiting, restartRequested=" + restartRequested);
if (restartRequested) {
+ trace("awaitTermination(restartor)=" + restartor);
awaitTermination(restartor);
}
return restartRequested;
* 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();
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();
}
}
* 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();
}
}
}
+ 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);
}
}
+ private static void awaitTermination(Thread... threads) {
+ for (Thread thread : threads) {
+ awaitTermination(thread);
+ }
+ }
+
private static void awaitTermination(@Nullable Thread t) {
if (t == null) {
return;
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");
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
*/
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
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;
@Rule
public TemporaryFolder temp = new TemporaryFolder();
+ private File tempDir;
+
+ @Before
+ public void setUp() throws Exception {
+ tempDir = temp.newFolder();
+
+ }
+
/**
* Safeguard
*/
}
@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();
@Test
public void fail_to_start_multiple_times() throws Exception {
- monitor = newDefaultMonitor();
+ monitor = newDefaultMonitor(tempDir);
monitor.start(Arrays.asList(newStandardProcessCommand()));
boolean failed = false;
try {
@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()));
@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)
@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();
@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();
@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();
@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();
}
@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 {
@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));
}
}
- 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);
}
/**
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;
}
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));
}
/**
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);
}
private boolean fileExists(String filename) {
- File file = new File(tempDir, filename);
+ File file = new File(tempDir, httpPort + "-" + filename);
return file.isFile() && file.exists();
}
}
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");
}
}
--- /dev/null
+/*
+ * 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();
+ }
+ }
+}
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();
}
}
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;
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();
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;
}
* <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;
--- /dev/null
+/*
+ * 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);
+ }
+}
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 {
@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 {
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
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
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
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();
}
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);
}
}
}
@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();
return newLifeCycle(STARTED, state);
case STOPPING:
return newLifeCycle(STARTED, state);
+ case HARD_STOPPING:
+ return newLifeCycle(STARTING, state);
case STOPPED:
return newLifeCycle(STOPPING, state);
default:
*/
public class HttpProcess implements Monitored {
+ private final int httpPort;
private final Server server;
private boolean ready = false;
// temp dir is specific to this process
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
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);
}
@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();
}
*/
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) {
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();
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/*")
.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")
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();
}
+
}
.startsWith(FilenameUtils.normalize(homeDir.getAbsolutePath(), true));
}
- @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);