/* * 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.ce.app; import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableMap; import java.util.concurrent.atomic.AtomicReference; import javax.annotation.CheckForNull; import org.sonar.api.utils.log.Logger; import org.sonar.api.utils.log.Loggers; import org.sonar.ce.ComputeEngine; import org.sonar.ce.ComputeEngineImpl; import org.sonar.ce.container.ComputeEngineContainerImpl; import org.sonar.ce.log.CeProcessLogging; import org.sonar.process.MinimumViableSystem; import org.sonar.process.Monitored; import org.sonar.process.ProcessEntryPoint; import org.sonar.process.Props; import static com.google.common.base.Preconditions.checkState; import static org.sonar.process.ProcessUtils.awaitTermination; /** * The Compute Engine server which starts a daemon thread to run the {@link ComputeEngineImpl} when it's {@link #start()} * method is called. * <p> * This is the class to call to run a standalone {@link ComputeEngineImpl} (see {@link #main(String[])}). * </p> */ public class CeServer implements Monitored { private static final Logger LOG = Loggers.get(CeServer.class); private static final String CE_MAIN_THREAD_NAME = "ce-main"; /** * Thread that currently is inside our await() method. */ private AtomicReference<Thread> awaitThread = new AtomicReference<>(); private volatile boolean stopAwait = false; private final ComputeEngine computeEngine; @CheckForNull private CeMainThread ceMainThread = null; @VisibleForTesting protected CeServer(ComputeEngine computeEngine, MinimumViableSystem mvs) { this.computeEngine = computeEngine; mvs .checkWritableTempDir() .checkRequiredJavaOptions(ImmutableMap.of("file.encoding", "UTF-8")); } @Override public void start() { checkState(ceMainThread == null, "start() can not be called twice"); // start main thread ceMainThread = new CeMainThread(); ceMainThread.start(); } @Override public Status getStatus() { checkState(ceMainThread != null, "getStatus() can not be called before start()"); if (ceMainThread.isStarted()) { return Status.OPERATIONAL; } return Status.DOWN; } @Override public void awaitStop() { checkState(awaitThread.compareAndSet(null, Thread.currentThread()), "There can't be more than one thread waiting for the Compute Engine to stop"); checkState(ceMainThread != null, "awaitStop() must not be called before start()"); try { while (!stopAwait) { try { // wait for a quite long time but we will be interrupted if flag changes anyway Thread.sleep(10_000); } catch (InterruptedException e) { // continue and check the flag } } } finally { awaitThread = null; } } @Override public void stop() { if (ceMainThread != null) { // signal main Thread to stop ceMainThread.stopIt(); awaitTermination(ceMainThread); } } /** * Can't be started as is. Needs to be bootstrapped by sonar-application */ public static void main(String[] args) { ProcessEntryPoint entryPoint = ProcessEntryPoint.createForArguments(args); Props props = entryPoint.getProps(); new CeProcessLogging().configure(props); CeServer server = new CeServer( new ComputeEngineImpl(props, new ComputeEngineContainerImpl()), new MinimumViableSystem()); entryPoint.launch(server); } private class CeMainThread extends Thread { private static final int CHECK_FOR_STOP_DELAY = 50; private volatile boolean stop = false; private volatile boolean started = false; public CeMainThread() { super(CE_MAIN_THREAD_NAME); } @Override public void run() { boolean startupSuccessful = attemptStartup(); this.started = true; if (startupSuccessful) { // call below is blocking waitForStopSignal(); } else { stopAwait(); } } private boolean attemptStartup() { try { startup(); return true; } catch (org.sonar.api.utils.MessageException | org.sonar.process.MessageException e) { LOG.error("Compute Engine startup failed: " + e.getMessage()); return false; } catch (Throwable e) { LOG.error("Compute Engine startup failed", e); return false; } } private void startup() { LOG.info("Compute Engine starting up..."); computeEngine.startup(); LOG.info("Compute Engine is operational"); } private void waitForStopSignal() { while (!stop) { try { Thread.sleep(CHECK_FOR_STOP_DELAY); } catch (InterruptedException e) { // ignore the interruption itself, check the flag } } attemptShutdown(); } private void attemptShutdown() { try { shutdown(); } catch (Throwable e) { LOG.error("Compute Engine shutdown failed", e); } finally { // release thread waiting for CeServer stopAwait(); } } private void shutdown() { LOG.info("Compute Engine shutting down..."); computeEngine.shutdown(); } public boolean isStarted() { return started; } public void stopIt() { // stop looping indefinitely this.stop = true; // interrupt current thread in case its waiting for WebServer interrupt(); } private void stopAwait() { stopAwait = true; Thread t = awaitThread.get(); if (t != null) { t.interrupt(); try { t.join(1000); } catch (InterruptedException e) { // Ignored } } } } }