001 /* 002 * SonarQube, open source software quality management tool. 003 * Copyright (C) 2008-2014 SonarSource 004 * mailto:contact AT sonarsource DOT com 005 * 006 * SonarQube is free software; you can redistribute it and/or 007 * modify it under the terms of the GNU Lesser General Public 008 * License as published by the Free Software Foundation; either 009 * version 3 of the License, or (at your option) any later version. 010 * 011 * SonarQube is distributed in the hope that it will be useful, 012 * but WITHOUT ANY WARRANTY; without even the implied warranty of 013 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 * Lesser General Public License for more details. 015 * 016 * You should have received a copy of the GNU Lesser General Public License 017 * along with this program; if not, write to the Free Software Foundation, 018 * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 019 */ 020 package org.sonar.process.monitor; 021 022 import org.slf4j.LoggerFactory; 023 import org.sonar.process.Lifecycle; 024 import org.sonar.process.Lifecycle.State; 025 import org.sonar.process.SystemExit; 026 027 import java.util.List; 028 import java.util.concurrent.CopyOnWriteArrayList; 029 030 public class Monitor { 031 032 private final List<ProcessRef> processes = new CopyOnWriteArrayList<ProcessRef>(); 033 private final TerminatorThread terminator; 034 private final JavaProcessLauncher launcher; 035 private final Lifecycle lifecycle = new Lifecycle(); 036 037 private final SystemExit systemExit; 038 private Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook"); 039 040 // used by awaitStop() to block until all processes are shutdown 041 private final List<WatcherThread> watcherThreads = new CopyOnWriteArrayList<WatcherThread>(); 042 043 Monitor(JavaProcessLauncher launcher, SystemExit exit, TerminatorThread terminator) { 044 this.launcher = launcher; 045 this.terminator = terminator; 046 this.systemExit = exit; 047 } 048 049 public static Monitor create() { 050 Timeouts timeouts = new Timeouts(); 051 return new Monitor(new JavaProcessLauncher(timeouts), new SystemExit(), new TerminatorThread(timeouts)); 052 } 053 054 /** 055 * Starts commands and blocks current thread until all processes are in state {@link State#STARTED}. 056 * @throws java.lang.IllegalArgumentException if commands list is empty 057 * @throws java.lang.IllegalStateException if already started or if at least one process failed to start. In this case 058 * all processes are terminated. No need to execute {@link #stop()} 059 */ 060 public void start(List<JavaCommand> commands) { 061 if (commands.isEmpty()) { 062 throw new IllegalArgumentException("At least one command is required"); 063 } 064 065 if (!lifecycle.tryToMoveTo(State.STARTING)) { 066 throw new IllegalStateException("Can not start multiple times"); 067 } 068 069 // intercepts CTRL-C 070 Runtime.getRuntime().addShutdownHook(shutdownHook); 071 072 for (JavaCommand command : commands) { 073 try { 074 ProcessRef processRef = launcher.launch(command); 075 monitor(command, processRef); 076 } catch (RuntimeException e) { 077 // fail to start or to monitor 078 stop(); 079 throw e; 080 } 081 } 082 083 if (!lifecycle.tryToMoveTo(State.STARTED)) { 084 // stopping or stopped during startup, for instance : 085 // 1. A is started 086 // 2. B starts 087 // 3. A crashes while B is starting 088 // 4. if B was not monitored during Terminator execution, then it's an alive orphan 089 stop(); 090 throw new IllegalStateException("Stopped during startup"); 091 } 092 } 093 094 private void monitor(JavaCommand command, ProcessRef processRef) { 095 // physically watch if process is alive 096 WatcherThread watcherThread = new WatcherThread(processRef, this); 097 watcherThread.start(); 098 watcherThreads.add(watcherThread); 099 100 processes.add(processRef); 101 102 // wait for process to be ready (accept requests or so on) 103 processRef.waitForReady(); 104 105 LoggerFactory.getLogger(getClass()).info(String.format("%s is up", processRef)); 106 } 107 108 /** 109 * Blocks until all processes are terminated 110 */ 111 public void awaitTermination() { 112 for (WatcherThread watcherThread : watcherThreads) { 113 while (watcherThread.isAlive()) { 114 try { 115 watcherThread.join(); 116 } catch (InterruptedException ignored) { 117 // ignore, stop blocking 118 } 119 } 120 } 121 } 122 123 /** 124 * Blocks until all processes are terminated. 125 */ 126 public void stop() { 127 stopAsync(); 128 try { 129 terminator.join(); 130 } catch (InterruptedException ignored) { 131 // stop blocking and exiting 132 } 133 // safeguard if TerminatorThread is buggy 134 lifecycle.tryToMoveTo(State.STOPPED); 135 systemExit.exit(0); 136 } 137 138 /** 139 * Asks for processes termination and returns without blocking until termination. 140 */ 141 public void stopAsync() { 142 if (lifecycle.tryToMoveTo(State.STOPPING)) { 143 terminator.setProcesses(processes); 144 terminator.start(); 145 } 146 } 147 148 public State getState() { 149 return lifecycle.getState(); 150 } 151 152 Thread getShutdownHook() { 153 return shutdownHook; 154 } 155 156 private class MonitorShutdownHook implements Runnable { 157 @Override 158 public void run() { 159 systemExit.setInShutdownHook(); 160 // blocks until everything is corrected terminated 161 stop(); 162 } 163 } 164 }