/* * 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.Logger; import org.slf4j.LoggerFactory; public class ProcessEntryPoint implements Stoppable { public static final String PROPERTY_PROCESS_KEY = "process.key"; public static final String PROPERTY_PROCESS_INDEX = "process.index"; public static final String PROPERTY_TERMINATION_TIMEOUT = "process.terminationTimeout"; public static final String PROPERTY_SHARED_PATH = "process.sharedDir"; private final Props props; private final String processKey; private final int processNumber; private final File sharedDir; private final Lifecycle lifecycle = new Lifecycle(); private final ProcessCommands commands; private final SystemExit exit; private volatile Monitored monitored; private volatile StopperThread stopperThread; private final StopWatcher stopWatcher; // new Runnable() is important to avoid conflict of call to ProcessEntryPoint#stop() with Thread#stop() private Thread shutdownHook = new Thread(new Runnable() { @Override public void run() { exit.setInShutdownHook(); stop(); } }); ProcessEntryPoint(Props props, SystemExit exit, ProcessCommands commands) { this(props, getProcessNumber(props), getSharedDir(props), exit, commands); } private ProcessEntryPoint(Props props, int processNumber, File sharedDir, SystemExit exit, ProcessCommands commands) { this.props = props; this.processKey = props.nonNullValue(PROPERTY_PROCESS_KEY); this.processNumber = processNumber; this.sharedDir = sharedDir; this.exit = exit; this.commands = commands; this.stopWatcher = new StopWatcher(commands, this); } public ProcessCommands getCommands() { return commands; } public Props getProps() { return props; } public String getKey() { return processKey; } public int getProcessNumber() { return processNumber; } public File getSharedDir() { return sharedDir; } /** * Launch process and waits until it's down */ public void launch(Monitored mp) { if (!lifecycle.tryToMoveTo(Lifecycle.State.STARTING)) { throw new IllegalStateException("Already started"); } monitored = mp; Logger logger = LoggerFactory.getLogger(getClass()); try { launch(logger); } catch (Exception e) { logger.warn("Fail to start " + getKey(), e); } finally { stop(); } } private void launch(Logger logger) throws InterruptedException { logger.info("Starting " + getKey()); Runtime.getRuntime().addShutdownHook(shutdownHook); stopWatcher.start(); monitored.start(); Monitored.Status status = waitForNotDownStatus(); if (status == Monitored.Status.UP || status == Monitored.Status.OPERATIONAL) { // notify monitor that process is ready commands.setUp(); if (lifecycle.tryToMoveTo(Lifecycle.State.STARTED)) { Monitored.Status newStatus = waitForOperational(status); if (newStatus == Monitored.Status.OPERATIONAL && lifecycle.tryToMoveTo(Lifecycle.State.OPERATIONAL)) { commands.setOperational(); } monitored.awaitStop(); } } else { stop(); } } private Monitored.Status waitForNotDownStatus() throws InterruptedException { Monitored.Status status = Monitored.Status.DOWN; while (status == Monitored.Status.DOWN) { status = monitored.getStatus(); Thread.sleep(20L); } return status; } private Monitored.Status waitForOperational(Monitored.Status currentStatus) throws InterruptedException { Monitored.Status status = currentStatus; // wait for operation or stop waiting if going to OPERATIONAL failed while (status != Monitored.Status.OPERATIONAL && status != Monitored.Status.FAILED) { status = monitored.getStatus(); Thread.sleep(20L); } return status; } boolean isStarted() { return lifecycle.getState() == Lifecycle.State.STARTED; } /** * Blocks until stopped in a timely fashion (see {@link org.sonar.process.StopperThread}) */ void stop() { stopAsync(); try { // stopperThread is not null for sure // join() does nothing if thread already finished stopperThread.join(); lifecycle.tryToMoveTo(Lifecycle.State.STOPPED); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } exit.exit(0); } @Override public void stopAsync() { if (lifecycle.tryToMoveTo(Lifecycle.State.STOPPING)) { stopperThread = new StopperThread(monitored, commands, Long.parseLong(props.nonNullValue(PROPERTY_TERMINATION_TIMEOUT))); stopperThread.start(); stopWatcher.stopWatching(); } } Lifecycle.State getState() { return lifecycle.getState(); } Thread getShutdownHook() { return shutdownHook; } public static ProcessEntryPoint createForArguments(String[] args) { Props props = ConfigurationUtils.loadPropsFromCommandLineArgs(args); File sharedDir = getSharedDir(props); int processNumber = getProcessNumber(props); ProcessCommands commands = DefaultProcessCommands.main(sharedDir, processNumber); return new ProcessEntryPoint(props, processNumber, sharedDir, new SystemExit(), commands); } private static int getProcessNumber(Props props) { return Integer.parseInt(props.nonNullValue(PROPERTY_PROCESS_INDEX)); } private static File getSharedDir(Props props) { return props.nonNullValueAsFile(PROPERTY_SHARED_PATH); } }