From: Simon Brandhof Date: Mon, 22 Sep 2014 20:56:07 +0000 (+0200) Subject: SONAR-4898 add sonar.enableStopCommand property for internal use X-Git-Tag: 4.5-RC3~13 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=f00cda378e649a87b2b1693db5aaaec37461a481;p=sonarqube.git SONAR-4898 add sonar.enableStopCommand property for internal use --- 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 0f2abc34b16..77a4b607d0d 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 @@ -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 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 45141afdc54..72c96d129ba 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 @@ -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() { diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java index ca34de55a5b..672fdf6cb6c 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java @@ -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 */ 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 259af6b1eeb..417d62cb5bf 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 @@ -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; - } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java index f4d6e7f90f7..870f1aea067 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java @@ -21,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( diff --git a/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java b/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java index 090b0052423..b7b4ffa15d8 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java +++ b/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java @@ -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(); } } diff --git a/server/sonar-process/src/main/java/org/sonar/process/Stoppable.java b/server/sonar-process/src/main/java/org/sonar/process/Stoppable.java new file mode 100644 index 00000000000..afd464a67a1 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/Stoppable.java @@ -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(); + +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java index 317cd6f62c3..764d551a93c 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java +++ b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java @@ -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(); } } diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java index 49c8f024c69..e81a537e2ee 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java @@ -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(); - } } 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 db1f9fcb5f6..c051e2f1c8d 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -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 /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(); + } }