ProcessRef launch(JavaCommand command) {
Process process = null;
try {
- // cleanup existing monitor file. Child process creates it when ready.
- // TODO fail if impossible to delete
+ // cleanup existing monitor files
ProcessCommands commands = new ProcessCommands(command.getTempDir(), command.getKey());
- commands.prepareMonitor();
+ commands.prepare();
ProcessBuilder processBuilder = create(command);
LoggerFactory.getLogger(getClass()).info("Launch {}: {}",
command.getKey(), StringUtils.join(processBuilder.command(), " "));
-
- long startedAt = System.currentTimeMillis();
process = processBuilder.start();
StreamGobbler inputGobbler = new StreamGobbler(process.getInputStream(), command.getKey());
inputGobbler.start();
- ProcessRef ref = new ProcessRef(command.getKey(), commands, process, inputGobbler);
- ref.setLaunchedAt(startedAt);
- return ref;
+ return new ProcessRef(command.getKey(), commands, process, inputGobbler);
} catch (Exception e) {
// just in case
/**
* Asks for processes termination and returns without blocking until termination.
- * @return true if termination was requested, false if it was already being terminated
*/
- boolean stopAsync() {
- boolean requested = false;
+ public void stopAsync() {
if (lifecycle.tryToMoveTo(State.STOPPING)) {
- requested = true;
terminator.setProcesses(processes);
terminator.start();
}
- return requested;
}
public State getState() {
private final ProcessCommands commands;
private final Process process;
private final StreamGobbler gobbler;
- private long launchedAt;
private volatile boolean stopped = false;
ProcessRef(String key, ProcessCommands commands, Process process, StreamGobbler gobbler) {
if (isStopped()) {
throw new MessageException(String.format("%s failed to start", this));
}
- ready = commands.wasReadyAfter(launchedAt);
+ ready = commands.isReady();
try {
Thread.sleep(200L);
} catch (InterruptedException e) {
}
}
- void setLaunchedAt(long launchedAt) {
- this.launchedAt = launchedAt;
- }
-
/**
* True if process is physically down
*/
this.stopFile = stopFile;
}
- /**
- * Executed by monitor - delete shared files before starting child process
- */
- public void prepareMonitor() {
+ public void prepare() {
deleteFile(readyFile);
deleteFile(stopFile);
}
- public void finalizeProcess() {
+ public void endWatch() {
// do not fail if files can't be deleted
FileUtils.deleteQuietly(readyFile);
FileUtils.deleteQuietly(stopFile);
}
- public boolean wasReadyAfter(long launchedAt) {
- return isCreatedAfter(readyFile, launchedAt);
+ public boolean isReady() {
+ return readyFile.exists();
}
/**
createFile(stopFile);
}
- public boolean askedForStopAfter(long launchedAt) {
- return isCreatedAfter(stopFile, launchedAt);
+ public boolean askedForStop() {
+ return stopFile.exists();
}
File getReadyFile() {
}
}
}
-
- private boolean isCreatedAfter(File file, long launchedAt) {
- // File#lastModified() can have second precision on some OS
- return file.exists() && file.lastModified() / 1000 >= launchedAt / 1000;
- }
}
import org.slf4j.LoggerFactory;
-public class ProcessEntryPoint {
+public class ProcessEntryPoint implements Stoppable {
public static final String PROPERTY_PROCESS_KEY = "process.key";
public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout";
private final ProcessCommands commands;
private final SystemExit exit;
private volatile Monitored monitored;
- private volatile long launchedAt;
private volatile StopperThread stopperThread;
private final StopWatcher stopWatcher;
this.props = props;
this.exit = exit;
this.commands = commands;
- this.launchedAt = System.currentTimeMillis();
this.stopWatcher = new StopWatcher(commands, this);
}
if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) {
throw new IllegalStateException("Already started");
}
+ commands.prepare();
monitored = mp;
try {
exit.exit(0);
}
- void stopAsync() {
+ @Override
+ public void stopAsync() {
if (lifecycle.tryToMoveTo(Lifecycle.State.STOPPING)) {
stopperThread = new StopperThread(monitored, commands, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT)));
stopperThread.start();
+ stopWatcher.stopWatching();
}
}
return shutdownHook;
}
- long getLaunchedAt() {
- return launchedAt;
- }
-
public static ProcessEntryPoint createForArguments(String[] args) {
Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args);
ProcessCommands commands = new ProcessCommands(
public class StopWatcher extends Thread {
- private final ProcessEntryPoint process;
+ private final Stoppable stoppable;
private final ProcessCommands commands;
private boolean watching = true;
- public StopWatcher(ProcessCommands commands, ProcessEntryPoint process) {
+ public StopWatcher(ProcessCommands commands, Stoppable stoppable) {
super("Stop Watcher");
this.commands = commands;
- this.process = process;
+ this.stoppable = stoppable;
}
@Override
public void run() {
- while (watching) {
- if (commands.askedForStopAfter(process.getLaunchedAt())) {
- LoggerFactory.getLogger(getClass()).info("Stopping process");
- process.stopAsync();
- watching = false;
- } else {
- try {
- Thread.sleep(500L);
- } catch (InterruptedException ignored) {
+ commands.prepare();
+ try {
+ while (watching) {
+ if (commands.askedForStop()) {
+ LoggerFactory.getLogger(getClass()).info("Stopping process");
+ stoppable.stopAsync();
watching = false;
+ } else {
+ try {
+ Thread.sleep(500L);
+ } catch (InterruptedException ignored) {
+ watching = false;
+ }
}
}
-
+ } finally {
+ commands.endWatch();
}
}
--- /dev/null
+/*
+ * SonarQube, open source software quality management tool.
+ * Copyright (C) 2008-2014 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * SonarQube is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * SonarQube is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+package org.sonar.process;
+
+public interface Stoppable {
+
+ void stopAsync();
+
+}
LoggerFactory.getLogger(getClass()).error(String.format("Can not stop in %dms", terminationTimeout), e);
}
executor.shutdownNow();
- commands.finalizeProcess();
+ commands.endWatch();
}
}
public void delete_files_on_monitor_startup() throws Exception {
File dir = temp.newFolder();
assertThat(dir).exists();
- FileUtils.touch(new File(dir, "WEB.ready"));
- FileUtils.touch(new File(dir, "WEB.stop"));
+ FileUtils.touch(new File(dir, "web.ready"));
+ FileUtils.touch(new File(dir, "web.stop"));
- ProcessCommands commands = new ProcessCommands(dir, "WEB");
- commands.prepareMonitor();
+ ProcessCommands commands = new ProcessCommands(dir, "web");
+ commands.prepare();
assertThat(commands.getReadyFile()).doesNotExist();
assertThat(commands.getStopFile()).doesNotExist();
ProcessCommands commands = new ProcessCommands(readyFile, temp.newFile());
try {
- commands.prepareMonitor();
+ commands.prepare();
fail();
} catch (MessageException e) {
// ok
File readyFile = temp.newFile();
ProcessCommands commands = new ProcessCommands(readyFile, temp.newFile());
- commands.prepareMonitor();
+ commands.prepare();
+ assertThat(commands.isReady()).isFalse();
assertThat(readyFile).doesNotExist();
commands.setReady();
+ assertThat(commands.isReady()).isTrue();
assertThat(readyFile).exists();
- commands.finalizeProcess();
+ commands.endWatch();
assertThat(readyFile).doesNotExist();
}
-
- @Test
- public void was_ready_after_date() throws Exception {
- File readyFile = mock(File.class);
- ProcessCommands commands = new ProcessCommands(readyFile, temp.newFile());
-
- // does not exist
- when(readyFile.exists()).thenReturn(false);
- when(readyFile.lastModified()).thenReturn(123456L);
- assertThat(commands.wasReadyAfter(122000L)).isFalse();
-
- // readyFile created before
- when(readyFile.exists()).thenReturn(true);
- when(readyFile.lastModified()).thenReturn(123456L);
- assertThat(commands.wasReadyAfter(124000L)).isFalse();
-
- // readyFile created after
- when(readyFile.exists()).thenReturn(true);
- when(readyFile.lastModified()).thenReturn(123456L);
- assertThat(commands.wasReadyAfter(123123L)).isTrue();
-
- // readyFile created after, but can be truncated to second on some OS
- when(readyFile.exists()).thenReturn(true);
- when(readyFile.lastModified()).thenReturn(123000L);
- assertThat(commands.wasReadyAfter(123456L)).isTrue();
- }
}
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.sonar.process.MinimumViableSystem;
+import org.sonar.process.ProcessCommands;
import org.sonar.process.ProcessLogging;
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;
/**
* Entry-point of process that starts and monitors elasticsearch and web servers
*/
-public class App {
+public class App implements Stoppable {
private final Monitor monitor;
}
public void start(Props props) {
+ if (props.valueAsBoolean("sonar.enableStopCommand", false)) {
+ // stop application when file <temp>/app.stop is created
+ File tempDir = props.nonNullValueAsFile("sonar.path.temp");
+ ProcessCommands commands = new ProcessCommands(tempDir, "app");
+ new StopWatcher(commands, this).start();
+ }
monitor.start(createCommands(props));
monitor.awaitTermination();
}
App app = new App();
app.start(props);
}
+
+ @Override
+ public void stopAsync() {
+ monitor.stopAsync();
+ }
}