]> source.dussan.org Git - sonarqube.git/commitdiff
SONAR-9590 move shared memory file classes to dedicated package
authorSébastien Lesaint <sebastien.lesaint@sonarsource.com>
Fri, 18 Aug 2017 07:51:33 +0000 (09:51 +0200)
committerSimon Brandhof <simon.brandhof@sonarsource.com>
Tue, 5 Sep 2017 12:24:12 +0000 (14:24 +0200)
33 files changed:
server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java
server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java
server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java
server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java
server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java
server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java [deleted file]
server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java [deleted file]
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/StopperThread.java
server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/AllProcessesCommands.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommands.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/ProcessCommands.java [new file with mode: 0644]
server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/package-info.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java [deleted file]
server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
server/sonar-process/src/test/java/org/sonar/process/StopWatcherTest.java
server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java
server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/AllProcessesCommandsTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommandsTest.java [new file with mode: 0644]
server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
server/sonar-server/src/main/java/org/sonar/ce/http/CeHttpClientImpl.java
server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java
server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
server/sonar-server/src/test/java/org/sonar/ce/http/CeHttpClientTest.java
server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java

index 3a8f28fb4a450dbf39b6010ca2d1fa409237b100..bf92cfb6ec055ede4ef530bdad187d71a41d2831 100644 (file)
@@ -30,7 +30,7 @@ import java.util.Map;
 import java.util.Optional;
 import java.util.Properties;
 import org.slf4j.LoggerFactory;
-import org.sonar.process.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkState;
index cb1d68eadd536e92c5d0d9ebf161ee745453f842..8cc7a8c38ec4feb770880b01dec9cb9678307c31 100644 (file)
@@ -35,7 +35,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.process.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
index 99d22bd26f3f2b6e2ea1a179aee15a883e5c9809..f397d608d55ae566a1c7ade356520e629c58dc7d 100644 (file)
@@ -32,7 +32,7 @@ import java.util.EnumSet;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.sonar.application.config.AppSettings;
-import org.sonar.process.AllProcessesCommands;
+import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
 
 import static java.lang.String.format;
 import static java.nio.file.FileVisitResult.CONTINUE;
index 77ebdeeb521711966a6e257607c1b67f262d2851..672debcadc0ee517761a09a30a5e14b24299b803 100644 (file)
@@ -19,7 +19,7 @@
  */
 package org.sonar.application.process;
 
-import org.sonar.process.ProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 import org.sonar.process.ProcessId;
 
 import static java.util.Objects.requireNonNull;
index 19b8eb6504f28b4cb2113d2432afd19d602194a1..4bb42a72f4563b415b063132f8db84818ed19b7c 100644 (file)
@@ -35,8 +35,8 @@ import java.util.stream.Collectors;
 import org.apache.commons.io.IOUtils;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
-import org.sonar.process.AllProcessesCommands;
-import org.sonar.process.ProcessCommands;
+import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 import org.sonar.process.ProcessId;
 
 import static java.lang.String.format;
