diff options
author | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-05-29 11:25:01 +0200 |
---|---|---|
committer | Simon Brandhof <simon.brandhof@sonarsource.com> | 2015-05-29 11:25:01 +0200 |
commit | 0cc7aa3e7a4f3e8a3838bbbfb970aeb4775c4e9d (patch) | |
tree | 16331cc2009b399fb7632fcb3d2661b8ebe10989 | |
parent | b0f14fa5862298c7296eb9cd61f6ca3de71b3d34 (diff) | |
download | sonarqube-0cc7aa3e7a4f3e8a3838bbbfb970aeb4775c4e9d.tar.gz sonarqube-0cc7aa3e7a4f3e8a3838bbbfb970aeb4775c4e9d.zip |
Extract an interface from org.sonar.process.ProcessCommands
Try to stabilize StopWatcherTest when machine is slow or under pressure. More than 1 second could
be spent in the creation of ProcessCommands mock.
-rw-r--r-- | server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java | 3 | ||||
-rw-r--r-- | server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java | 9 | ||||
-rw-r--r-- | server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java | 16 | ||||
-rw-r--r-- | server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java | 160 | ||||
-rw-r--r-- | server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java | 116 | ||||
-rw-r--r-- | server/sonar-process/src/main/java/org/sonar/process/ProcessEntryPoint.java | 2 | ||||
-rw-r--r-- | server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java (renamed from server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java) | 23 | ||||
-rw-r--r-- | server/sonar-process/src/test/java/org/sonar/process/StopWatcherTest.java | 17 | ||||
-rw-r--r-- | sonar-application/src/main/java/org/sonar/application/App.java | 3 |
9 files changed, 210 insertions, 139 deletions
diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java index 1e3ee06648e..390746bca59 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/JavaProcessLauncher.java @@ -21,6 +21,7 @@ package org.sonar.process.monitor; import org.apache.commons.lang.StringUtils; import org.slf4j.LoggerFactory; +import org.sonar.process.DefaultProcessCommands; import org.sonar.process.ProcessCommands; import org.sonar.process.ProcessEntryPoint; import org.sonar.process.ProcessUtils; @@ -45,7 +46,7 @@ public class JavaProcessLauncher { Process process = null; try { // cleanup existing monitor files - ProcessCommands commands = new ProcessCommands(command.getTempDir(), command.getProcessIndex()); + ProcessCommands commands = new DefaultProcessCommands(command.getTempDir(), command.getProcessIndex()); ProcessBuilder processBuilder = create(command); LoggerFactory.getLogger(getClass()).info("Launch process[{}]: {}", diff --git a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java index df3d53518ee..dd0ca58ba0a 100644 --- a/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java +++ b/server/sonar-process-monitor/src/main/java/org/sonar/process/monitor/Monitor.java @@ -19,15 +19,14 @@ */ package org.sonar.process.monitor; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; import org.slf4j.LoggerFactory; import org.sonar.process.Lifecycle; import org.sonar.process.Lifecycle.State; import org.sonar.process.ProcessCommands; import org.sonar.process.SystemExit; -import java.util.List; -import java.util.concurrent.CopyOnWriteArrayList; - public class Monitor { private final List<ProcessRef> processes = new CopyOnWriteArrayList<>(); @@ -165,8 +164,8 @@ public class Monitor { } public static int getNextProcessId() { - if (nextProcessId >= ProcessCommands.getMaxProcesses()) { - throw new IllegalStateException("The maximum number of processes launched has been reached " + ProcessCommands.getMaxProcesses()); + if (nextProcessId >= ProcessCommands.MAX_PROCESSES) { + throw new IllegalStateException("The maximum number of processes launched has been reached " + ProcessCommands.MAX_PROCESSES); } return nextProcessId++; } diff --git a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java index 57e3478162c..00ae573ed6a 100644 --- a/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java +++ b/server/sonar-process-monitor/src/test/java/org/sonar/process/monitor/MonitorTest.java @@ -20,6 +20,11 @@ package org.sonar.process.monitor; import com.github.kevinsawicki.http.HttpRequest; +import java.io.File; +import java.io.IOException; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import org.apache.commons.io.FileUtils; import org.junit.After; import org.junit.BeforeClass; @@ -27,17 +32,11 @@ import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; import org.junit.rules.Timeout; -import org.sonar.process.NetworkUtils; import org.sonar.process.Lifecycle.State; +import org.sonar.process.NetworkUtils; import org.sonar.process.ProcessCommands; import org.sonar.process.SystemExit; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; - import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.fail; import static org.mockito.Mockito.mock; @@ -211,7 +210,8 @@ public class MonitorTest { @Test public void test_too_many_processes() { - while (Monitor.getNextProcessId() < ProcessCommands.getMaxProcesses() - 1) {} + while (Monitor.getNextProcessId() < ProcessCommands.MAX_PROCESSES - 1) { + } try { newDefaultMonitor(); } catch (IllegalStateException e) { 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 new file mode 100644 index 00000000000..f2992523f35 --- /dev/null +++ b/server/sonar-process/src/main/java/org/sonar/process/DefaultProcessCommands.java @@ -0,0 +1,160 @@ +/* + * SonarQube, open source software quality management tool. + * Copyright (C) 2008-2014 SonarSource + * mailto:contact AT sonarsource DOT com + * + * SonarQube is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 3 of the License, or (at your option) any later version. + * + * SonarQube is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + */ +package org.sonar.process; + +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.MappedByteBuffer; +import java.nio.channels.FileChannel; +import org.apache.commons.io.IOUtils; +import org.slf4j.LoggerFactory; + +/** + * Process inter-communication to : + * <ul> + * <li>share status of child process</li> + * <li>stop child process</li> + * </ul> + * + * <p/> + * It relies on files shared by both processes. Following alternatives were considered but not selected : + * <ul> + * <li>JMX beans over RMI: network issues (mostly because of Java reverse-DNS) + requires to configure and open a new port</li> + * <li>simple socket protocol: same drawbacks are RMI connection</li> + * <li>java.lang.Process#destroy(): shutdown hooks are not executed on some OS (mostly MSWindows)</li> + * <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li> + * </ul> + */ +public class DefaultProcessCommands implements ProcessCommands { + + /** + * The ByteBuffer will contains : + * <ul> + * <li>First byte will contains 0x00 until stop command is issued = 0xFF</li> + * <li>Then each 10 bytes will be reserved for each process</li> + * </ul> + * + * Description of ten bytes of each process : + * <ul> + * <li>First byte will contains the state 0x00 until READY 0x01</li> + * <li>The second byte will contains the request for stopping 0x00 or STOP (0xFF)</li> + * <li>The next 8 bytes contains a long (System.currentTimeInMillis for ping)</li> + * </ul> + */ + final MappedByteBuffer mappedByteBuffer; + private final RandomAccessFile sharedMemory; + private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 8; + + // With this shared memory we can handle up to MAX_PROCESSES processes + private static final int MAX_SHARED_MEMORY = BYTE_LENGTH_FOR_ONE_PROCESS * MAX_PROCESSES; + + public static final byte STOP = (byte) 0xFF; + public static final byte READY = (byte) 0x01; + public static final byte EMPTY = (byte) 0x00; + + private int processNumber; + + public DefaultProcessCommands(File directory, int processNumber) { + // processNumber should not excess MAX_PROCESSES and must not be below -1 + assert processNumber <= MAX_PROCESSES : "Incorrect process number"; + assert processNumber >= -1 : "Incorrect process number"; + + this.processNumber = processNumber; + if (!directory.isDirectory() || !directory.exists()) { + throw new IllegalArgumentException("Not a valid directory: " + directory); + } + + try { + sharedMemory = new RandomAccessFile(new File(directory, "sharedmemory"), "rw"); + mappedByteBuffer = sharedMemory.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, MAX_SHARED_MEMORY); + cleanData(); + } catch (IOException e) { + throw new IllegalArgumentException("Unable to create shared memory : ", e); + } + } + + @Override + public boolean isReady() { + return canBeMonitored() && mappedByteBuffer.get(offset()) == READY; + } + + /** + * To be executed by child process to declare that it's ready + */ + @Override + public void setReady() { + if (canBeMonitored()) { + mappedByteBuffer.put(offset(), READY); + } + } + + @Override + public void ping() { + if (canBeMonitored()) { + mappedByteBuffer.putLong(2 + offset(), System.currentTimeMillis()); + } + } + + @Override + public long getLastPing() { + if (canBeMonitored()) { + return mappedByteBuffer.getLong(2 + offset()); + } else { + return -1; + } + } + + /** + * To be executed by monitor process to ask for child process termination + */ + @Override + public void askForStop() { + mappedByteBuffer.put(offset() + 1, STOP); + } + + @Override + public boolean askedForStop() { + return mappedByteBuffer.get(offset() + 1) == STOP; + } + + @Override + public void endWatch() { + IOUtils.closeQuietly(sharedMemory); + } + + int offset() { + return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber; + } + + private boolean canBeMonitored() { + boolean result = processNumber >= 0 && processNumber < MAX_PROCESSES; + if (!result) { + LoggerFactory.getLogger(getClass()).info("This process cannot be monitored. Process Id : [{}]", processNumber); + } + return result; + } + + private void cleanData() { + for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) { + mappedByteBuffer.put(offset() + i, EMPTY); + } + } +} diff --git a/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java b/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java index d697f542f32..8c027d45c7c 100644 --- a/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java +++ b/server/sonar-process/src/main/java/org/sonar/process/ProcessCommands.java @@ -19,15 +19,6 @@ */ package org.sonar.process; -import org.apache.commons.io.IOUtils; -import org.slf4j.LoggerFactory; - -import java.io.File; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.MappedByteBuffer; -import java.nio.channels.FileChannel; - /** * Process inter-communication to : * <ul> @@ -44,116 +35,27 @@ import java.nio.channels.FileChannel; * <li>execute OS-specific commands (for instance kill on *nix): OS-specific, so hell to support. Moreover how to get identify a process ?</li> * </ul> */ -public class ProcessCommands { - - /** - * The ByteBuffer will contains : - * <ul> - * <li>First byte will contains 0x00 until stop command is issued = 0xFF</li> - * <li>Then each 10 bytes will be reserved for each process</li> - * </ul> - * - * Description of ten bytes of each process : - * <ul> - * <li>First byte will contains the state 0x00 until READY 0x01</li> - * <li>The second byte will contains the request for stopping 0x00 or STOP (0xFF)</li> - * <li>The next 8 bytes contains a long (System.currentTimeInMillis for ping)</li> - * </ul> - */ - final MappedByteBuffer mappedByteBuffer; - private final RandomAccessFile sharedMemory; - private static final int MAX_PROCESSES = 50; - private static final int BYTE_LENGTH_FOR_ONE_PROCESS = 1 + 1 + 8; - - // With this shared memory we can handle up to MAX_PROCESSES processes - private static final int MAX_SHARED_MEMORY = BYTE_LENGTH_FOR_ONE_PROCESS * MAX_PROCESSES; - - public static final byte STOP = (byte) 0xFF; - public static final byte READY = (byte) 0x01; - public static final byte EMPTY = (byte) 0x00; +public interface ProcessCommands { - private int processNumber; + int MAX_PROCESSES = 50; - public ProcessCommands(File directory, int processNumber) { - // processNumber should not excess MAX_PROCESSES and must not be below -1 - assert processNumber <= MAX_PROCESSES : "Incorrect process number"; - assert processNumber >= -1 : "Incorrect process number"; - - this.processNumber = processNumber; - if (!directory.isDirectory() || !directory.exists()) { - throw new IllegalArgumentException("Not a valid directory: " + directory); - } - - try { - sharedMemory = new RandomAccessFile(new File(directory, "sharedmemory"), "rw"); - mappedByteBuffer = sharedMemory.getChannel().map(FileChannel.MapMode.READ_WRITE, 0, MAX_SHARED_MEMORY); - cleanData(); - } catch (IOException e) { - throw new IllegalArgumentException("Unable to create shared memory : ", e); - } - } - - public boolean isReady() { - return canBeMonitored() && mappedByteBuffer.get(offset()) == READY; - } + boolean isReady(); /** * To be executed by child process to declare that it's ready */ - public void setReady() { - if (canBeMonitored()) { - mappedByteBuffer.put(offset(), READY); - } - } + void setReady(); - public void ping() { - if (canBeMonitored()) { - mappedByteBuffer.putLong(2 + offset(), System.currentTimeMillis()); - } - } + void ping(); - public long getLastPing() { - if (canBeMonitored()) { - return mappedByteBuffer.getLong(2 + offset()); - } else { - return -1; - } - } + long getLastPing(); /** * To be executed by monitor process to ask for child process termination */ - public void askForStop() { - mappedByteBuffer.put(offset() + 1, STOP); - } - - public boolean askedForStop() { - return mappedByteBuffer.get(offset() + 1) == STOP; - } - - public void endWatch() { - IOUtils.closeQuietly(sharedMemory); - } - - int offset() { - return BYTE_LENGTH_FOR_ONE_PROCESS * processNumber; - } - - private boolean canBeMonitored() { - boolean result = processNumber >= 0 && processNumber < MAX_PROCESSES; - if (!result) { - LoggerFactory.getLogger(getClass()).info("This process cannot be monitored. Process Id : [{}]", processNumber); - } - return result; - } + void askForStop(); - private void cleanData() { - for (int i = 0; i < BYTE_LENGTH_FOR_ONE_PROCESS; i++) { - mappedByteBuffer.put(offset() + i, EMPTY); - } - } + boolean askedForStop(); - public static int getMaxProcesses() { - return MAX_PROCESSES; - } + 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 49f954a16ac..19f361911cf 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 @@ -134,7 +134,7 @@ public class ProcessEntryPoint implements Stoppable { public static ProcessEntryPoint createForArguments(String[] args) { Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); - ProcessCommands commands = new ProcessCommands( + ProcessCommands commands = new DefaultProcessCommands( props.nonNullValueAsFile(PROPERTY_SHARED_PATH), Integer.parseInt(props.nonNullValue(PROPERTY_PROCESS_INDEX))); return new ProcessEntryPoint(props, new SystemExit(), commands); } diff --git a/server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java b/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java index aba88feb016..16bc7e558c7 100644 --- a/server/sonar-process/src/test/java/org/sonar/process/ProcessCommandsTest.java +++ b/server/sonar-process/src/test/java/org/sonar/process/DefaultProcessCommandsTest.java @@ -19,18 +19,17 @@ */ package org.sonar.process; +import java.io.File; import org.apache.commons.io.FileUtils; import org.junit.Rule; import org.junit.Test; import org.junit.rules.TemporaryFolder; -import java.io.File; - import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.failBecauseExceptionWasNotThrown; import static org.junit.Assert.fail; -public class ProcessCommandsTest { +public class DefaultProcessCommandsTest { @Rule public TemporaryFolder temp = new TemporaryFolder(); @@ -41,7 +40,7 @@ public class ProcessCommandsTest { FileUtils.deleteQuietly(dir); try { - new ProcessCommands(dir, 1); + new DefaultProcessCommands(dir, 1); fail(); } catch (IllegalArgumentException e) { assertThat(e).hasMessage("Not a valid directory: " + dir.getAbsolutePath()); @@ -52,14 +51,14 @@ public class ProcessCommandsTest { public void child_process_update_the_mapped_memory() throws Exception { File dir = temp.newFolder(); - ProcessCommands commands = new ProcessCommands(dir, 1); + DefaultProcessCommands commands = new DefaultProcessCommands(dir, 1); assertThat(commands.isReady()).isFalse(); - assertThat(commands.mappedByteBuffer.get(commands.offset())).isEqualTo(ProcessCommands.EMPTY); + assertThat(commands.mappedByteBuffer.get(commands.offset())).isEqualTo(DefaultProcessCommands.EMPTY); assertThat(commands.mappedByteBuffer.getLong(2 + commands.offset())).isEqualTo(0L); commands.setReady(); assertThat(commands.isReady()).isTrue(); - assertThat(commands.mappedByteBuffer.get(commands.offset())).isEqualTo(ProcessCommands.READY); + assertThat(commands.mappedByteBuffer.get(commands.offset())).isEqualTo(DefaultProcessCommands.READY); long currentTime = System.currentTimeMillis(); commands.ping(); @@ -70,26 +69,26 @@ public class ProcessCommandsTest { public void ask_for_stop() throws Exception { File dir = temp.newFolder(); - ProcessCommands commands = new ProcessCommands(dir, 1); - assertThat(commands.mappedByteBuffer.get(commands.offset() + 1)).isNotEqualTo(ProcessCommands.STOP); + DefaultProcessCommands commands = new DefaultProcessCommands(dir, 1); + assertThat(commands.mappedByteBuffer.get(commands.offset() + 1)).isNotEqualTo(DefaultProcessCommands.STOP); assertThat(commands.askedForStop()).isFalse(); commands.askForStop(); assertThat(commands.askedForStop()).isTrue(); - assertThat(commands.mappedByteBuffer.get(commands.offset() + 1)).isEqualTo(ProcessCommands.STOP); + assertThat(commands.mappedByteBuffer.get(commands.offset() + 1)).isEqualTo(DefaultProcessCommands.STOP); } @Test public void test_max_processes() throws Exception { File dir = temp.newFolder(); try { - new ProcessCommands(dir, -2); + new DefaultProcessCommands(dir, -2); failBecauseExceptionWasNotThrown(AssertionError.class); } catch (AssertionError e) { assertThat(e).hasMessage("Incorrect process number"); } try { - new ProcessCommands(dir, ProcessCommands.getMaxProcesses() + 1); + new DefaultProcessCommands(dir, ProcessCommands.MAX_PROCESSES + 1); failBecauseExceptionWasNotThrown(AssertionError.class); } catch (AssertionError e) { assertThat(e).hasMessage("Incorrect process number"); 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 ac045982ef7..e78d1c17b3e 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 @@ -19,16 +19,25 @@ */ package org.sonar.process; +import java.util.concurrent.TimeUnit; +import org.junit.Rule; import org.junit.Test; +import org.junit.rules.Timeout; -import static org.mockito.Mockito.*; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; public class StopWatcherTest { - @Test(timeout = 1000L) + @Rule + public Timeout timeout = new Timeout(1000, TimeUnit.MILLISECONDS); + + @Test public void stop_if_receive_command() throws InterruptedException { ProcessCommands commands = mock(ProcessCommands.class); - when(commands.askedForStop()).thenReturn(false).thenReturn(true); + when(commands.askedForStop()).thenReturn(false, true); Stoppable stoppable = mock(Stoppable.class); StopWatcher watcher = new StopWatcher(commands, stoppable, 1L); @@ -38,7 +47,7 @@ public class StopWatcherTest { verify(stoppable).stopAsync(); } - @Test(timeout = 1000L) + @Test public void stop_watching_on_interruption() throws InterruptedException { ProcessCommands commands = mock(ProcessCommands.class); when(commands.askedForStop()).thenReturn(false); diff --git a/sonar-application/src/main/java/org/sonar/application/App.java b/sonar-application/src/main/java/org/sonar/application/App.java index 26130401dac..9547d505895 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -21,6 +21,7 @@ package org.sonar.application; import org.apache.commons.io.FilenameUtils; import org.apache.commons.lang.StringUtils; +import org.sonar.process.DefaultProcessCommands; import org.sonar.process.MinimumViableSystem; import org.sonar.process.ProcessCommands; import org.sonar.process.ProcessProperties; @@ -54,7 +55,7 @@ public class App implements Stoppable { public void start(Props props) { if (props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false)) { File tempDir = props.nonNullValueAsFile(ProcessProperties.PATH_TEMP); - ProcessCommands commands = new ProcessCommands(tempDir, 0); + ProcessCommands commands = new DefaultProcessCommands(tempDir, 0); stopWatcher = new StopWatcher(commands, this); stopWatcher.start(); } |