diff options
8 files changed, 385 insertions, 105 deletions
diff --git a/server/sonar-main/build.gradle b/server/sonar-main/build.gradle index 936ace8238d..40494cf097d 100644 --- a/server/sonar-main/build.gradle +++ b/server/sonar-main/build.gradle @@ -25,6 +25,7 @@ dependencies { testCompile 'com.tngtech.java:junit-dataprovider' testCompile 'junit:junit' testCompile 'org.assertj:assertj-core' + testCompile 'org.awaitility:awaitility' testCompile 'org.mockito:mockito-core' testCompile project(':sonar-testing-harness') } diff --git a/server/sonar-main/src/main/java/org/sonar/application/AbstractStopRequestWatcher.java b/server/sonar-main/src/main/java/org/sonar/application/AbstractStopRequestWatcher.java new file mode 100644 index 00000000000..6e0d06fd43d --- /dev/null +++ b/server/sonar-main/src/main/java/org/sonar/application/AbstractStopRequestWatcher.java @@ -0,0 +1,76 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.application; + +import com.google.common.annotations.VisibleForTesting; +import java.util.function.BooleanSupplier; + +public abstract class AbstractStopRequestWatcher extends Thread implements StopRequestWatcher { + private static final long DEFAULT_WATCHER_DELAY_MS = 500L; + private final BooleanSupplier stopRequestedTest; + private final Runnable stopAction; + private final long delayMs; + + protected AbstractStopRequestWatcher(String threadName, BooleanSupplier stopRequestedTest, Runnable stopAction) { + this(threadName, stopRequestedTest, stopAction, DEFAULT_WATCHER_DELAY_MS); + } + + @VisibleForTesting + AbstractStopRequestWatcher(String threadName, BooleanSupplier stopRequestedTest, Runnable stopAction, long delayMs) { + super(threadName); + this.stopRequestedTest = stopRequestedTest; + this.stopAction = stopAction; + this.delayMs = delayMs; + + // safeguard, do not block the JVM if thread is not interrupted + // (method stopWatching() never called). + setDaemon(true); + } + + @Override + public void run() { + try { + while (true) { + if (stopRequestedTest.getAsBoolean()) { + stopAction.run(); + return; + } + Thread.sleep(delayMs); + } + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + // stop watching the commands + } + } + + @VisibleForTesting + long getDelayMs() { + return delayMs; + } + + public void startWatching() { + start(); + } + + public void stopWatching() { + // does nothing if not started + interrupt(); + } +} diff --git a/server/sonar-main/src/main/java/org/sonar/application/HardStopRequestWatcherImpl.java b/server/sonar-main/src/main/java/org/sonar/application/HardStopRequestWatcherImpl.java index 661ab6607c7..d3bb1b7f5e0 100644 --- a/server/sonar-main/src/main/java/org/sonar/application/HardStopRequestWatcherImpl.java +++ b/server/sonar-main/src/main/java/org/sonar/application/HardStopRequestWatcherImpl.java @@ -19,72 +19,25 @@ */ package org.sonar.application; -import org.sonar.application.config.AppSettings; +import com.google.common.annotations.VisibleForTesting; import org.sonar.process.ProcessId; import org.sonar.process.sharedmemoryfile.DefaultProcessCommands; import org.sonar.process.sharedmemoryfile.ProcessCommands; -import static org.sonar.process.ProcessProperties.Property.ENABLE_STOP_COMMAND; +public class HardStopRequestWatcherImpl extends AbstractStopRequestWatcher { -public class HardStopRequestWatcherImpl extends Thread implements StopRequestWatcher { - - private static final long DEFAULT_WATCHER_DELAY_MS = 500L; - - private final ProcessCommands commands; - private final Runnable listener; - private final AppSettings settings; - private long delayMs = DEFAULT_WATCHER_DELAY_MS; - - HardStopRequestWatcherImpl(AppSettings settings, Runnable listener, ProcessCommands commands) { - super("HardStopRequestWatcherImpl"); - this.settings = settings; - this.commands = commands; - this.listener = listener; - - // safeguard, do not block the JVM if thread is not interrupted - // (method stopWatching() never called). - setDaemon(true); - } - - public static HardStopRequestWatcherImpl create(AppSettings settings, Runnable listener, FileSystem fs) { - DefaultProcessCommands commands = DefaultProcessCommands.secondary(fs.getTempDir(), ProcessId.APP.getIpcIndex()); - return new HardStopRequestWatcherImpl(settings, listener, commands); + HardStopRequestWatcherImpl(Scheduler scheduler, ProcessCommands commands) { + super("SQ Hard stop request watcher", commands::askedForHardStop, scheduler::hardStop); } - long getDelayMs() { - return delayMs; + @VisibleForTesting + HardStopRequestWatcherImpl(Scheduler scheduler, ProcessCommands commands, long delayMs) { + super("SQ Hard stop request watcher", commands::askedForHardStop, scheduler::hardStop, delayMs); } - void setDelayMs(long delayMs) { - this.delayMs = delayMs; - } - - @Override - public void run() { - try { - while (true) { - if (commands.askedForHardStop()) { - listener.run(); - return; - } - Thread.sleep(delayMs); - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - // stop watching the commands - } - } - - @Override - public void startWatching() { - if (settings.getProps().valueAsBoolean(ENABLE_STOP_COMMAND.getKey())) { - start(); - } + public static HardStopRequestWatcherImpl create(Scheduler scheduler, FileSystem fs) { + DefaultProcessCommands commands = DefaultProcessCommands.secondary(fs.getTempDir(), ProcessId.APP.getIpcIndex()); + return new HardStopRequestWatcherImpl(scheduler, commands); } - @Override - public void stopWatching() { - // does nothing if not started - interrupt(); - } } diff --git a/server/sonar-main/src/main/java/org/sonar/application/StopRequestWatcherImpl.java b/server/sonar-main/src/main/java/org/sonar/application/StopRequestWatcherImpl.java new file mode 100644 index 00000000000..c7350df7815 --- /dev/null +++ b/server/sonar-main/src/main/java/org/sonar/application/StopRequestWatcherImpl.java @@ -0,0 +1,49 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.application; + +import org.sonar.application.config.AppSettings; +import org.sonar.process.ProcessId; +import org.sonar.process.sharedmemoryfile.DefaultProcessCommands; +import org.sonar.process.sharedmemoryfile.ProcessCommands; + +import static org.sonar.process.ProcessProperties.Property.ENABLE_STOP_COMMAND; + +public class StopRequestWatcherImpl extends AbstractStopRequestWatcher { + private final AppSettings settings; + + StopRequestWatcherImpl(AppSettings settings, Scheduler scheduler, ProcessCommands commands) { + super("SQ stop request watcher", commands::askedForStop, scheduler::stop); + + this.settings = settings; + } + + public static StopRequestWatcherImpl create(AppSettings settings, Scheduler scheduler, FileSystem fs) { + DefaultProcessCommands commands = DefaultProcessCommands.secondary(fs.getTempDir(), ProcessId.APP.getIpcIndex()); + return new StopRequestWatcherImpl(settings, scheduler, commands); + } + + @Override + public void startWatching() { + if (settings.getProps().valueAsBoolean(ENABLE_STOP_COMMAND.getKey())) { + super.startWatching(); + } + } +} diff --git a/server/sonar-main/src/test/java/org/sonar/application/AbstractStopRequestWatcherTest.java b/server/sonar-main/src/test/java/org/sonar/application/AbstractStopRequestWatcherTest.java new file mode 100644 index 00000000000..f155b1872fe --- /dev/null +++ b/server/sonar-main/src/test/java/org/sonar/application/AbstractStopRequestWatcherTest.java @@ -0,0 +1,125 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.application; + +import java.util.Random; +import java.util.concurrent.TimeUnit; +import java.util.function.BooleanSupplier; +import org.apache.commons.lang.RandomStringUtils; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; + +public class AbstractStopRequestWatcherTest { + + @Rule + public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60)); + + private String threadName = RandomStringUtils.randomAlphabetic(12); + private TestBooleanSupplier booleanSupplier = new TestBooleanSupplier(); + private TestAction stopAction = new TestAction(); + + @Test + public void constructor_sets_thread_name_and_daemon_true() { + AbstractStopRequestWatcher underTest = new AbstractStopRequestWatcher(threadName, booleanSupplier, stopAction) { + + }; + assertThat(underTest.getName()).isEqualTo(threadName); + assertThat(underTest.isDaemon()).isTrue(); + } + + @Test + public void call_stop_action_when_booleanSupplier_returns_true() { + AbstractStopRequestWatcher underTest = new AbstractStopRequestWatcher(threadName, booleanSupplier, stopAction) { + + }; + + underTest.startWatching(); + assertThat(underTest.isAlive()).isTrue(); + assertThat(stopAction.isCalled()).isFalse(); + + booleanSupplier.flag = true; + await().atMost(1, TimeUnit.MINUTES).until(() -> stopAction.isCalled()); + assertThat(stopAction.isCalled()).isTrue(); + + underTest.stopWatching(); + await().until(() -> !underTest.isAlive()); + } + + @Test + public void create_instance_with_default_delay() { + AbstractStopRequestWatcher underTest = new AbstractStopRequestWatcher(threadName, booleanSupplier, stopAction) { + + }; + + assertThat(underTest.getDelayMs()).isEqualTo(500L); + } + + @Test + public void create_instance_with_specified_delay() { + long delayMs = new Random().nextLong(); + AbstractStopRequestWatcher underTest = new AbstractStopRequestWatcher(threadName, booleanSupplier, stopAction, delayMs) { + + }; + + assertThat(underTest.getDelayMs()).isEqualTo(delayMs); + } + + @Test + public void stop_watching_commands_if_thread_is_interrupted() { + AbstractStopRequestWatcher underTest = new AbstractStopRequestWatcher(threadName, booleanSupplier, stopAction) { + + }; + + underTest.startWatching(); + underTest.interrupt(); + + await().until(() -> !underTest.isAlive()); + assertThat(underTest.isAlive()).isFalse(); + } + + private static class TestBooleanSupplier implements BooleanSupplier { + volatile boolean flag = false; + + @Override + public boolean getAsBoolean() { + return flag; + } + + } + + private static class TestAction implements Runnable { + volatile boolean called = false; + + @Override + public void run() { + called = true; + } + + public boolean isCalled() { + return called; + } + } +} diff --git a/server/sonar-main/src/test/java/org/sonar/application/HardStopRequestWatcherImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/HardStopRequestWatcherImplTest.java index 50ff3132a1a..c2d5938008a 100644 --- a/server/sonar-main/src/test/java/org/sonar/application/HardStopRequestWatcherImplTest.java +++ b/server/sonar-main/src/test/java/org/sonar/application/HardStopRequestWatcherImplTest.java @@ -26,18 +26,15 @@ import org.junit.rules.DisableOnDebug; import org.junit.rules.TemporaryFolder; import org.junit.rules.TestRule; import org.junit.rules.Timeout; -import org.sonar.application.config.AppSettings; import org.sonar.process.sharedmemoryfile.ProcessCommands; import static org.assertj.core.api.Assertions.assertThat; -import static org.mockito.Mockito.RETURNS_DEEP_STUBS; +import static org.awaitility.Awaitility.await; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.verifyZeroInteractions; import static org.mockito.Mockito.when; -import static org.sonar.process.ProcessProperties.Property.ENABLE_STOP_COMMAND; public class HardStopRequestWatcherImplTest { @@ -46,39 +43,24 @@ public class HardStopRequestWatcherImplTest { @Rule public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60)); - private AppSettings settings = mock(AppSettings.class, RETURNS_DEEP_STUBS); private ProcessCommands commands = mock(ProcessCommands.class); - private Runnable listener = mock(Runnable.class); + private Scheduler scheduler = mock(Scheduler.class); @Test - public void do_not_watch_command_if_disabled() { - enableSetting(false); - HardStopRequestWatcherImpl underTest = new HardStopRequestWatcherImpl(settings, listener, commands); - - underTest.startWatching(); - assertThat(underTest.isAlive()).isFalse(); - - underTest.stopWatching(); - verifyZeroInteractions(commands, listener); - } - - @Test - public void watch_stop_command_if_enabled() throws Exception { - enableSetting(true); - HardStopRequestWatcherImpl underTest = new HardStopRequestWatcherImpl(settings, listener, commands); - underTest.setDelayMs(1L); + public void watch_hard_stop_command() { + HardStopRequestWatcherImpl underTest = new HardStopRequestWatcherImpl(scheduler, commands, 1); underTest.startWatching(); assertThat(underTest.isAlive()).isTrue(); - verify(listener, never()).run(); + verify(scheduler, never()).hardStop(); when(commands.askedForHardStop()).thenReturn(true); - verify(listener, timeout(1_000L)).run(); + verify(scheduler, timeout(1_000L)).hardStop(); underTest.stopWatching(); - while (underTest.isAlive()) { - Thread.sleep(1L); - } + + await().until(() -> !underTest.isAlive()); + assertThat(underTest.isAlive()).isFalse(); } @Test @@ -86,27 +68,20 @@ public class HardStopRequestWatcherImplTest { FileSystem fs = mock(FileSystem.class); when(fs.getTempDir()).thenReturn(temp.newFolder()); - HardStopRequestWatcherImpl underTest = HardStopRequestWatcherImpl.create(settings, listener, fs); + HardStopRequestWatcherImpl underTest = HardStopRequestWatcherImpl.create(scheduler, fs); assertThat(underTest.getDelayMs()).isEqualTo(500L); } @Test - public void stop_watching_commands_if_thread_is_interrupted() throws Exception { - enableSetting(true); - HardStopRequestWatcherImpl underTest = new HardStopRequestWatcherImpl(settings, listener, commands); + public void stop_watching_commands_if_thread_is_interrupted() { + HardStopRequestWatcherImpl underTest = new HardStopRequestWatcherImpl(scheduler, commands); underTest.startWatching(); underTest.interrupt(); - while (underTest.isAlive()) { - Thread.sleep(1L); - } + await().until(() -> !underTest.isAlive()); assertThat(underTest.isAlive()).isFalse(); } - private void enableSetting(boolean b) { - when(settings.getProps().valueAsBoolean(ENABLE_STOP_COMMAND.getKey())).thenReturn(b); - } - } diff --git a/server/sonar-main/src/test/java/org/sonar/application/StopRequestWatcherImplTest.java b/server/sonar-main/src/test/java/org/sonar/application/StopRequestWatcherImplTest.java new file mode 100644 index 00000000000..011d9696a50 --- /dev/null +++ b/server/sonar-main/src/test/java/org/sonar/application/StopRequestWatcherImplTest.java @@ -0,0 +1,99 @@ +/* + * SonarQube + * Copyright (C) 2009-2019 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.application; + +import java.io.IOException; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.DisableOnDebug; +import org.junit.rules.TemporaryFolder; +import org.junit.rules.TestRule; +import org.junit.rules.Timeout; +import org.sonar.application.config.TestAppSettings; +import org.sonar.process.sharedmemoryfile.ProcessCommands; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.awaitility.Awaitility.await; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.timeout; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.sonar.process.ProcessProperties.Property.ENABLE_STOP_COMMAND; + +public class StopRequestWatcherImplTest { + + @Rule + public TemporaryFolder temp = new TemporaryFolder(); + @Rule + public TestRule safeguardTimeout = new DisableOnDebug(Timeout.seconds(60)); + + private TestAppSettings appSettings = new TestAppSettings(); + private ProcessCommands commands = mock(ProcessCommands.class); + private Scheduler scheduler = mock(Scheduler.class); + + @Test + public void startWatching_does_not_start_thread_if_stop_command_is_disabled() { + StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(appSettings, scheduler, commands); + + underTest.startWatching(); + assertThat(underTest.isAlive()).isFalse(); + } + + @Test + public void watch_stop_command_if_stop_command_is_enabled() { + appSettings.set(ENABLE_STOP_COMMAND.getKey(), "true"); + StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(appSettings, scheduler, commands); + + underTest.startWatching(); + assertThat(underTest.isAlive()).isTrue(); + verify(scheduler, never()).stop(); + + when(commands.askedForStop()).thenReturn(true); + verify(scheduler, timeout(1_000L)).stop(); + + underTest.stopWatching(); + + await().until(() -> !underTest.isAlive()); + assertThat(underTest.isAlive()).isFalse(); + } + + @Test + public void create_instance_with_default_delay() throws IOException { + FileSystem fs = mock(FileSystem.class); + when(fs.getTempDir()).thenReturn(temp.newFolder()); + + StopRequestWatcherImpl underTest = StopRequestWatcherImpl.create(appSettings, scheduler, fs); + + assertThat(underTest.getDelayMs()).isEqualTo(500L); + } + + @Test + public void stop_watching_commands_if_thread_is_interrupted() { + StopRequestWatcherImpl underTest = new StopRequestWatcherImpl(appSettings, scheduler, commands); + + underTest.startWatching(); + underTest.interrupt(); + + await().until(() -> !underTest.isAlive()); + assertThat(underTest.isAlive()).isFalse(); + } + +} 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 7dd45f82f8c..9133001ecda 100644 --- a/sonar-application/src/main/java/org/sonar/application/App.java +++ b/sonar-application/src/main/java/org/sonar/application/App.java @@ -40,7 +40,8 @@ public class App { private final SystemExit systemExit = new SystemExit(); private final JavaVersion javaVersion; - private StopRequestWatcher hardStopRequestWatcher; + private StopRequestWatcher stopRequestWatcher = null; + private StopRequestWatcher hardStopRequestWatcher = null; public App(JavaVersion javaVersion) { this.javaVersion = javaVersion; @@ -65,12 +66,15 @@ public class App { try (ProcessLauncher processLauncher = new ProcessLauncherImpl(fileSystem.getTempDir())) { Scheduler scheduler = new SchedulerImpl(settings, appReloader, commandFactory, processLauncher, appState); + scheduler.schedule(); + + stopRequestWatcher = StopRequestWatcherImpl.create(settings, scheduler, fileSystem); + hardStopRequestWatcher = HardStopRequestWatcherImpl.create(scheduler, fileSystem); + // intercepts CTRL-C Runtime.getRuntime().addShutdownHook(new ShutdownHook(scheduler)); - scheduler.schedule(); - - hardStopRequestWatcher = HardStopRequestWatcherImpl.create(settings, scheduler::hardStop, fileSystem); + stopRequestWatcher.startWatching(); hardStopRequestWatcher.startWatching(); scheduler.awaitTermination(); @@ -91,7 +95,7 @@ public class App { } } - public static void main(String... args) throws Exception { + public static void main(String[] args) throws Exception { new App(JavaVersion.INSTANCE).start(args); } @@ -99,17 +103,15 @@ public class App { private final Scheduler scheduler; public ShutdownHook(Scheduler scheduler) { - super("SonarQube Shutdown Hook"); + super("Shutdown Hook"); this.scheduler = scheduler; } @Override public void run() { systemExit.setInShutdownHook(); - - if (hardStopRequestWatcher != null) { - hardStopRequestWatcher.stopWatching(); - } + stopRequestWatcher.stopWatching(); + hardStopRequestWatcher.stopWatching(); // blocks until everything is corrected terminated scheduler.stop(); |