@@ -47,7 +47,7 @@ import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT;
 
 public class ProcessLauncherImpl implements ProcessLauncher {
   private static final Logger LOG = LoggerFactory.getLogger(ProcessLauncherImpl.class);
-  public static final String ELASTICSEARCH_JVM_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
+  private static final String ELASTICSEARCH_JVM_OPTIONS_HEADER = "# This file has been automatically generated by SonarQube during startup.\n" +
     "# Please use the sonar.search.javaOpts in sonar.properties to specify jvm options for Elasticsearch\n" +
     "\n" +
     "# DO NOT EDIT THIS FILE\n" +
index 6c92a14a2e2888912741c9450642dea0707ffd32..fc042f614c312da59279b4fd29033f4c078aeb6f 100644 (file)
@@ -22,8 +22,8 @@ package org.sonar.application.process;
 import org.sonar.application.FileSystem;
 import org.sonar.application.Scheduler;
 import org.sonar.application.config.AppSettings;
-import org.sonar.process.DefaultProcessCommands;
-import org.sonar.process.ProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 import org.sonar.process.ProcessId;
 import org.sonar.process.ProcessProperties;
 
index 16c11d89946150fe2061890d977cf41aeec0dd8c..3786190fb375715ca6666d2d1b759ad8de9f44d7 100644 (file)
@@ -33,11 +33,11 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.application.config.TestAppSettings;
-import org.sonar.process.AllProcessesCommands;
+import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
 import org.sonar.process.ProcessProperties;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
+import static org.sonar.process.sharedmemoryfile.ProcessCommands.MAX_PROCESSES;
 
 public class AppFileSystemTest {
 
index c7a779bcb610d675c62390a3721f709a86246095..66c60fc73ee1f65ad6323233930223a7df9eecd4 100644 (file)
@@ -24,7 +24,7 @@ import java.io.InputStream;
 import java.util.concurrent.TimeUnit;
 import org.junit.Test;
 import org.mockito.Mockito;
-import org.sonar.process.ProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 import org.sonar.process.ProcessId;
 
 import static org.assertj.core.api.Assertions.assertThat;
index c7d55a92b57767847550c18386e93fd8bb3d76f0..d45927b589f1e871a84a39534f7a2d4128059953 100644 (file)
@@ -30,7 +30,7 @@ import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
-import org.sonar.process.AllProcessesCommands;
+import org.sonar.process.sharedmemoryfile.AllProcessesCommands;
 import org.sonar.process.ProcessId;
 
 import static org.assertj.core.api.Assertions.assertThat;
index 2ac305797e49c6dc4198f615ca6f1a1f26700195..6d0b9725453e55d1ced45d16740ab4f35b47722f 100644 (file)
@@ -29,7 +29,7 @@ import org.junit.rules.Timeout;
 import org.sonar.application.FileSystem;
 import org.sonar.application.Scheduler;
 import org.sonar.application.config.AppSettings;
-import org.sonar.process.ProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 import org.sonar.process.ProcessProperties;
 
 import static org.assertj.core.api.Assertions.assertThat;
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
deleted file mode 100644 (file)
index ea4b6ae..0000000
+++ /dev/null
@@ -1,330 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info 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 java.nio.charset.StandardCharsets;
-import org.apache.commons.io.IOUtils;
-
-import static java.lang.String.format;
-import static org.apache.commons.lang.StringUtils.rightPad;
-import static org.sonar.process.ProcessCommands.MAX_PROCESSES;
-
-/**
- * Process inter-communication to :
- * <ul>
- *   <li>share status of child process</li>
- *   <li>stop/restart child process</li>
- * </ul>
- *
- * <p>
- * It relies on a single file accessed by all processes through a {@link MappedByteBuffer}.<br/>
- * 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>
- * </p>
- *
- * <p>
- * The file contains {@link ProcessCommands#MAX_PROCESSES} groups of {@link #BYTE_LENGTH_FOR_ONE_PROCESS} bits.
- * Each group of byte is used as follow:
- * <ul>
- *   <li>First byte contains {@link #EMPTY} until process is UP and writes {@link #UP}</li>
- *   <li>Second byte contains {@link #EMPTY} until any process requests current one to stop by writing value {@link #STOP}</li>
- *   <li>Third byte contains {@link #EMPTY} until any process requests current one to restart by writing value {@link #RESTART}.
- *       Process acknowledges restart by writing back {@link #EMPTY}</li>
- *   <li>Fourth byte will always contain {@link #EMPTY} unless process declares that it is operational by writing {@link #OPERATIONAL}.
- *       This does not imply that is done starting.</li>
- *   <li>The next 8 bytes contains a long (value of {@link System#currentTimeMillis()}) which represents the date of the last ping</li>
- * </ul>
- * </p>
- */
-public class AllProcessesCommands implements AutoCloseable {
-  private static final int UP_BYTE_OFFSET = 0;
-  private static final int STOP_BYTE_OFFSET = 1;
-  private static final int RESTART_BYTE_OFFSET = 2;
-  private static final int OPERATIONAL_BYTE_OFFSET = 3;
-  private static final int PING_BYTE_OFFSET = 4;
-  private static final int SYSTEM_INFO_URL_BYTE_OFFSET = PING_BYTE_OFFSET + 8;
-
-  private static final int SYSTEM_INFO_URL_SIZE_IN_BYTES = 500;
-
-  private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 1 + 1 + 8 + SYSTEM_INFO_URL_SIZE_IN_BYTES;
-
-  // 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;
-
-  private static final byte STOP = (byte) 0xFF;
-  private static final byte RESTART = (byte) 0xAA;
-  private static final byte OPERATIONAL = (byte) 0x59;
-  private static final byte UP = (byte) 0x01;
-  private static final byte EMPTY = (byte) 0x00;
-
-  // VisibleForTesting
-  final MappedByteBuffer mappedByteBuffer;
-  private final RandomAccessFile sharedMemory;
-
-  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 void clean() {
-    for (int i = 0; i < MAX_PROCESSES; i++) {
-      cleanData(i);
-    }
-  }
-
-  public ProcessCommands create(int processNumber) {
-    return createForProcess(processNumber, false);
-  }
-
-  public ProcessCommands createAfterClean(int processNumber) {
-    return createForProcess(processNumber, true);
-  }
-
-  private ProcessCommands createForProcess(int processNumber, boolean clean) {
-    checkProcessNumber(processNumber);
-    ProcessCommands processCommands = new ProcessCommandsImpl(processNumber);
-    if (clean) {
-      cleanData(processNumber);
-    }
-    return processCommands;
-  }
-
-  boolean isUp(int processNumber) {
-    return readByte(processNumber, UP_BYTE_OFFSET) == UP;
-  }
-
-  /**
-   * To be executed by child process to declare that it is done starting
-   */
-  void setUp(int processNumber) {
-    writeByte(processNumber, UP_BYTE_OFFSET, UP);
-  }
-
-  boolean isOperational(int processNumber) {
-    return readByte(processNumber, OPERATIONAL_BYTE_OFFSET) == OPERATIONAL;
-  }
-
-  /**
-   * To be executed by child process to declare that it is started and fully operational
-   */
-  void setOperational(int processNumber) {
-    writeByte(processNumber, OPERATIONAL_BYTE_OFFSET, OPERATIONAL);
-  }
-
-  void ping(int processNumber) {
-    writeLong(processNumber, PING_BYTE_OFFSET, System.currentTimeMillis());
-  }
-
-  long getLastPing(int processNumber) {
-    return readLong(processNumber, PING_BYTE_OFFSET);
-  }
-
-  String getSystemInfoUrl(int processNumber) {
-    byte[] urlBytes = readBytes(processNumber, SYSTEM_INFO_URL_BYTE_OFFSET, SYSTEM_INFO_URL_SIZE_IN_BYTES);
-    return new String(urlBytes, StandardCharsets.US_ASCII).trim();
-  }
-
-  void setSystemInfoUrl(int processNumber, String url) {
-    byte[] urlBytes = rightPad(url, SYSTEM_INFO_URL_SIZE_IN_BYTES).getBytes(StandardCharsets.US_ASCII);
-    if (urlBytes.length > SYSTEM_INFO_URL_SIZE_IN_BYTES) {
-      throw new IllegalArgumentException(format("System Info URL is too long. Max is %d bytes. Got: %s", SYSTEM_INFO_URL_SIZE_IN_BYTES, url));
-    }
-    writeBytes(processNumber, SYSTEM_INFO_URL_BYTE_OFFSET, urlBytes);
-  }
-
-  /**
-   * To be executed by monitor process to ask for child process termination
-   */
-  void askForStop(int processNumber) {
-    writeByte(processNumber, STOP_BYTE_OFFSET, STOP);
-  }
-
-  boolean askedForStop(int processNumber) {
-    return readByte(processNumber, STOP_BYTE_OFFSET) == STOP;
-  }
-
-  void askForRestart(int processNumber) {
-    writeByte(processNumber, RESTART_BYTE_OFFSET, RESTART);
-  }
-
-  boolean askedForRestart(int processNumber) {
-    return readByte(processNumber, RESTART_BYTE_OFFSET) == RESTART;
-  }
-
-  void acknowledgeAskForRestart(int processNumber) {
-    writeByte(processNumber, RESTART_BYTE_OFFSET, EMPTY);
-  }
-
-  @Override
-  public void close() {
-    IOUtils.closeQuietly(sharedMemory);
-  }
-
-  public void checkProcessNumber(int processNumber) {
-    if (processNumber < 0 || processNumber >= MAX_PROCESSES) {
-      throw new IllegalArgumentException(format("Process number %s is not valid", processNumber));
-    }
-  }
-
-  private void cleanData(int processNumber) {
-    for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) {
-      writeByte(processNumber, i, EMPTY);
-    }
-  }
-
-  private void writeByte(int processNumber, int offset, byte value) {
-    mappedByteBuffer.put(offset(processNumber) + offset, value);
-  }
-
-  private void writeBytes(int processNumber, int offset, byte[] value) {
-    int bufferOffset = offset(processNumber) + offset;
-    for (int i = 0; i < value.length; i++) {
-      mappedByteBuffer.put(bufferOffset + i, value[i]);
-    }
-  }
-
-  private byte readByte(int processNumber, int offset) {
-    return mappedByteBuffer.get(offset(processNumber) + offset);
-  }
-
-  private byte[] readBytes(int processNumber, int offset, int length) {
-    int bufferOffset = offset(processNumber) + offset;
-    byte[] bytes = new byte[length];
-    for (int i = 0; i < length; i++) {
-      bytes[i] = mappedByteBuffer.get(bufferOffset + i);
-    }
-    return bytes;
-  }
-
-  private void writeLong(int processNumber, int offset, long value) {
-    mappedByteBuffer.putLong(offset(processNumber) + offset, value);
-  }
-
-  private long readLong(int processNumber, int offset) {
-    return mappedByteBuffer.getLong(offset(processNumber) + offset);
-  }
-
-  // VisibleForTesting
-  int offset(int processNumber) {
-    return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber;
-  }
-
-  private class ProcessCommandsImpl implements ProcessCommands {
-
-    private final int processNumber;
-
-    public ProcessCommandsImpl(int processNumber) {
-      this.processNumber = processNumber;
-    }
-
-    @Override
-    public boolean isUp() {
-      return AllProcessesCommands.this.isUp(processNumber);
-    }
-
-    @Override
-    public void setUp() {
-      AllProcessesCommands.this.setUp(processNumber);
-    }
-
-    @Override
-    public boolean isOperational() {
-      return AllProcessesCommands.this.isOperational(processNumber);
-    }
-
-    @Override
-    public void setOperational() {
-      AllProcessesCommands.this.setOperational(processNumber);
-    }
-
-    @Override
-    public void ping() {
-      AllProcessesCommands.this.ping(processNumber);
-    }
-
-    @Override
-    public long getLastPing() {
-      return AllProcessesCommands.this.getLastPing(processNumber);
-    }
-
-    @Override
-    public void setHttpUrl(String s) {
-      AllProcessesCommands.this.setSystemInfoUrl(processNumber, s);
-    }
-
-    @Override
-    public String getHttpUrl() {
-      return AllProcessesCommands.this.getSystemInfoUrl(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() {
-      endWatch();
-    }
-  }
-}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
deleted file mode 100644 (file)
index 8c29a97..0000000
+++ /dev/null
@@ -1,142 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info 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.slf4j.LoggerFactory;
-
-/**
- * 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 {
-  private final AllProcessesCommands allProcessesCommands;
-  private final ProcessCommands delegate;
-
-  private DefaultProcessCommands(File directory, int processNumber, boolean clean) {
-    this.allProcessesCommands = new AllProcessesCommands(directory);
-    this.delegate = clean ? allProcessesCommands.createAfterClean(processNumber) : allProcessesCommands.create(processNumber);
-  }
-
-  /**
-   * Main DefaultProcessCommands will clear the shared memory space of the specified process number when created and will
-   * then write and/or read to it.
-   * Therefor there should be only one main DefaultProcessCommands.
-   */
-  public static DefaultProcessCommands main(File directory, int processNumber) {
-    return new DefaultProcessCommands(directory, processNumber, true);
-  }
-
-  /**
-   * Secondary DefaultProcessCommands will read and write to the shared memory space but will not clear it. Therefor, there
-   * can be any number of them.
-   */
-  public static DefaultProcessCommands secondary(File directory, int processNumber) {
-    return new DefaultProcessCommands(directory, processNumber, false);
-  }
-
-  /**
-   * Clears the shared memory space of the specified process number.
-   */
-  public static void reset(File directory, int processNumber) {
-    try (DefaultProcessCommands processCommands = new DefaultProcessCommands(directory, processNumber, true)) {
-      // nothing else to do than open file and reset the space of specified process
-    }
-  }
-
-  @Override
-  public boolean isUp() {
-    return delegate.isUp();
-  }
-
-  @Override
-  public void setUp() {
-    delegate.setUp();
-  }
-
-  @Override
-  public boolean isOperational() {
-    return delegate.isOperational();
-  }
-
-  @Override
-  public void setOperational() {
-    delegate.setOperational();
-  }
-
-  @Override
-  public void ping() {
-    delegate.ping();
-  }
-
-  @Override
-  public long getLastPing() {
-    return delegate.getLastPing();
-  }
-
-  @Override
-  public void setHttpUrl(String s) {
-    delegate.setHttpUrl(s);
-  }
-
-  @Override
-  public String getHttpUrl() {
-    return delegate.getHttpUrl();
-  }
-
-  @Override
-  public void askForStop() {
-    delegate.askForStop();
-  }
-
-  @Override
-  public boolean askedForStop() {
-    return delegate.askedForStop();
-  }
-
-  @Override
-  public void askForRestart() {
-    delegate.askForRestart();
-  }
-
-  @Override
-  public boolean askedForRestart() {
-    return delegate.askedForRestart();
-  }
-
-  @Override
-  public void acknowledgeAskForRestart() {
-    delegate.acknowledgeAskForRestart();
-  }
-
-  @Override
-  public void endWatch() {
-    try {
-      close();
-    } catch (Exception e) {
-      LoggerFactory.getLogger(getClass()).error("Failed to close DefaultProcessCommands", e);
-    }
-  }
-
-  @Override
-  public void close() {
-    allProcessesCommands.close();
-  }
-}
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
deleted file mode 100644 (file)
index 6ec0bf1..0000000
+++ /dev/null
@@ -1,85 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info 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;
-
-/**
- * Process inter-communication to :
- * <ul>
- *   <li>share status of specific process</li>
- *   <li>stop/restart a specific processes</li>
- * </ul>
- *
- * @see DefaultProcessCommands#main(File, int)
- * @see DefaultProcessCommands#secondary(File, int)
- */
-public interface ProcessCommands extends AutoCloseable {
-
-  int MAX_PROCESSES = 5;
-
-  boolean isUp();
-
-  /**
-   * To be executed by child process to declare that it is done starting
-   */
-  void setUp();
-
-  boolean isOperational();
-
-  /**
-   * To be executed by child process to declare that it is done starting and fully operational.
-   *
-   * @throws IllegalStateException if {@link #setUp()} has not been called
-   */
-  void setOperational();
-
-  void ping();
-
-  long getLastPing();
-
-  void setHttpUrl(String s);
-
-  String getHttpUrl();
-
-  /**
-   * To be executed by monitor process to ask for child process termination
-   */
-  void askForStop();
-
-  boolean askedForStop();
-
-  /**
-   * To be executed by child process to ask for restart of all child processes
-   */
-  void askForRestart();
-
-  /**
-   * Can be called by child or monitor process to know whether child process asked for restart
-   */
-  boolean askedForRestart();
-
-  /**
-   * To be executed by monitor process to acknowledge restart request from child process.
-   */
-  void acknowledgeAskForRestart();
-
-  void endWatch();
-}
index 15190a41dccfcc6830822ac8eef0b787501b11be..b1476fba53e68f2ce58916fbbf27b93ea00c0f6d 100644 (file)
@@ -22,6 +22,8 @@ package org.sonar.process;
 import java.io.File;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
 public class ProcessEntryPoint implements Stoppable {
 
index 0043bccf23973e0557fca296f47bfdebf24b936c..1739dc0423f1b28834a4b456c7ac126d04351cfc 100644 (file)
@@ -20,6 +20,7 @@
 package org.sonar.process;
 
 import org.slf4j.LoggerFactory;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
 /**
  * This watchdog asks for graceful termination of process when the file
index f537d204bcbb7b250423b774f235e2444786f927..746cd31dc1e85c9c7b6c47bd3da5c149c4be345d 100644 (file)
@@ -25,6 +25,7 @@ import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
 /**
  * Gracefully stops process in a timely fashion
diff --git a/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/AllProcessesCommands.java b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/AllProcessesCommands.java
new file mode 100644 (file)
index 0000000..c5d7ec4
--- /dev/null
@@ -0,0 +1,330 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.sharedmemoryfile;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.RandomAccessFile;
+import java.nio.MappedByteBuffer;
+import java.nio.channels.FileChannel;
+import java.nio.charset.StandardCharsets;
+import org.apache.commons.io.IOUtils;
+
+import static java.lang.String.format;
+import static org.apache.commons.lang.StringUtils.rightPad;
+import static org.sonar.process.sharedmemoryfile.ProcessCommands.MAX_PROCESSES;
+
+/**
+ * Process inter-communication to :
+ * <ul>
+ *   <li>share status of child process</li>
+ *   <li>stop/restart child process</li>
+ * </ul>
+ *
+ * <p>
+ * It relies on a single file accessed by all processes through a {@link MappedByteBuffer}.<br/>
+ * 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>
+ * </p>
+ *
+ * <p>
+ * The file contains {@link ProcessCommands#MAX_PROCESSES} groups of {@link #BYTE_LENGTH_FOR_ONE_PROCESS} bits.
+ * Each group of byte is used as follow:
+ * <ul>
+ *   <li>First byte contains {@link #EMPTY} until process is UP and writes {@link #UP}</li>
+ *   <li>Second byte contains {@link #EMPTY} until any process requests current one to stop by writing value {@link #STOP}</li>
+ *   <li>Third byte contains {@link #EMPTY} until any process requests current one to restart by writing value {@link #RESTART}.
+ *       Process acknowledges restart by writing back {@link #EMPTY}</li>
+ *   <li>Fourth byte will always contain {@link #EMPTY} unless process declares that it is operational by writing {@link #OPERATIONAL}.
+ *       This does not imply that is done starting.</li>
+ *   <li>The next 8 bytes contains a long (value of {@link System#currentTimeMillis()}) which represents the date of the last ping</li>
+ * </ul>
+ * </p>
+ */
+public class AllProcessesCommands implements AutoCloseable {
+  private static final int UP_BYTE_OFFSET = 0;
+  private static final int STOP_BYTE_OFFSET = 1;
+  private static final int RESTART_BYTE_OFFSET = 2;
+  private static final int OPERATIONAL_BYTE_OFFSET = 3;
+  private static final int PING_BYTE_OFFSET = 4;
+  private static final int SYSTEM_INFO_URL_BYTE_OFFSET = PING_BYTE_OFFSET + 8;
+
+  private static final int SYSTEM_INFO_URL_SIZE_IN_BYTES = 500;
+
+  private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 1 + 1 + 8 + SYSTEM_INFO_URL_SIZE_IN_BYTES;
+
+  // 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;
+
+  private static final byte STOP = (byte) 0xFF;
+  private static final byte RESTART = (byte) 0xAA;
+  private static final byte OPERATIONAL = (byte) 0x59;
+  private static final byte UP = (byte) 0x01;
+  private static final byte EMPTY = (byte) 0x00;
+
+  // VisibleForTesting
+  final MappedByteBuffer mappedByteBuffer;
+  private final RandomAccessFile sharedMemory;
+
+  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 void clean() {
+    for (int i = 0; i < MAX_PROCESSES; i++) {
+      cleanData(i);
+    }
+  }
+
+  public ProcessCommands create(int processNumber) {
+    return createForProcess(processNumber, false);
+  }
+
+  public ProcessCommands createAfterClean(int processNumber) {
+    return createForProcess(processNumber, true);
+  }
+
+  private ProcessCommands createForProcess(int processNumber, boolean clean) {
+    checkProcessNumber(processNumber);
+    ProcessCommands processCommands = new ProcessCommandsImpl(processNumber);
+    if (clean) {
+      cleanData(processNumber);
+    }
+    return processCommands;
+  }
+
+  boolean isUp(int processNumber) {
+    return readByte(processNumber, UP_BYTE_OFFSET) == UP;
+  }
+
+  /**
+   * To be executed by child process to declare that it is done starting
+   */
+  void setUp(int processNumber) {
+    writeByte(processNumber, UP_BYTE_OFFSET, UP);
+  }
+
+  boolean isOperational(int processNumber) {
+    return readByte(processNumber, OPERATIONAL_BYTE_OFFSET) == OPERATIONAL;
+  }
+
+  /**
+   * To be executed by child process to declare that it is started and fully operational
+   */
+  void setOperational(int processNumber) {
+    writeByte(processNumber, OPERATIONAL_BYTE_OFFSET, OPERATIONAL);
+  }
+
+  void ping(int processNumber) {
+    writeLong(processNumber, PING_BYTE_OFFSET, System.currentTimeMillis());
+  }
+
+  long getLastPing(int processNumber) {
+    return readLong(processNumber, PING_BYTE_OFFSET);
+  }
+
+  String getSystemInfoUrl(int processNumber) {
+    byte[] urlBytes = readBytes(processNumber, SYSTEM_INFO_URL_BYTE_OFFSET, SYSTEM_INFO_URL_SIZE_IN_BYTES);
+    return new String(urlBytes, StandardCharsets.US_ASCII).trim();
+  }
+
+  void setSystemInfoUrl(int processNumber, String url) {
+    byte[] urlBytes = rightPad(url, SYSTEM_INFO_URL_SIZE_IN_BYTES).getBytes(StandardCharsets.US_ASCII);
+    if (urlBytes.length > SYSTEM_INFO_URL_SIZE_IN_BYTES) {
+      throw new IllegalArgumentException(format("System Info URL is too long. Max is %d bytes. Got: %s", SYSTEM_INFO_URL_SIZE_IN_BYTES, url));
+    }
+    writeBytes(processNumber, SYSTEM_INFO_URL_BYTE_OFFSET, urlBytes);
+  }
+
+  /**
+   * To be executed by monitor process to ask for child process termination
+   */
+  void askForStop(int processNumber) {
+    writeByte(processNumber, STOP_BYTE_OFFSET, STOP);
+  }
+
+  boolean askedForStop(int processNumber) {
+    return readByte(processNumber, STOP_BYTE_OFFSET) == STOP;
+  }
+
+  void askForRestart(int processNumber) {
+    writeByte(processNumber, RESTART_BYTE_OFFSET, RESTART);
+  }
+
+  boolean askedForRestart(int processNumber) {
+    return readByte(processNumber, RESTART_BYTE_OFFSET) == RESTART;
+  }
+
+  void acknowledgeAskForRestart(int processNumber) {
+    writeByte(processNumber, RESTART_BYTE_OFFSET, EMPTY);
+  }
+
+  @Override
+  public void close() {
+    IOUtils.closeQuietly(sharedMemory);
+  }
+
+  public void checkProcessNumber(int processNumber) {
+    if (processNumber < 0 || processNumber >= MAX_PROCESSES) {
+      throw new IllegalArgumentException(format("Process number %s is not valid", processNumber));
+    }
+  }
+
+  private void cleanData(int processNumber) {
+    for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) {
+      writeByte(processNumber, i, EMPTY);
+    }
+  }
+
+  private void writeByte(int processNumber, int offset, byte value) {
+    mappedByteBuffer.put(offset(processNumber) + offset, value);
+  }
+
+  private void writeBytes(int processNumber, int offset, byte[] value) {
+    int bufferOffset = offset(processNumber) + offset;
+    for (int i = 0; i < value.length; i++) {
+      mappedByteBuffer.put(bufferOffset + i, value[i]);
+    }
+  }
+
+  private byte readByte(int processNumber, int offset) {
+    return mappedByteBuffer.get(offset(processNumber) + offset);
+  }
+
+  private byte[] readBytes(int processNumber, int offset, int length) {
+    int bufferOffset = offset(processNumber) + offset;
+    byte[] bytes = new byte[length];
+    for (int i = 0; i < length; i++) {
+      bytes[i] = mappedByteBuffer.get(bufferOffset + i);
+    }
+    return bytes;
+  }
+
+  private void writeLong(int processNumber, int offset, long value) {
+    mappedByteBuffer.putLong(offset(processNumber) + offset, value);
+  }
+
+  private long readLong(int processNumber, int offset) {
+    return mappedByteBuffer.getLong(offset(processNumber) + offset);
+  }
+
+  // VisibleForTesting
+  int offset(int processNumber) {
+    return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber;
+  }
+
+  private class ProcessCommandsImpl implements ProcessCommands {
+
+    private final int processNumber;
+
+    public ProcessCommandsImpl(int processNumber) {
+      this.processNumber = processNumber;
+    }
+
+    @Override
+    public boolean isUp() {
+      return AllProcessesCommands.this.isUp(processNumber);
+    }
+
+    @Override
+    public void setUp() {
+      AllProcessesCommands.this.setUp(processNumber);
+    }
+
+    @Override
+    public boolean isOperational() {
+      return AllProcessesCommands.this.isOperational(processNumber);
+    }
+
+    @Override
+    public void setOperational() {
+      AllProcessesCommands.this.setOperational(processNumber);
+    }
+
+    @Override
+    public void ping() {
+      AllProcessesCommands.this.ping(processNumber);
+    }
+
+    @Override
+    public long getLastPing() {
+      return AllProcessesCommands.this.getLastPing(processNumber);
+    }
+
+    @Override
+    public void setHttpUrl(String s) {
+      AllProcessesCommands.this.setSystemInfoUrl(processNumber, s);
+    }
+
+    @Override
+    public String getHttpUrl() {
+      return AllProcessesCommands.this.getSystemInfoUrl(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() {
+      endWatch();
+    }
+  }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommands.java
new file mode 100644 (file)
index 0000000..882a3ea
--- /dev/null
@@ -0,0 +1,142 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.sharedmemoryfile;
+
+import java.io.File;
+import org.slf4j.LoggerFactory;
+
+/**
+ * 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 {
+  private final AllProcessesCommands allProcessesCommands;
+  private final ProcessCommands delegate;
+
+  private DefaultProcessCommands(File directory, int processNumber, boolean clean) {
+    this.allProcessesCommands = new AllProcessesCommands(directory);
+    this.delegate = clean ? allProcessesCommands.createAfterClean(processNumber) : allProcessesCommands.create(processNumber);
+  }
+
+  /**
+   * Main DefaultProcessCommands will clear the shared memory space of the specified process number when created and will
+   * then write and/or read to it.
+   * Therefor there should be only one main DefaultProcessCommands.
+   */
+  public static DefaultProcessCommands main(File directory, int processNumber) {
+    return new DefaultProcessCommands(directory, processNumber, true);
+  }
+
+  /**
+   * Secondary DefaultProcessCommands will read and write to the shared memory space but will not clear it. Therefor, there
+   * can be any number of them.
+   */
+  public static DefaultProcessCommands secondary(File directory, int processNumber) {
+    return new DefaultProcessCommands(directory, processNumber, false);
+  }
+
+  /**
+   * Clears the shared memory space of the specified process number.
+   */
+  public static void reset(File directory, int processNumber) {
+    try (DefaultProcessCommands processCommands = new DefaultProcessCommands(directory, processNumber, true)) {
+      // nothing else to do than open file and reset the space of specified process
+    }
+  }
+
+  @Override
+  public boolean isUp() {
+    return delegate.isUp();
+  }
+
+  @Override
+  public void setUp() {
+    delegate.setUp();
+  }
+
+  @Override
+  public boolean isOperational() {
+    return delegate.isOperational();
+  }
+
+  @Override
+  public void setOperational() {
+    delegate.setOperational();
+  }
+
+  @Override
+  public void ping() {
+    delegate.ping();
+  }
+
+  @Override
+  public long getLastPing() {
+    return delegate.getLastPing();
+  }
+
+  @Override
+  public void setHttpUrl(String s) {
+    delegate.setHttpUrl(s);
+  }
+
+  @Override
+  public String getHttpUrl() {
+    return delegate.getHttpUrl();
+  }
+
+  @Override
+  public void askForStop() {
+    delegate.askForStop();
+  }
+
+  @Override
+  public boolean askedForStop() {
+    return delegate.askedForStop();
+  }
+
+  @Override
+  public void askForRestart() {
+    delegate.askForRestart();
+  }
+
+  @Override
+  public boolean askedForRestart() {
+    return delegate.askedForRestart();
+  }
+
+  @Override
+  public void acknowledgeAskForRestart() {
+    delegate.acknowledgeAskForRestart();
+  }
+
+  @Override
+  public void endWatch() {
+    try {
+      close();
+    } catch (Exception e) {
+      LoggerFactory.getLogger(getClass()).error("Failed to close DefaultProcessCommands", e);
+    }
+  }
+
+  @Override
+  public void close() {
+    allProcessesCommands.close();
+  }
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/ProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/ProcessCommands.java
new file mode 100644 (file)
index 0000000..6d4bda3
--- /dev/null
@@ -0,0 +1,85 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.sharedmemoryfile;
+
+import java.io.File;
+
+/**
+ * Process inter-communication to :
+ * <ul>
+ *   <li>share status of specific process</li>
+ *   <li>stop/restart a specific processes</li>
+ * </ul>
+ *
+ * @see DefaultProcessCommands#main(File, int)
+ * @see DefaultProcessCommands#secondary(File, int)
+ */
+public interface ProcessCommands extends AutoCloseable {
+
+  int MAX_PROCESSES = 5;
+
+  boolean isUp();
+
+  /**
+   * To be executed by child process to declare that it is done starting
+   */
+  void setUp();
+
+  boolean isOperational();
+
+  /**
+   * To be executed by child process to declare that it is done starting and fully operational.
+   *
+   * @throws IllegalStateException if {@link #setUp()} has not been called
+   */
+  void setOperational();
+
+  void ping();
+
+  long getLastPing();
+
+  void setHttpUrl(String s);
+
+  String getHttpUrl();
+
+  /**
+   * To be executed by monitor process to ask for child process termination
+   */
+  void askForStop();
+
+  boolean askedForStop();
+
+  /**
+   * To be executed by child process to ask for restart of all child processes
+   */
+  void askForRestart();
+
+  /**
+   * Can be called by child or monitor process to know whether child process asked for restart
+   */
+  boolean askedForRestart();
+
+  /**
+   * To be executed by monitor process to acknowledge restart request from child process.
+   */
+  void acknowledgeAskForRestart();
+
+  void endWatch();
+}
diff --git a/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/package-info.java b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/package-info.java
new file mode 100644 (file)
index 0000000..126be7e
--- /dev/null
@@ -0,0 +1,23 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.
+ */
+@ParametersAreNonnullByDefault
+package org.sonar.process.sharedmemoryfile;
+
+import javax.annotation.ParametersAreNonnullByDefault;
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
deleted file mode 100644 (file)
index 4667f05..0000000
+++ /dev/null
@@ -1,218 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info 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 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;
-  private static final byte STOP = (byte) 0xFF;
-  private static final byte RESTART = (byte) 0xAA;
-  private static final byte UP = (byte) 0x01;
-  private static final byte OPERATIONAL = (byte) 0x59;
-  private static final byte EMPTY = (byte) 0x00;
-
-  @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 write_and_read_up() throws IOException {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int offset = 0;
-
-      assertThat(commands.isUp(PROCESS_NUMBER)).isFalse();
-      assertThat(readByte(commands, offset)).isEqualTo(EMPTY);
-
-      commands.setUp(PROCESS_NUMBER);
-      assertThat(commands.isUp(PROCESS_NUMBER)).isTrue();
-      assertThat(readByte(commands, offset)).isEqualTo(UP);
-    }
-  }
-
-  @Test
-  public void write_and_read_operational() throws IOException {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int offset = 3;
-
-      assertThat(commands.isOperational(PROCESS_NUMBER)).isFalse();
-      assertThat(readByte(commands, offset)).isEqualTo(EMPTY);
-
-      commands.setOperational(PROCESS_NUMBER);
-      assertThat(commands.isOperational(PROCESS_NUMBER)).isTrue();
-      assertThat(readByte(commands, offset)).isEqualTo(OPERATIONAL);
-    }
-  }
-
-  @Test
-  public void write_and_read_ping() throws IOException {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-
-      int offset = 4;
-      assertThat(readLong(commands, offset)).isEqualTo(0L);
-
-      long currentTime = System.currentTimeMillis();
-      commands.ping(PROCESS_NUMBER);
-      assertThat(readLong(commands, offset)).isGreaterThanOrEqualTo(currentTime);
-    }
-  }
-
-  @Test
-  public void write_and_read_jmx_url() throws IOException {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-
-      int offset = 12;
-      for (int i = 0; i < 500; i++) {
-        assertThat(readByte(commands, offset + i)).isEqualTo(EMPTY);
-      }
-
-      commands.setSystemInfoUrl(PROCESS_NUMBER, "jmx:foo");
-      assertThat(readByte(commands, offset)).isNotEqualTo(EMPTY);
-      assertThat(commands.getSystemInfoUrl(PROCESS_NUMBER)).isEqualTo("jmx:foo");
-    }
-  }
-
-  @Test
-  public void ask_for_stop() throws Exception {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int offset = 1;
-
-      assertThat(readByte(commands, offset)).isNotEqualTo(STOP);
-      assertThat(commands.askedForStop(PROCESS_NUMBER)).isFalse();
-
-      commands.askForStop(PROCESS_NUMBER);
-      assertThat(commands.askedForStop(PROCESS_NUMBER)).isTrue();
-      assertThat(readByte(commands, offset)).isEqualTo(STOP);
-    }
-  }
-
-  @Test
-  public void ask_for_restart() throws Exception {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int offset = 2;
-
-      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
-      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
-
-      commands.askForRestart(PROCESS_NUMBER);
-      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
-      assertThat(readByte(commands, offset)).isEqualTo(RESTART);
-    }
-  }
-
-  @Test
-  public void acknowledgeAskForRestart_has_no_effect_when_no_restart_asked() throws Exception {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int offset = 2;
-
-      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
-      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
-
-      commands.acknowledgeAskForRestart(PROCESS_NUMBER);
-      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
-      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
-    }
-  }
-
-  @Test
-  public void acknowledgeAskForRestart_resets_askForRestart_has_no_effect_when_no_restart_asked() throws Exception {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int offset = 2;
-
-      commands.askForRestart(PROCESS_NUMBER);
-      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
-      assertThat(readByte(commands, offset)).isEqualTo(RESTART);
-
-      commands.acknowledgeAskForRestart(PROCESS_NUMBER);
-      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
-      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
-    }
-  }
-
-  @Test
-  public void getProcessCommands_fails_if_processNumber_is_less_than_0() throws Exception {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int processNumber = -2;
-
-      expectedException.expect(IllegalArgumentException.class);
-      expectedException.expectMessage("Process number " + processNumber + " is not valid");
-
-      commands.createAfterClean(processNumber);
-    }
-  }
-
-  @Test
-  public void getProcessCommands_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      int processNumber = MAX_PROCESSES + 1;
-
-      expectedException.expect(IllegalArgumentException.class);
-      expectedException.expectMessage("Process number " + processNumber + " is not valid");
-
-      commands.createAfterClean(processNumber);
-    }
-  }
-
-  @Test
-  public void clean_cleans_sharedMemory_of_any_process_less_than_MAX_PROCESSES() throws IOException {
-    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
-      for (int i = 0; i < MAX_PROCESSES; i++) {
-        commands.create(i).setUp();
-      }
-      commands.clean();
-      for (int i = 0; i < MAX_PROCESSES; i++) {
-        assertThat(commands.create(i).isUp()).isFalse();
-      }
-    }
-  }
-
-  private byte readByte(AllProcessesCommands commands, int offset) {
-    return commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + offset);
-  }
-
-  private long readLong(AllProcessesCommands commands, int offset) {
-    return commands.mappedByteBuffer.getLong(offset + commands.offset(PROCESS_NUMBER));
-  }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
deleted file mode 100644 (file)
index 3c311c2..0000000
+++ /dev/null
@@ -1,245 +0,0 @@
-/*
- * SonarQube
- * Copyright (C) 2009-2017 SonarSource SA
- * mailto:info 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 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 DefaultProcessCommandsTest {
-
-  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 {
-      DefaultProcessCommands.main(dir, PROCESS_NUMBER);
-      fail();
-    } catch (IllegalArgumentException e) {
-      assertThat(e).hasMessage("Not a valid directory: " + dir.getAbsolutePath());
-    }
-  }
-
-  @Test
-  public void main_clears_the_memory_space_of_the_specified_process_number() throws IOException {
-    File dir = temp.newFolder();
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      commands.setUp();
-      commands.setHttpUrl("bla");
-      commands.setOperational();
-    }
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      assertThat(commands.isUp()).isFalse();
-      assertThat(commands.getHttpUrl()).isEmpty();
-      assertThat(commands.isOperational()).isFalse();
-    }
-  }
-
-  @Test
-  public void secondary_does_not_clear_the_memory_space_of_the_specified_process_number() throws IOException {
-    File dir = temp.newFolder();
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      commands.setUp();
-      commands.setHttpUrl("bla");
-      commands.setOperational();
-    }
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.secondary(dir, PROCESS_NUMBER)) {
-      assertThat(commands.isUp()).isTrue();
-      assertThat(commands.getHttpUrl()).isEqualTo("bla");
-      assertThat(commands.isOperational()).isTrue();
-    }
-  }
-
-  @Test
-  public void child_process_update_the_mapped_memory() throws Exception {
-    File dir = temp.newFolder();
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      assertThat(commands.isUp()).isFalse();
-      commands.setUp();
-      assertThat(commands.isUp()).isTrue();
-    }
-  }
-
-  @Test
-  public void reset_clears_only_the_memory_space_of_specified_process_number() throws IOException {
-    File dir = temp.newFolder();
-
-    try (AllProcessesCommands commands = new AllProcessesCommands(dir)) {
-      for (int i = 0; i < MAX_PROCESSES; i++) {
-        commands.setOperational(i);
-        commands.setUp(i);
-      }
-
-      int resetProcess = 3;
-      DefaultProcessCommands.reset(dir, resetProcess);
-      for (int i = 0; i < MAX_PROCESSES; i++) {
-        assertThat(commands.isOperational(i)).isEqualTo(i != resetProcess);
-        assertThat(commands.isUp(i)).isEqualTo(i != resetProcess);
-      }
-    }
-  }
-
-  @Test
-  public void ask_for_stop() throws Exception {
-    File dir = temp.newFolder();
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      assertThat(commands.askedForStop()).isFalse();
-
-      commands.askForStop();
-      assertThat(commands.askedForStop()).isTrue();
-    }
-  }
-
-  @Test
-  public void ask_for_restart() throws Exception {
-    File dir = temp.newFolder();
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      assertThat(commands.askedForRestart()).isFalse();
-
-      commands.askForRestart();
-      assertThat(commands.askedForRestart()).isTrue();
-    }
-  }
-
-  @Test
-  public void acknowledgeAskForRestart_has_no_effect_when_no_restart_asked() throws Exception {
-    File dir = temp.newFolder();
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      assertThat(commands.askedForRestart()).isFalse();
-
-      commands.acknowledgeAskForRestart();
-      assertThat(commands.askedForRestart()).isFalse();
-    }
-  }
-
-  @Test
-  public void acknowledgeAskForRestart_resets_askForRestart_has_no_effect_when_no_restart_asked() throws Exception {
-    File dir = temp.newFolder();
-
-    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
-      commands.askForRestart();
-      assertThat(commands.askedForRestart()).isTrue();
-
-      commands.acknowledgeAskForRestart();
-      assertThat(commands.askedForRestart()).isFalse();
-    }
-  }
-
-  @Test
-  public void main_fails_if_processNumber_is_less_than_0() throws Exception {
-    int processNumber = -2;
-
-    expectProcessNumberNoValidIAE(processNumber);
-
-
-    try (DefaultProcessCommands main = DefaultProcessCommands.main(temp.newFolder(), processNumber)) {
-
-    }
-  }
-
-  @Test
-  public void main_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
-    int processNumber = MAX_PROCESSES + 1;
-
-    expectProcessNumberNoValidIAE(processNumber);
-
-    try (DefaultProcessCommands main = DefaultProcessCommands.main(temp.newFolder(), processNumber)) {
-
-    }
-  }
-
-  @Test
-  public void main_fails_if_processNumber_is_MAX_PROCESSES() throws Exception {
-    int processNumber = MAX_PROCESSES;
-
-    expectProcessNumberNoValidIAE(processNumber);
-
-    try (DefaultProcessCommands main = DefaultProcessCommands.main(temp.newFolder(), processNumber)) {
-
-    }
-  }
-
-  @Test
-  public void secondary_fails_if_processNumber_is_less_than_0() throws Exception {
-    int processNumber = -2;
-
-    expectProcessNumberNoValidIAE(processNumber);
-
-    DefaultProcessCommands.secondary(temp.newFolder(), processNumber);
-  }
-
-  @Test
-  public void secondary_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
-    int processNumber = MAX_PROCESSES + 1;
-
-    expectProcessNumberNoValidIAE(processNumber);
-
-    try (DefaultProcessCommands secondary = DefaultProcessCommands.secondary(temp.newFolder(), processNumber)) {
-
-    }
-  }
-
-  @Test
-  public void reset_fails_if_processNumber_is_less_than_0() throws Exception {
-    int processNumber = -2;
-
-    expectProcessNumberNoValidIAE(processNumber);
-
-    DefaultProcessCommands.reset(temp.newFolder(), processNumber);
-  }
-
-  @Test
-  public void reset_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
-    int processNumber = MAX_PROCESSES + 1;
-
-    expectProcessNumberNoValidIAE(processNumber);
-
-    DefaultProcessCommands.reset(temp.newFolder(), processNumber);
-  }
-
-  private void expectProcessNumberNoValidIAE(int processNumber) {
-    expectedException.expect(IllegalArgumentException.class);
-    expectedException.expectMessage("Process number " + processNumber + " is not valid");
-  }
-}
index 68996625c72248ed42491515ada596e76f8ed7e6..3b7a4659d3148215b5f3e2339a130e3a3899cca7 100644 (file)
@@ -28,6 +28,7 @@ import org.junit.rules.TemporaryFolder;
 import org.junit.rules.TestRule;
 import org.junit.rules.Timeout;
 import org.sonar.process.Lifecycle.State;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 import org.sonar.process.test.StandardProcess;
 
 import java.io.File;
index 9938ae1050cb31ec693cefa1cbfbcae326272d79..fa5704f37e92e86787049134636a31b56461fa8f 100644 (file)
@@ -24,6 +24,7 @@ import org.junit.Test;
 import org.junit.rules.DisableOnDebug;
 import org.junit.rules.TestRule;
 import org.junit.rules.Timeout;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
index cfd00bf01c8c8da7d728c0f080bfe38dd27f83a8..e62c479e1bf8b8334594f5673fec24fb6a052c4b 100644 (file)
@@ -22,6 +22,7 @@ package org.sonar.process;
 import org.junit.Test;
 import org.mockito.invocation.InvocationOnMock;
 import org.mockito.stubbing.Answer;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
 import static org.mockito.Mockito.*;
 
diff --git a/server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/AllProcessesCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/AllProcessesCommandsTest.java
new file mode 100644 (file)
index 0000000..2bad735
--- /dev/null
@@ -0,0 +1,218 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.sharedmemoryfile;
+
+import java.io.File;
+import java.io.IOException;
+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.sharedmemoryfile.ProcessCommands.MAX_PROCESSES;
+
+public class AllProcessesCommandsTest {
+
+  private static final int PROCESS_NUMBER = 1;
+  private static final byte STOP = (byte) 0xFF;
+  private static final byte RESTART = (byte) 0xAA;
+  private static final byte UP = (byte) 0x01;
+  private static final byte OPERATIONAL = (byte) 0x59;
+  private static final byte EMPTY = (byte) 0x00;
+
+  @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 write_and_read_up() throws IOException {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int offset = 0;
+
+      assertThat(commands.isUp(PROCESS_NUMBER)).isFalse();
+      assertThat(readByte(commands, offset)).isEqualTo(EMPTY);
+
+      commands.setUp(PROCESS_NUMBER);
+      assertThat(commands.isUp(PROCESS_NUMBER)).isTrue();
+      assertThat(readByte(commands, offset)).isEqualTo(UP);
+    }
+  }
+
+  @Test
+  public void write_and_read_operational() throws IOException {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int offset = 3;
+
+      assertThat(commands.isOperational(PROCESS_NUMBER)).isFalse();
+      assertThat(readByte(commands, offset)).isEqualTo(EMPTY);
+
+      commands.setOperational(PROCESS_NUMBER);
+      assertThat(commands.isOperational(PROCESS_NUMBER)).isTrue();
+      assertThat(readByte(commands, offset)).isEqualTo(OPERATIONAL);
+    }
+  }
+
+  @Test
+  public void write_and_read_ping() throws IOException {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+
+      int offset = 4;
+      assertThat(readLong(commands, offset)).isEqualTo(0L);
+
+      long currentTime = System.currentTimeMillis();
+      commands.ping(PROCESS_NUMBER);
+      assertThat(readLong(commands, offset)).isGreaterThanOrEqualTo(currentTime);
+    }
+  }
+
+  @Test
+  public void write_and_read_jmx_url() throws IOException {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+
+      int offset = 12;
+      for (int i = 0; i < 500; i++) {
+        assertThat(readByte(commands, offset + i)).isEqualTo(EMPTY);
+      }
+
+      commands.setSystemInfoUrl(PROCESS_NUMBER, "jmx:foo");
+      assertThat(readByte(commands, offset)).isNotEqualTo(EMPTY);
+      assertThat(commands.getSystemInfoUrl(PROCESS_NUMBER)).isEqualTo("jmx:foo");
+    }
+  }
+
+  @Test
+  public void ask_for_stop() throws Exception {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int offset = 1;
+
+      assertThat(readByte(commands, offset)).isNotEqualTo(STOP);
+      assertThat(commands.askedForStop(PROCESS_NUMBER)).isFalse();
+
+      commands.askForStop(PROCESS_NUMBER);
+      assertThat(commands.askedForStop(PROCESS_NUMBER)).isTrue();
+      assertThat(readByte(commands, offset)).isEqualTo(STOP);
+    }
+  }
+
+  @Test
+  public void ask_for_restart() throws Exception {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int offset = 2;
+
+      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
+      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+
+      commands.askForRestart(PROCESS_NUMBER);
+      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
+      assertThat(readByte(commands, offset)).isEqualTo(RESTART);
+    }
+  }
+
+  @Test
+  public void acknowledgeAskForRestart_has_no_effect_when_no_restart_asked() throws Exception {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int offset = 2;
+
+      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
+      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+
+      commands.acknowledgeAskForRestart(PROCESS_NUMBER);
+      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
+      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+    }
+  }
+
+  @Test
+  public void acknowledgeAskForRestart_resets_askForRestart_has_no_effect_when_no_restart_asked() throws Exception {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int offset = 2;
+
+      commands.askForRestart(PROCESS_NUMBER);
+      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isTrue();
+      assertThat(readByte(commands, offset)).isEqualTo(RESTART);
+
+      commands.acknowledgeAskForRestart(PROCESS_NUMBER);
+      assertThat(readByte(commands, offset)).isNotEqualTo(RESTART);
+      assertThat(commands.askedForRestart(PROCESS_NUMBER)).isFalse();
+    }
+  }
+
+  @Test
+  public void getProcessCommands_fails_if_processNumber_is_less_than_0() throws Exception {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int processNumber = -2;
+
+      expectedException.expect(IllegalArgumentException.class);
+      expectedException.expectMessage("Process number " + processNumber + " is not valid");
+
+      commands.createAfterClean(processNumber);
+    }
+  }
+
+  @Test
+  public void getProcessCommands_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      int processNumber = MAX_PROCESSES + 1;
+
+      expectedException.expect(IllegalArgumentException.class);
+      expectedException.expectMessage("Process number " + processNumber + " is not valid");
+
+      commands.createAfterClean(processNumber);
+    }
+  }
+
+  @Test
+  public void clean_cleans_sharedMemory_of_any_process_less_than_MAX_PROCESSES() throws IOException {
+    try (AllProcessesCommands commands = new AllProcessesCommands(temp.newFolder())) {
+      for (int i = 0; i < MAX_PROCESSES; i++) {
+        commands.create(i).setUp();
+      }
+      commands.clean();
+      for (int i = 0; i < MAX_PROCESSES; i++) {
+        assertThat(commands.create(i).isUp()).isFalse();
+      }
+    }
+  }
+
+  private byte readByte(AllProcessesCommands commands, int offset) {
+    return commands.mappedByteBuffer.get(commands.offset(PROCESS_NUMBER) + offset);
+  }
+
+  private long readLong(AllProcessesCommands commands, int offset) {
+    return commands.mappedByteBuffer.getLong(offset + commands.offset(PROCESS_NUMBER));
+  }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommandsTest.java
new file mode 100644 (file)
index 0000000..76d450e
--- /dev/null
@@ -0,0 +1,245 @@
+/*
+ * SonarQube
+ * Copyright (C) 2009-2017 SonarSource SA
+ * mailto:info 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.sharedmemoryfile;
+
+import java.io.File;
+import java.io.IOException;
+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.sharedmemoryfile.ProcessCommands.MAX_PROCESSES;
+
+public class DefaultProcessCommandsTest {
+
+  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 {
+      DefaultProcessCommands.main(dir, PROCESS_NUMBER);
+      fail();
+    } catch (IllegalArgumentException e) {
+      assertThat(e).hasMessage("Not a valid directory: " + dir.getAbsolutePath());
+    }
+  }
+
+  @Test
+  public void main_clears_the_memory_space_of_the_specified_process_number() throws IOException {
+    File dir = temp.newFolder();
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      commands.setUp();
+      commands.setHttpUrl("bla");
+      commands.setOperational();
+    }
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      assertThat(commands.isUp()).isFalse();
+      assertThat(commands.getHttpUrl()).isEmpty();
+      assertThat(commands.isOperational()).isFalse();
+    }
+  }
+
+  @Test
+  public void secondary_does_not_clear_the_memory_space_of_the_specified_process_number() throws IOException {
+    File dir = temp.newFolder();
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      commands.setUp();
+      commands.setHttpUrl("bla");
+      commands.setOperational();
+    }
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.secondary(dir, PROCESS_NUMBER)) {
+      assertThat(commands.isUp()).isTrue();
+      assertThat(commands.getHttpUrl()).isEqualTo("bla");
+      assertThat(commands.isOperational()).isTrue();
+    }
+  }
+
+  @Test
+  public void child_process_update_the_mapped_memory() throws Exception {
+    File dir = temp.newFolder();
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      assertThat(commands.isUp()).isFalse();
+      commands.setUp();
+      assertThat(commands.isUp()).isTrue();
+    }
+  }
+
+  @Test
+  public void reset_clears_only_the_memory_space_of_specified_process_number() throws IOException {
+    File dir = temp.newFolder();
+
+    try (AllProcessesCommands commands = new AllProcessesCommands(dir)) {
+      for (int i = 0; i < MAX_PROCESSES; i++) {
+        commands.setOperational(i);
+        commands.setUp(i);
+      }
+
+      int resetProcess = 3;
+      DefaultProcessCommands.reset(dir, resetProcess);
+      for (int i = 0; i < MAX_PROCESSES; i++) {
+        assertThat(commands.isOperational(i)).isEqualTo(i != resetProcess);
+        assertThat(commands.isUp(i)).isEqualTo(i != resetProcess);
+      }
+    }
+  }
+
+  @Test
+  public void ask_for_stop() throws Exception {
+    File dir = temp.newFolder();
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      assertThat(commands.askedForStop()).isFalse();
+
+      commands.askForStop();
+      assertThat(commands.askedForStop()).isTrue();
+    }
+  }
+
+  @Test
+  public void ask_for_restart() throws Exception {
+    File dir = temp.newFolder();
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      assertThat(commands.askedForRestart()).isFalse();
+
+      commands.askForRestart();
+      assertThat(commands.askedForRestart()).isTrue();
+    }
+  }
+
+  @Test
+  public void acknowledgeAskForRestart_has_no_effect_when_no_restart_asked() throws Exception {
+    File dir = temp.newFolder();
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      assertThat(commands.askedForRestart()).isFalse();
+
+      commands.acknowledgeAskForRestart();
+      assertThat(commands.askedForRestart()).isFalse();
+    }
+  }
+
+  @Test
+  public void acknowledgeAskForRestart_resets_askForRestart_has_no_effect_when_no_restart_asked() throws Exception {
+    File dir = temp.newFolder();
+
+    try (DefaultProcessCommands commands = DefaultProcessCommands.main(dir, PROCESS_NUMBER)) {
+      commands.askForRestart();
+      assertThat(commands.askedForRestart()).isTrue();
+
+      commands.acknowledgeAskForRestart();
+      assertThat(commands.askedForRestart()).isFalse();
+    }
+  }
+
+  @Test
+  public void main_fails_if_processNumber_is_less_than_0() throws Exception {
+    int processNumber = -2;
+
+    expectProcessNumberNoValidIAE(processNumber);
+
+
+    try (DefaultProcessCommands main = DefaultProcessCommands.main(temp.newFolder(), processNumber)) {
+
+    }
+  }
+
+  @Test
+  public void main_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
+    int processNumber = MAX_PROCESSES + 1;
+
+    expectProcessNumberNoValidIAE(processNumber);
+
+    try (DefaultProcessCommands main = DefaultProcessCommands.main(temp.newFolder(), processNumber)) {
+
+    }
+  }
+
+  @Test
+  public void main_fails_if_processNumber_is_MAX_PROCESSES() throws Exception {
+    int processNumber = MAX_PROCESSES;
+
+    expectProcessNumberNoValidIAE(processNumber);
+
+    try (DefaultProcessCommands main = DefaultProcessCommands.main(temp.newFolder(), processNumber)) {
+
+    }
+  }
+
+  @Test
+  public void secondary_fails_if_processNumber_is_less_than_0() throws Exception {
+    int processNumber = -2;
+
+    expectProcessNumberNoValidIAE(processNumber);
+
+    DefaultProcessCommands.secondary(temp.newFolder(), processNumber);
+  }
+
+  @Test
+  public void secondary_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
+    int processNumber = MAX_PROCESSES + 1;
+
+    expectProcessNumberNoValidIAE(processNumber);
+
+    try (DefaultProcessCommands secondary = DefaultProcessCommands.secondary(temp.newFolder(), processNumber)) {
+
+    }
+  }
+
+  @Test
+  public void reset_fails_if_processNumber_is_less_than_0() throws Exception {
+    int processNumber = -2;
+
+    expectProcessNumberNoValidIAE(processNumber);
+
+    DefaultProcessCommands.reset(temp.newFolder(), processNumber);
+  }
+
+  @Test
+  public void reset_fails_if_processNumber_is_higher_than_MAX_PROCESSES() throws Exception {
+    int processNumber = MAX_PROCESSES + 1;
+
+    expectProcessNumberNoValidIAE(processNumber);
+
+    DefaultProcessCommands.reset(temp.newFolder(), processNumber);
+  }
+
+  private void expectProcessNumberNoValidIAE(int processNumber) {
+    expectedException.expect(IllegalArgumentException.class);
+    expectedException.expectMessage("Process number " + processNumber + " is not valid");
+  }
+}
index 93110668414deea7f4c4bf50223890d6c756280f..8f7ab2830490542a333e969fc211a097cc3cdd2f 100644 (file)
@@ -25,7 +25,7 @@ import org.eclipse.jetty.server.Server;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 import org.eclipse.jetty.server.handler.ContextHandler;
 import org.sonar.process.Monitored;
-import org.sonar.process.ProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 import org.sonar.process.ProcessEntryPoint;
 
 import javax.servlet.ServletException;
index 09f0f8abeaa3b2795855c5978d7895c2a68df0fd..e119aa7948bcd992d2cbb7dfbe1ae8d5969f29af 100644 (file)
@@ -28,7 +28,7 @@ import okhttp3.RequestBody;
 import org.apache.commons.io.IOUtils;
 import org.sonar.api.config.Configuration;
 import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.process.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
 
 import static java.util.Objects.requireNonNull;
index 54e998e9576a2023f07197eb6c2e0dad64110474..65732bd45c7aa4fd667601479e6c3bc36f07d5b9 100644 (file)
@@ -21,8 +21,8 @@ package org.sonar.server.app;
 
 import java.io.File;
 import org.sonar.api.config.Configuration;
-import org.sonar.process.DefaultProcessCommands;
-import org.sonar.process.ProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.ProcessCommands;
 
 import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
 import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
index 3b6b5d1629b7003d075b1d2d7c7ca872c1d85d50..31016f4a0b2b52bd1b782012ae5e150b44afdfb4 100644 (file)
@@ -21,7 +21,7 @@ package org.sonar.server.app;
 
 import com.google.common.collect.ImmutableMap;
 import java.io.File;
-import org.sonar.process.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
 import org.sonar.process.MinimumViableSystem;
 import org.sonar.process.Monitored;
 import org.sonar.process.ProcessEntryPoint;
index 2e121fc8dddc93cdd2b51fc18716c52ef7918667..2234c209618c4f9716c4ec4051d3b7b134eb4fe5 100644 (file)
@@ -32,7 +32,7 @@ import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.config.internal.MapSettings;
 import org.sonar.api.utils.log.LoggerLevel;
-import org.sonar.process.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
 import org.sonar.process.ProcessEntryPoint;
 import org.sonar.process.ProcessId;
 import org.sonar.process.systeminfo.protobuf.ProtobufSystemInfo;
index b34daac76eb325b4ffcd104555566b86aaf6b6d4..f328f10de44873de9dafa58d7d4b31273528a621 100644 (file)
@@ -26,7 +26,7 @@ import org.junit.Test;
 import org.junit.rules.ExpectedException;
 import org.junit.rules.TemporaryFolder;
 import org.sonar.api.config.internal.MapSettings;
-import org.sonar.process.DefaultProcessCommands;
+import org.sonar.process.sharedmemoryfile.DefaultProcessCommands;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;