]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-4898 add sonar.enableStopCommand property for internal use
authorSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 22 Sep 2014 20:56:07 +0000 (22:56 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Mon, 22 Sep 2014 20:56:07 +0000 (22:56 +0200)
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/ProcessRef.java
server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java
server/sonar-process/src/main/java/org/sonar/process/Stoppable.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/StopperThread.java
server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java
sonar-application/src/main/java/org/sonar/application/App.java

index 0f2abc34b16e01f4bd20f43152b536097d2aecdc..77a4b607d0d5bdd3aafcd2a202e0cd75b10b0c69 100644 (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
index 45141afdc5438d0c0a77253213a18e0a0d616c0e..72c96d129baceff0c34763e5d85bf1b19970f1fd 100644 (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() {
index ca34de55a5bb74d7e98b404068b683bb98279969..672fdf6cb6ccf57aa0851df37f920efd84a739dd 100644 (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
    */
index 259af6b1eeb36494033d0a1c25aaaef9762fd279..417d62cb5bff5a9649c363d39513727d692c0cf1 100644 (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;
-  }
 }
index f4d6e7f90f71a5a5ae5303513d1c6d56b456171a..870f1aea067075bbfa40e28f7325a69fbc2ecf94 100644 (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(
index 090b005242354d23b666cf41178bfa89c439f355..b7b4ffa15d8f8a271bf2116633f10ab6b07c9259 100644 (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();
     }
   }
 
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 (file)
index 0000000..afd464a
--- /dev/null
@@ -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();
+
+}
index 317cd6f62c3eb02fe3480f8c31ee11caeafb0af4..764d551a93c623a7e9b1202814fd7ad987f683af 100644 (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();
   }
 }
index 49c8f024c697cd961da4ab02d69abd35f91f49c4..e81a537e2ee984da26bac443bb031754db8c997b 100644 (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();
-  }
 }
index db1f9fcb5f6e075293f63ebbadcb23226e1f4ddc..c051e2f1c8d11f5d09a305c5012bed5b1b9ef45b 100644 (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();
+  }
 }