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    }