From: Sébastien Lesaint
Date: Fri, 18 Aug 2017 07:51:33 +0000 (+0200)
Subject: SONAR-9590 move shared memory file classes to dedicated package
X-Git-Tag: 6.6-RC1~474
X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=dd056cf31ff9cbd6b230cf824227fbb26ee29bc8;p=sonarqube.git
SONAR-9590 move shared memory file classes to dedicated package
---
diff --git a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
index 3a8f28fb4a4..bf92cfb6ec0 100644
--- a/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
+++ b/server/sonar-ce/src/main/java/org/sonar/ce/httpd/CeHttpServer.java
@@ -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;
diff --git a/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
index cb1d68eadd5..8cc7a8c38ec 100644
--- a/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
+++ b/server/sonar-ce/src/test/java/org/sonar/ce/httpd/CeHttpServerTest.java
@@ -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;
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java
index 99d22bd26f3..f397d608d55 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/AppFileSystem.java
@@ -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;
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java
index 77ebdeeb521..672debcadc0 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessCommandsProcessMonitor.java
@@ -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;
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
index 19b8eb6504f..4bb42a72f45 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/ProcessLauncherImpl.java
@@ -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" +
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java
index 6c92a14a2e2..fc042f614c3 100644
--- a/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java
+++ b/server/sonar-process-monitor/src/main/java/org/sonar/application/process/StopRequestWatcherImpl.java
@@ -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;
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java
index 16c11d89946..3786190fb37 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/AppFileSystemTest.java
@@ -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 {
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java
index c7a779bcb61..66c60fc73ee 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessCommandsProcessMonitorTest.java
@@ -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;
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
index c7d55a92b57..d45927b589f 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/ProcessLauncherImplTest.java
@@ -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;
diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java
index 2ac305797e4..6d0b9725453 100644
--- a/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java
+++ b/server/sonar-process-monitor/src/test/java/org/sonar/application/process/StopRequestWatcherImplTest.java
@@ -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
index ea4b6ae2b3b..00000000000
--- a/server/sonar-process/src/main/java/org/sonar/process/AllProcessesCommands.java
+++ /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 :
- *
- * - share status of child process
- * - stop/restart child process
- *
- *
- *
- * It relies on a single file accessed by all processes through a {@link MappedByteBuffer}.
- * Following alternatives were considered but not selected :
- *
- * - JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port
- * - simple socket protocol: same drawbacks are RMI connection
- * - java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)
- * - execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?
- *
- *
- *
- *
- * The file contains {@link ProcessCommands#MAX_PROCESSES} groups of {@link #BYTE_LENGTH_FOR_ONE_PROCESS} bits.
- * Each group of byte is used as follow:
- *
- * - First byte contains {@link #EMPTY} until process is UP and writes {@link #UP}
- * - Second byte contains {@link #EMPTY} until any process requests current one to stop by writing value {@link #STOP}
- * - 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}
- * - 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.
- * - The next 8 bytes contains a long (value of {@link System#currentTimeMillis()}) which represents the date of the last ping
- *
- *
- */
-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
index 8c29a972f70..00000000000
--- a/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java
+++ /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
index 6ec0bf16736..00000000000
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java
+++ /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 :
- *
- * - share status of specific process
- * - stop/restart a specific processes
- *
- *
- * @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/ProcessEntryPoint.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
index 15190a41dcc..b1476fba53e 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java
@@ -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 {
diff --git a/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java b/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java
index 0043bccf239..1739dc0423f 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/StopWatcher.java
@@ -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
diff --git a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java
index f537d204bcb..746cd31dc1e 100644
--- a/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java
+++ b/server/sonar-process/src/main/java/org/sonar/process/StopperThread.java
@@ -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
index 00000000000..c5d7ec46f5e
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/AllProcessesCommands.java
@@ -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 :
+ *
+ * - share status of child process
+ * - stop/restart child process
+ *
+ *
+ *
+ * It relies on a single file accessed by all processes through a {@link MappedByteBuffer}.
+ * Following alternatives were considered but not selected :
+ *
+ * - JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port
+ * - simple socket protocol: same drawbacks are RMI connection
+ * - java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)
+ * - execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?
+ *
+ *
+ *
+ *
+ * The file contains {@link ProcessCommands#MAX_PROCESSES} groups of {@link #BYTE_LENGTH_FOR_ONE_PROCESS} bits.
+ * Each group of byte is used as follow:
+ *
+ * - First byte contains {@link #EMPTY} until process is UP and writes {@link #UP}
+ * - Second byte contains {@link #EMPTY} until any process requests current one to stop by writing value {@link #STOP}
+ * - 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}
+ * - 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.
+ * - The next 8 bytes contains a long (value of {@link System#currentTimeMillis()}) which represents the date of the last ping
+ *
+ *
+ */
+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
index 00000000000..882a3eae37f
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommands.java
@@ -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
index 00000000000..6d4bda331ab
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/ProcessCommands.java
@@ -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 :
+ *
+ * - share status of specific process
+ * - stop/restart a specific processes
+ *
+ *
+ * @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
index 00000000000..126be7e9c58
--- /dev/null
+++ b/server/sonar-process/src/main/java/org/sonar/process/sharedmemoryfile/package-info.java
@@ -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
index 4667f055fbc..00000000000
--- a/server/sonar-process/src/test/java/org/sonar/process/AllProcessesCommandsTest.java
+++ /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
index 3c311c208de..00000000000
--- a/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java
+++ /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");
- }
-}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
index 68996625c72..3b7a4659d31 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/ProcessEntryPointTest.java
@@ -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;
diff --git a/server/sonar-process/src/test/java/org/sonar/process/StopWatcherTest.java b/server/sonar-process/src/test/java/org/sonar/process/StopWatcherTest.java
index 9938ae1050c..fa5704f37e9 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/StopWatcherTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/StopWatcherTest.java
@@ -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;
diff --git a/server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java b/server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java
index cfd00bf01c8..e62c479e1bf 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/StopperThreadTest.java
@@ -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
index 00000000000..2bad735aaa6
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/AllProcessesCommandsTest.java
@@ -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
index 00000000000..76d450e3bc8
--- /dev/null
+++ b/server/sonar-process/src/test/java/org/sonar/process/sharedmemoryfile/DefaultProcessCommandsTest.java
@@ -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");
+ }
+}
diff --git a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
index 93110668414..8f7ab283049 100644
--- a/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
+++ b/server/sonar-process/src/test/java/org/sonar/process/test/HttpProcess.java
@@ -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;
diff --git a/server/sonar-server/src/main/java/org/sonar/ce/http/CeHttpClientImpl.java b/server/sonar-server/src/main/java/org/sonar/ce/http/CeHttpClientImpl.java
index 09f0f8abeaa..e119aa7948b 100644
--- a/server/sonar-server/src/main/java/org/sonar/ce/http/CeHttpClientImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/ce/http/CeHttpClientImpl.java
@@ -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;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java b/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java
index 54e998e9576..65732bd45c7 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/ProcessCommandWrapperImpl.java
@@ -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;
diff --git a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
index 3b6b5d1629b..31016f4a0b2 100644
--- a/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
+++ b/server/sonar-server/src/main/java/org/sonar/server/app/WebServer.java
@@ -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;
diff --git a/server/sonar-server/src/test/java/org/sonar/ce/http/CeHttpClientTest.java b/server/sonar-server/src/test/java/org/sonar/ce/http/CeHttpClientTest.java
index 2e121fc8ddd..2234c209618 100644
--- a/server/sonar-server/src/test/java/org/sonar/ce/http/CeHttpClientTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/ce/http/CeHttpClientTest.java
@@ -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;
diff --git a/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java b/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java
index b34daac76eb..f328f10de44 100644
--- a/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java
+++ b/server/sonar-server/src/test/java/org/sonar/server/app/ProcessCommandWrapperImplTest.java
@@ -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;