]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-7168 fix stop during restart not working
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 8 Jan 2016 17:01:57 +0000 (18:01 +0100)
committerSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Wed, 13 Jan 2016 12:42:43 +0000 (13:42 +0100)
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

17 files changed:
server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaCommand.java
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/test/java/org/sonar/process/monitor/JavaCommandTest.java
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/JavaProcessLauncherTest.java
server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java
server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
server/sonar-process/src/main/java/org/sonar/process/Lifecycle.java
server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
server/sonar-process/src/test/java/org/sonar/process/LifecycleTest.java
server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
server/sonar-server/src/main/java/org/sonar/server/platform/ws/RestartAction.java
sonar-application/src/main/java/org/sonar/application/App.java
sonar-application/src/test/java/org/sonar/application/AppTest.java

index 9187e0305eaa2c0c2bcb6895b27756e9c0c8cdff..8a5551039d5b577a23dd16c11bf898db54f73960 100644 (file)
@@ -52,8 +52,6 @@ public class JavaCommand {
 
   private final Map<String, String> envVariables = new HashMap<>(System.getenv());
 
-  private File tempDir = null;
-
   private int processIndex = -1;
 
   public JavaCommand(String key) {
@@ -78,15 +76,6 @@ public class JavaCommand {
     return this;
   }
 
-  public File getTempDir() {
-    return tempDir;
-  }
-
-  public JavaCommand setTempDir(File tempDir) {
-    this.tempDir = tempDir;
-    return this;
-  }
-
   public List<String> getJavaOptions() {
     return javaOptions;
   }
index 6a2eb83bb44685d78b776d850b712198e495f8d8..9bf969cd9444fac3a7c055f6d2d5cc14b27ddb87 100644 (file)
  */
 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;
@@ -33,20 +26,33 @@ import java.util.ArrayList;
 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[{}]: {}",
@@ -69,7 +75,7 @@ public class JavaProcessLauncher {
     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());
@@ -101,7 +107,7 @@ public class JavaProcessLauncher {
       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();
index 749389d105478f1ff0f4e430729d4015c7ebd4e9..7dcf8ede0d0f8d4501eae75f3300fa463dc20070 100644 (file)
@@ -19,6 +19,7 @@
  */
 package org.sonar.process.monitor;
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.List;
@@ -27,6 +28,7 @@ import javax.annotation.CheckForNull;
 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;
@@ -37,9 +39,11 @@ public class Monitor {
   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;
@@ -53,15 +57,18 @@ public class Monitor {
   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());
   }
 
   /**
@@ -108,6 +115,12 @@ public class Monitor {
     }
   }
 
+  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);
@@ -145,7 +158,9 @@ public class Monitor {
 
   boolean waitForOneRestart() {
     boolean restartRequested = awaitChildProcessesTermination();
+    trace("finished waiting, restartRequested=" + restartRequested);
     if (restartRequested) {
+      trace("awaitTermination(restartor)=" + restartor);
       awaitTermination(restartor);
     }
     return restartRequested;
@@ -176,8 +191,8 @@ public class Monitor {
    * 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();
@@ -187,17 +202,18 @@ public class Monitor {
 
   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();
     }
   }
 
@@ -207,7 +223,11 @@ public class Monitor {
    * 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();
     }
   }
@@ -288,12 +308,38 @@ public class Monitor {
 
   }
 
+  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);
@@ -339,6 +385,12 @@ public class Monitor {
     }
   }
 
+  private static void awaitTermination(Thread... threads) {
+    for (Thread thread : threads) {
+      awaitTermination(thread);
+    }
+  }
+
   private static void awaitTermination(@Nullable Thread t) {
     if (t == null) {
       return;
index cc376db2d3535330e7e0d57de324fe363020e5a5..5e36ef6c797d46abadfafbc44172b8dc93bc0358 100644 (file)
@@ -44,8 +44,6 @@ public class JavaCommandTest {
 
     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");
@@ -56,7 +54,6 @@ public class JavaCommandTest {
     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
index a4c2c82ca645468c4d7703592625a1158cf4ca95..cdd723c6fb4ecdf9e6292b8d57646e36ebb61113 100644 (file)
  */
 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
index a9937311e1e6476973c74b58c04bfc19560551bc..038d9aa64e15c5d4da188ff968cb377136b28cc3 100644 (file)
@@ -32,6 +32,7 @@ import org.apache.commons.lang.StringUtils;
 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;
@@ -90,6 +91,14 @@ public class MonitorTest {
   @Rule
   public TemporaryFolder temp = new TemporaryFolder();
 
+  private File tempDir;
+
+  @Before
+  public void setUp() throws Exception {
+    tempDir = temp.newFolder();
+
+  }
+
   /**
    * Safeguard
    */
@@ -104,8 +113,8 @@ public class MonitorTest {
   }
 
   @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();
@@ -116,7 +125,7 @@ public class MonitorTest {
 
   @Test
   public void fail_to_start_multiple_times() throws Exception {
-    monitor = newDefaultMonitor();
+    monitor = newDefaultMonitor(tempDir);
     monitor.start(Arrays.asList(newStandardProcessCommand()));
     boolean failed = false;
     try {
@@ -130,8 +139,8 @@ public class MonitorTest {
 
   @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()));
 
@@ -148,9 +157,9 @@ public class MonitorTest {
 
   @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)
@@ -173,9 +182,9 @@ public class MonitorTest {
 
   @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();
@@ -190,9 +199,9 @@ public class MonitorTest {
 
   @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();
@@ -221,9 +230,9 @@ public class MonitorTest {
 
   @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();
@@ -242,9 +251,9 @@ public class MonitorTest {
 
   @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();
@@ -260,11 +269,11 @@ public class MonitorTest {
   }
 
   @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 {
@@ -274,11 +283,10 @@ public class MonitorTest {
 
   @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));
@@ -289,9 +297,19 @@ public class MonitorTest {
     }
   }
 
-  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);
   }
 
   /**
@@ -302,16 +320,16 @@ public class MonitorTest {
     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;
     }
 
@@ -319,8 +337,7 @@ public class MonitorTest {
       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));
     }
 
     /**
@@ -386,7 +403,7 @@ public class MonitorTest {
 
     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);
@@ -402,7 +419,7 @@ public class MonitorTest {
     }
 
     private boolean fileExists(String filename) {
-      File file = new File(tempDir, filename);
+      File file = new File(tempDir, httpPort + "-" + filename);
       return file.isFile() && file.exists();
     }
   }
@@ -542,8 +559,7 @@ public class MonitorTest {
   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");
   }
 
 }
diff --git a/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java b/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java
new file mode 100644 (file)
index 0000000..d6c1ee4
--- /dev/null
@@ -0,0 +1,224 @@
+/*
+ * 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();
+    }
+  }
+}
index b00a9bb91fa7bdae15b612a0687f9b755695d338..6f8286b5d7d55e599eddf1bc11230f50f5ccc4a4 100644 (file)
 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();
   }
 }
index 6aa8e1d932b86a0c5e985ea70573d2ee58f5f530..18a747e14e4b454d8969115ec5e7a42ae190a884 100644 (file)
@@ -28,6 +28,7 @@ import javax.annotation.Nullable;
 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;
@@ -39,7 +40,7 @@ public class Lifecycle {
   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();
@@ -47,10 +48,11 @@ public class Lifecycle {
   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;
   }
index 789a4b8cefc4eabad198c0d21b1d9c425c8860c4..8c0fe5ed008da89c087572136385040b4d5de069 100644 (file)
@@ -36,7 +36,7 @@ package org.sonar.process;
  *   <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;
 
diff --git a/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java
new file mode 100644 (file)
index 0000000..97a8a6d
--- /dev/null
@@ -0,0 +1,152 @@
+/*
+ * 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);
+  }
+}
index ee5f2f2124328268f0fd631c1f833264d8775b9e..2da77889377e23e4680c9671946881b8926e15d0 100644 (file)
@@ -23,11 +23,12 @@ 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.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown;
 import static org.junit.Assert.fail;
+import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
 
 public class DefaultProcessCommandsTest {
 
@@ -35,6 +36,8 @@ 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 {
@@ -55,16 +58,9 @@ public class DefaultProcessCommandsTest {
 
     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
@@ -72,12 +68,10 @@ public class DefaultProcessCommandsTest {
     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
@@ -85,12 +79,10 @@ public class DefaultProcessCommandsTest {
     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
@@ -98,11 +90,9 @@ public class DefaultProcessCommandsTest {
     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();
   }
 
@@ -114,27 +104,30 @@ public class DefaultProcessCommandsTest {
 
     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);
   }
 }
index 6337fe60e74582d2251cceb423ab904665b93b6a..b91c84754ef8b53da9f50a55d189df1dbd64002c 100644 (file)
@@ -62,10 +62,10 @@ public class LifecycleTest {
   }
 
   @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();
@@ -90,6 +90,8 @@ public class LifecycleTest {
         return newLifeCycle(STARTED, state);
       case STOPPING:
         return newLifeCycle(STARTED, state);
+      case HARD_STOPPING:
+        return newLifeCycle(STARTING, state);
       case STOPPED:
         return newLifeCycle(STOPPING, state);
       default:
index 86f4ccb4b3775698326f7086c749482dfc5e89b8..73a6b27a788c6a077032c6589b7e468faf71b600 100644 (file)
@@ -43,6 +43,7 @@ import java.io.IOException;
  */
 public class HttpProcess implements Monitored {
 
+  private final int httpPort;
   private final Server server;
   private boolean ready = false;
   // temp dir is specific to this process
@@ -50,8 +51,9 @@ public class HttpProcess implements Monitored {
   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
@@ -120,7 +122,7 @@ public class HttpProcess implements Monitored {
 
   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);
     }
index 0d395e89199cc932d290b42c9e61cdcd88681aa0..d50a751725c1ee1b3a9d21230d5f4a121535c947 100644 (file)
@@ -68,15 +68,20 @@ public class RestartAction implements SystemWsAction {
   @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();
   }
index db78e1bf0c8ef64f26d6196708cd70a7459c1340..f704fcc41a7108f33ac0ee6279829294bdb941f4 100644 (file)
  */
 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) {
@@ -56,10 +49,7 @@ public class App implements Stoppable {
 
   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();
@@ -68,14 +58,12 @@ public class App implements Stoppable {
   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/*")
@@ -89,7 +77,6 @@ public class App implements Stoppable {
         .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")
@@ -118,16 +105,14 @@ public class App implements Stoppable {
     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();
   }
+
 }
index 8b468bb2b528bca3c88043cbde2ec9ff9fa6805f..03b33e8111b75e7de0cbe1791d56a165434ccc5c 100644 (file)
@@ -52,30 +52,6 @@ public class AppTest {
       .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);