Browse Source

SONAR-4898 add sonar.enableStopCommand property for internal use

tags/4.5-RC3
Simon Brandhof 9 years ago
parent
commit
f00cda378e

+ 3
- 8
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java View File

@@ -44,23 +44,18 @@ public class JavaProcessLauncher {
ProcessRef launch(JavaCommand command) {
Process process = null;
try {
// cleanup existing monitor file. Child process creates it when ready.
// TODO fail if impossible to delete
// 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

+ 1
- 5
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java View File

@@ -137,16 +137,12 @@ public class Monitor {

/**
* Asks for processes termination and returns without blocking until termination.
* @return true if termination was requested, false if it was already being terminated
*/
boolean stopAsync() {
boolean requested = false;
public void stopAsync() {
if (lifecycle.tryToMoveTo(State.STOPPING)) {
requested = true;
terminator.setProcesses(processes);
terminator.start();
}
return requested;
}

public State getState() {

+ 1
- 6
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java View File

@@ -30,7 +30,6 @@ class ProcessRef {
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) {
@@ -61,7 +60,7 @@ class ProcessRef {
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) {
@@ -70,10 +69,6 @@ class ProcessRef {
}
}

void setLaunchedAt(long launchedAt) {
this.launchedAt = launchedAt;
}

/**
* True if process is physically down
*/

+ 6
- 14
server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java View File

@@ -57,22 +57,19 @@ public class ProcessCommands {
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();
}

/**
@@ -89,8 +86,8 @@ public class ProcessCommands {
createFile(stopFile);
}

public boolean askedForStopAfter(long launchedAt) {
return isCreatedAfter(stopFile, launchedAt);
public boolean askedForStop() {
return stopFile.exists();
}

File getReadyFile() {
@@ -117,9 +114,4 @@ public class ProcessCommands {
}
}
}

private boolean isCreatedAfter(File file, long launchedAt) {
// File#lastModified() can have second precision on some OS
return file.exists() && file.lastModified() / 1000 >= launchedAt / 1000;
}
}

+ 5
- 8
server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java View File

@@ -21,7 +21,7 @@ package org.sonar.process;

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";
@@ -32,7 +32,6 @@ public class ProcessEntryPoint {
private final ProcessCommands commands;
private final SystemExit exit;
private volatile Monitored monitored;
private volatile long launchedAt;
private volatile StopperThread stopperThread;
private final StopWatcher stopWatcher;

@@ -49,7 +48,6 @@ public class ProcessEntryPoint {
this.props = props;
this.exit = exit;
this.commands = commands;
this.launchedAt = System.currentTimeMillis();
this.stopWatcher = new StopWatcher(commands, this);
}

@@ -68,6 +66,7 @@ public class ProcessEntryPoint {
if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) {
throw new IllegalStateException("Already started");
}
commands.prepare();
monitored = mp;

try {
@@ -116,10 +115,12 @@ public class ProcessEntryPoint {
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();
}
}

@@ -131,10 +132,6 @@ public class ProcessEntryPoint {
return shutdownHook;
}

long getLaunchedAt() {
return launchedAt;
}

public static ProcessEntryPoint createForArguments(String[] args) {
Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args);
ProcessCommands commands = new ProcessCommands(

+ 17
- 13
server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java View File

@@ -23,31 +23,35 @@ import org.slf4j.LoggerFactory;

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();
}
}


+ 26
- 0
server/sonar-process/src/main/java/org/sonar/process/Stoppable.java View File

@@ -0,0 +1,26 @@
/*
* 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();

}

+ 1
- 1
server/sonar-process/src/main/java/org/sonar/process/StopperThread.java View File

@@ -57,6 +57,6 @@ class StopperThread extends Thread {
LoggerFactory.getLogger(getClass()).error(String.format("Can not stop in %dms", terminationTimeout), e);
}
executor.shutdownNow();
commands.finalizeProcess();
commands.endWatch();
}
}

+ 9
- 33
server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java View File

@@ -40,11 +40,11 @@ public class ProcessCommandsTest {
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();
@@ -58,7 +58,7 @@ public class ProcessCommandsTest {

ProcessCommands commands = new ProcessCommands(readyFile, temp.newFile());
try {
commands.prepareMonitor();
commands.prepare();
fail();
} catch (MessageException e) {
// ok
@@ -70,39 +70,15 @@ public class ProcessCommandsTest {
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();
}
}

+ 15
- 1
sonar-application/src/main/java/org/sonar/application/App.java View File

@@ -22,8 +22,11 @@ package org.sonar.application;
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;

@@ -35,7 +38,7 @@ import java.util.Properties;
/**
* Entry-point of process that starts and monitors elasticsearch and web servers
*/
public class App {
public class App implements Stoppable {

private final Monitor monitor;

@@ -48,6 +51,12 @@ public class App {
}

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();
}
@@ -103,4 +112,9 @@ public class App {
App app = new App();
app.start(props);
}

@Override
public void stopAsync() {
monitor.stopAsync();
}
}

Loading…
Cancel
Save