From 75a661f61c2e0667b626bd17853eaf3e5201b4f0 Mon Sep 17 00:00:00 2001 From: Duarte Meneses Date: Tue, 23 Jun 2015 10:08:02 +0200 Subject: [PATCH] clean shutdown --- .../src/main/java/org/sonar/runner/Main.java | 28 ++++-- .../main/java/org/sonar/runner/Shutdown.java | 92 +++++++++++++++++++ .../test/java/org/sonar/runner/MainTest.java | 3 +- .../java/org/sonar/runner/ShutdownTest.java | 78 ++++++++++++++++ 4 files changed, 190 insertions(+), 11 deletions(-) create mode 100644 sonar-runner-dist/src/main/java/org/sonar/runner/Shutdown.java create mode 100644 sonar-runner-dist/src/test/java/org/sonar/runner/ShutdownTest.java diff --git a/sonar-runner-dist/src/main/java/org/sonar/runner/Main.java b/sonar-runner-dist/src/main/java/org/sonar/runner/Main.java index d991dc2..ea472d1 100644 --- a/sonar-runner-dist/src/main/java/org/sonar/runner/Main.java +++ b/sonar-runner-dist/src/main/java/org/sonar/runner/Main.java @@ -43,15 +43,15 @@ import org.sonar.runner.impl.Logs; */ public class Main { - private final Exit exit; + private final Shutdown shutdown; private final Cli cli; private final Conf conf; private final RunnerFactory runnerFactory; private Runner runner; private BufferedReader inputReader; - Main(Exit exit, Cli cli, Conf conf, RunnerFactory runnerFactory) { - this.exit = exit; + Main(Shutdown shutdown, Cli cli, Conf conf, RunnerFactory runnerFactory) { + this.shutdown = shutdown; this.cli = cli; this.conf = conf; this.runnerFactory = runnerFactory; @@ -59,9 +59,10 @@ public class Main { public static void main(String[] args) { Exit exit = new Exit(); + Shutdown shutdown = new Shutdown(exit); Cli cli = new Cli(exit).parse(args); cli.verify(); - Main main = new Main(exit, cli, new Conf(cli), new RunnerFactory()); + Main main = new Main(shutdown, cli, new Conf(cli), new RunnerFactory()); main.execute(); } @@ -84,17 +85,17 @@ public class Main { } catch (Exception e) { displayExecutionResult(stats, "FAILURE"); showError("Error during Sonar runner execution", e, cli.isDisplayStackTrace()); - exit.exit(Exit.ERROR); + shutdown.exit(Exit.ERROR); } runner.stop(); - exit.exit(Exit.SUCCESS); + shutdown.exit(Exit.SUCCESS); } private void init(Properties p) throws IOException { SystemInfo.print(); if (cli.isDisplayVersionOnly()) { - exit.exit(Exit.SUCCESS); + shutdown.exit(Exit.SUCCESS); } if (cli.isDisplayStackTrace()) { @@ -113,9 +114,18 @@ public class Main { if (inputReader == null) { inputReader = new BufferedReader(new InputStreamReader(System.in, StandardCharsets.UTF_8)); } + + shutdown.signalReady(true); + if(shutdown.shouldExit()) { + //exit before displaying message + return false; + } + Logs.info(""); - - return inputReader.readLine() != null; + String line = inputReader.readLine(); + shutdown.signalReady(false); + + return line != null; } private static void displayExecutionResult(Stats stats, String resultMsg) { diff --git a/sonar-runner-dist/src/main/java/org/sonar/runner/Shutdown.java b/sonar-runner-dist/src/main/java/org/sonar/runner/Shutdown.java new file mode 100644 index 0000000..70ec898 --- /dev/null +++ b/sonar-runner-dist/src/main/java/org/sonar/runner/Shutdown.java @@ -0,0 +1,92 @@ +/* + * SonarQube Runner - Distribution + * Copyright (C) 2011 SonarSource + * dev@sonar.codehaus.org + * + * 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 02 + */ +package org.sonar.runner; + +class Shutdown { + static final int SUCCESS = 0; + static final int ERROR = 1; + private static final long DEFAULT_MAX_WAIT = 10_000; + + private long maxWait; + ShutdownHook hook = new ShutdownHook(); + private boolean isReady = false; + private boolean exiting = false; + private Object lock = new Object(); + private Exit exit; + private Thread t; + + Shutdown(Exit exit) { + this(exit, DEFAULT_MAX_WAIT); + } + + Shutdown(Exit exit, long maxWait) { + this.maxWait = maxWait; + this.exit = exit; + Runtime.getRuntime().addShutdownHook(hook); + } + + void exit(int status) { + synchronized (lock) { + signalReady(true); + } + exit.exit(status); + } + + void signalReady(boolean ready) { + synchronized (lock) { + System.out.println("READY: " + ready); + this.isReady = ready; + lock.notifyAll(); + } + } + + boolean shouldExit() { + synchronized (lock) { + return exiting; + } + } + + class ShutdownHook extends Thread { + private ShutdownHook() { + this.setName("shutdown-hook"); + } + + @Override + public void run() { + long startTime = System.currentTimeMillis(); + synchronized (lock) { + exiting = true; + + while (!isReady) { + long waitTime = startTime + maxWait - System.currentTimeMillis(); + if (waitTime <= 0) { + break; + } + + try { + lock.wait(waitTime); + } catch (InterruptedException e) { + // continue + } + } + } + } + } +} diff --git a/sonar-runner-dist/src/test/java/org/sonar/runner/MainTest.java b/sonar-runner-dist/src/test/java/org/sonar/runner/MainTest.java index 773632d..7b7e019 100644 --- a/sonar-runner-dist/src/test/java/org/sonar/runner/MainTest.java +++ b/sonar-runner-dist/src/test/java/org/sonar/runner/MainTest.java @@ -30,7 +30,6 @@ import org.sonar.runner.api.Runner; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.util.Properties; @@ -44,7 +43,7 @@ import static org.mockito.Mockito.when; public class MainTest { @Mock - private Exit exit; + private Shutdown exit; @Mock private Cli cli; @Mock diff --git a/sonar-runner-dist/src/test/java/org/sonar/runner/ShutdownTest.java b/sonar-runner-dist/src/test/java/org/sonar/runner/ShutdownTest.java new file mode 100644 index 0000000..9de7f4c --- /dev/null +++ b/sonar-runner-dist/src/test/java/org/sonar/runner/ShutdownTest.java @@ -0,0 +1,78 @@ +/* + * SonarQube Runner - Distribution + * Copyright (C) 2011 SonarSource + * dev@sonar.codehaus.org + * + * 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 02 + */ +package org.sonar.runner; + +import static org.mockito.Mockito.verify; +import static org.fest.assertions.Assertions.assertThat; +import org.mockito.MockitoAnnotations; +import org.mockito.Mock; +import org.junit.Test; +import org.junit.Before; + +public class ShutdownTest { + @Mock + private Exit exit; + private Shutdown shutdown; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + shutdown = new Shutdown(exit); + } + + @Test + public void testShutdown() { + shutdown.exit(3); + verify(exit).exit(3); + } + + @Test(timeout = 60_000) + public void testWaitReady() throws InterruptedException { + shutdown = new Shutdown(exit, 100_000); + shutdown.signalReady(false); + assertThat(shutdown.shouldExit()).isFalse(); + + Thread t = new HookCaller(); + t.start(); + Thread.sleep(1000); + + assertThat(t.isAlive()).isTrue(); + assertThat(shutdown.shouldExit()).isTrue(); + + shutdown.signalReady(true); + t.join(); + } + + @Test(timeout = 60_000) + public void testTimeout() throws InterruptedException { + shutdown = new Shutdown(exit, 0); + + Thread t = new HookCaller(); + t.start(); + t.join(); + } + + private class HookCaller extends Thread { + @Override + public void run() { + shutdown.hook.run(); + } + } +} -- 2.39.5