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 */ 020package org.sonar.process.monitor; 021 022import org.slf4j.LoggerFactory; 023import org.sonar.process.Lifecycle; 024import org.sonar.process.Lifecycle.State; 025import org.sonar.process.ProcessCommands; 026import org.sonar.process.SystemExit; 027 028import java.util.List; 029import java.util.concurrent.CopyOnWriteArrayList; 030 031public class Monitor { 032 033 private final List<ProcessRef> processes = new CopyOnWriteArrayList<ProcessRef>(); 034 private final TerminatorThread terminator; 035 private final JavaProcessLauncher launcher; 036 private final Lifecycle lifecycle = new Lifecycle(); 037 038 private final SystemExit systemExit; 039 private Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook"); 040 041 // used by awaitStop() to block until all processes are shutdown 042 private final List<WatcherThread> watcherThreads = new CopyOnWriteArrayList<WatcherThread>(); 043 static int nextProcessId = 0; 044 045 Monitor(JavaProcessLauncher launcher, SystemExit exit, TerminatorThread terminator) { 046 this.launcher = launcher; 047 this.terminator = terminator; 048 this.systemExit = exit; 049 } 050 051 public static Monitor create() { 052 Timeouts timeouts = new Timeouts(); 053 return new Monitor(new JavaProcessLauncher(timeouts), new SystemExit(), new TerminatorThread(timeouts)); 054 } 055 056 /** 057 * Starts commands and blocks current thread until all processes are in state {@link State#STARTED}. 058 * @throws java.lang.IllegalArgumentException if commands list is empty 059 * @throws java.lang.IllegalStateException if already started or if at least one process failed to start. In this case 060 * all processes are terminated. No need to execute {@link #stop()} 061 */ 062 public void start(List<JavaCommand> commands) { 063 if (commands.isEmpty()) { 064 throw new IllegalArgumentException("At least one command is required"); 065 } 066 067 if (!lifecycle.tryToMoveTo(State.STARTING)) { 068 throw new IllegalStateException("Can not start multiple times"); 069 } 070 071 // intercepts CTRL-C 072 Runtime.getRuntime().addShutdownHook(shutdownHook); 073 074 for (JavaCommand command : commands) { 075 try { 076 ProcessRef processRef = launcher.launch(command); 077 monitor(processRef); 078 } catch (RuntimeException e) { 079 // fail to start or to monitor 080 stop(); 081 throw e; 082 } 083 } 084 085 if (!lifecycle.tryToMoveTo(State.STARTED)) { 086 // stopping or stopped during startup, for instance : 087 // 1. A is started 088 // 2. B starts 089 // 3. A crashes while B is starting 090 // 4. if B was not monitored during Terminator execution, then it's an alive orphan 091 stop(); 092 throw new IllegalStateException("Stopped during startup"); 093 } 094 } 095 096 private void monitor(ProcessRef processRef) { 097 // physically watch if process is alive 098 WatcherThread watcherThread = new WatcherThread(processRef, this); 099 watcherThread.start(); 100 watcherThreads.add(watcherThread); 101 102 processes.add(processRef); 103 104 // wait for process to be ready (accept requests or so on) 105 processRef.waitForReady(); 106 107 LoggerFactory.getLogger(getClass()).info(String.format("%s is up", processRef)); 108 } 109 110 /** 111 * Blocks until all processes are terminated 112 */ 113 public void awaitTermination() { 114 for (WatcherThread watcherThread : watcherThreads) { 115 while (watcherThread.isAlive()) { 116 try { 117 watcherThread.join(); 118 } catch (InterruptedException ignored) { 119 // ignore, stop blocking 120 } 121 } 122 } 123 } 124 125 /** 126 * Blocks until all processes are terminated. 127 */ 128 public void stop() { 129 stopAsync(); 130 try { 131 terminator.join(); 132 } catch (InterruptedException ignored) { 133 // stop blocking and exiting 134 } 135 // safeguard if TerminatorThread is buggy 136 lifecycle.tryToMoveTo(State.STOPPED); 137 systemExit.exit(0); 138 } 139 140 /** 141 * Asks for processes termination and returns without blocking until termination. 142 */ 143 public void stopAsync() { 144 if (lifecycle.tryToMoveTo(State.STOPPING)) { 145 terminator.setProcesses(processes); 146 terminator.start(); 147 } 148 } 149 150 public State getState() { 151 return lifecycle.getState(); 152 } 153 154 Thread getShutdownHook() { 155 return shutdownHook; 156 } 157 158 private class MonitorShutdownHook implements Runnable { 159 @Override 160 public void run() { 161 systemExit.setInShutdownHook(); 162 // blocks until everything is corrected terminated 163 stop(); 164 } 165 } 166 167 public static int getNextProcessId() { 168 if (nextProcessId >= ProcessCommands.getMaxProcesses()) { 169 throw new IllegalStateException("The maximum number of processes launched has been reached " + ProcessCommands.getMaxProcesses()); 170 } 171 return nextProcessId++; 172 } 173}