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 }