SonarQube6.2源码解析(一)

首先看sonar后台的启动进程:

[root@uranuspreweb34 logs]# ps -ef | grep java
root      3169  3167  0 Apr19 ?        00:06:04 java -Dsonar.wrapped=true -Djava.awt.headless=true -Xms8m -Xmx8m -Djava.library.path=./lib -classpath ../../lib/jsw/wrapper-3.2.3.jar:../../lib/sonar-application-6.2.jar -Dwrapper.key=t7uBnfyOYRRUy2un -Dwrapper.port=32000 -Dwrapper.jvm.port.min=31000 -Dwrapper.jvm.port.max=31999 -Dwrapper.pid=3167 -Dwrapper.version=3.2.3 -Dwrapper.native_library=wrapper -Dwrapper.service=TRUE -Dwrapper.cpu.timeout=10 -Dwrapper.jvmid=1 org.tanukisoftware.wrapper.WrapperSimpleApp org.sonar.application.App
root      3200  3169  4 Apr19 ?        06:33:32 /usr/java/jdk1.8.0_51/jre/bin/java -Djava.awt.headless=true -Xms4G -Xmx8G -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=7094 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=10.37.124.178 -Djava.io.tmpdir=/opt/CI/sonarqube-6.2/temp -javaagent:/usr/java/jdk1.8.0_51/jre/lib/management-agent.jar -cp ./lib/common/*:./lib/search/* org.sonar.search.SearchServer /opt/CI/sonarqube-6.2/temp/sq-process1495333341950424468properties
root      3351  3169  3 Apr19 ?        05:25:40 /usr/java/jdk1.8.0_51/jre/bin/java -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Djruby.management.enabled=false -Djruby.compile.invokedynamic=false -Xms4g -Xmx4g -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=7095 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=10.37.124.178 -Djava.io.tmpdir=/opt/CI/sonarqube-6.2/temp -javaagent:/usr/java/jdk1.8.0_51/jre/lib/management-agent.jar -cp ./lib/common/*:./lib/server/*:/opt/CI/sonarqube-6.2/lib/jdbc/mysql/mysql-connector-java-5.1.39.jar org.sonar.server.app.WebServer /opt/CI/sonarqube-6.2/temp/sq-process368230285702864980properties
root      3540  3169 41 Apr19 ?        2-10:22:17 /usr/java/jdk1.8.0_51/jre/bin/java -Djava.awt.headless=true -Dfile.encoding=UTF-8 -Xmx8G -Xms4G -Dcom.sun.management.jmxremote=true -Dcom.sun.management.jmxremote.port=7093 -Dcom.sun.management.jmxremote.authenticate=false -Dcom.sun.management.jmxremote.ssl=false -Djava.rmi.server.hostname=10.37.124.178 -Djava.io.tmpdir=/opt/CI/sonarqube-6.2/temp -javaagent:/usr/java/jdk1.8.0_51/jre/lib/management-agent.jar -cp ./lib/common/*:./lib/server/*:./lib/ce/*:/opt/CI/sonarqube-6.2/lib/jdbc/mysql/mysql-connector-java-5.1.39.jar org.sonar.ce.app.CeServer /opt/CI/sonarqube-6.2/temp/sq-process599434287403847159properties

从进程的启动逻辑看是 WrapperSimpleApp 进程拉起来的 web, search, ce进程

WrapperSimpleApp 对应的是sonar-qube6.2源码包中的sonar-application这个子工程,这个下面就有


main 入口是App:

/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact 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.application;

import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.apache.commons.io.FilenameUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.process.Lifecycle;
import org.sonar.process.ProcessId;
import org.sonar.process.ProcessProperties;
import org.sonar.process.Props;
import org.sonar.process.Stoppable;
import org.sonar.process.monitor.JavaCommand;
import org.sonar.process.monitor.Monitor;

import static org.sonar.process.Lifecycle.State;
import static org.sonar.process.ProcessId.APP;
import static org.sonar.process.ProcessProperties.HTTPS_PROXY_HOST;
import static org.sonar.process.ProcessProperties.HTTPS_PROXY_PORT;
import static org.sonar.process.ProcessProperties.HTTP_PROXY_HOST;
import static org.sonar.process.ProcessProperties.HTTP_PROXY_PORT;

/**
 * Entry-point of process that starts and monitors ElasticSearch, the Web Server and the Compute Engine.
 */
public class App implements Stoppable {

  /**
   * Properties about proxy that must be set as system properties
   */
  private static final String[] PROXY_PROPERTY_KEYS = new String[] {
    HTTP_PROXY_HOST,
    HTTP_PROXY_PORT,
    "http.nonProxyHosts",
    HTTPS_PROXY_HOST,
    HTTPS_PROXY_PORT,
    "http.auth.ntlm.domain",
    "socksProxyHost",
    "socksProxyPort"};

  private final Monitor monitor;

  public App(AppFileSystem appFileSystem, boolean watchForHardStop) {
    this(Monitor.create(APP.getIpcIndex(), appFileSystem, watchForHardStop, new AppLifecycleListener()));
  }

  App(Monitor monitor) {
    this.monitor = monitor;
  }

  public void start(Props props) throws InterruptedException {
    monitor.start(createCommands(props));
    monitor.awaitTermination();
  }

  private static List<JavaCommand> createCommands(Props props) {
    File homeDir = props.nonNullValueAsFile(ProcessProperties.PATH_HOME);
    List<JavaCommand> commands = new ArrayList<>(3);
    if (isProcessEnabled(props, ProcessProperties.CLUSTER_SEARCH_DISABLED)) {
      commands.add(createESCommand(props, homeDir));
    }

    if (isProcessEnabled(props, ProcessProperties.CLUSTER_WEB_DISABLED)) {
      commands.add(createWebServerCommand(props, homeDir));
    }

    if (isProcessEnabled(props, ProcessProperties.CLUSTER_CE_DISABLED)) {
      commands.add(createCeServerCommand(props, homeDir));
    }

    return commands;
  }

  private static boolean isProcessEnabled(Props props, String disabledPropertyKey) {
    return !props.valueAsBoolean(ProcessProperties.CLUSTER_ENABLED) ||
      !props.valueAsBoolean(disabledPropertyKey);
  }

  private static JavaCommand createESCommand(Props props, File homeDir) {
    return newJavaCommand(ProcessId.ELASTICSEARCH, props, homeDir)
      .addJavaOptions("-Djava.awt.headless=true")
      .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_OPTS))
      .addJavaOptions(props.nonNullValue(ProcessProperties.SEARCH_JAVA_ADDITIONAL_OPTS))
      .setClassName("org.sonar.search.SearchServer")
      .addClasspath("./lib/common/*")
      .addClasspath("./lib/search/*");
  }

  private static JavaCommand createWebServerCommand(Props props, File homeDir) {
    JavaCommand command = newJavaCommand(ProcessId.WEB_SERVER, props, homeDir)
      .addJavaOptions(ProcessProperties.WEB_ENFORCED_JVM_ARGS)
      .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_OPTS))
      .addJavaOptions(props.nonNullValue(ProcessProperties.WEB_JAVA_ADDITIONAL_OPTS))
      // required for logback tomcat valve
      .setEnvVariable(ProcessProperties.PATH_LOGS, props.nonNullValue(ProcessProperties.PATH_LOGS))
      // ensure JRuby uses SQ's temp directory as temp directory (eg. for temp files used during HTTP uploads)
      .setEnvVariable("TMPDIR", props.nonNullValue(ProcessProperties.PATH_TEMP))
      .setClassName("org.sonar.server.app.WebServer")
      .addClasspath("./lib/common/*")
      .addClasspath("./lib/server/*");
    String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
    if (driverPath != null) {
      command.addClasspath(driverPath);
    }
    return command;
  }

  private static JavaCommand createCeServerCommand(Props props, File homeDir) {
    JavaCommand command = newJavaCommand(ProcessId.COMPUTE_ENGINE, props, homeDir)
      .addJavaOptions(ProcessProperties.CE_ENFORCED_JVM_ARGS)
      .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_OPTS))
      .addJavaOptions(props.nonNullValue(ProcessProperties.CE_JAVA_ADDITIONAL_OPTS))
      .setClassName("org.sonar.ce.app.CeServer")
      .addClasspath("./lib/common/*")
      .addClasspath("./lib/server/*")
      .addClasspath("./lib/ce/*");
    String driverPath = props.value(ProcessProperties.JDBC_DRIVER_PATH);
    if (driverPath != null) {
      command.addClasspath(driverPath);
    }
    return command;
  }

  private static JavaCommand newJavaCommand(ProcessId id, Props props, File homeDir) {
    JavaCommand command = new JavaCommand(id)
      .setWorkDir(homeDir)
      .setArguments(props.rawProperties());

    for (String key : PROXY_PROPERTY_KEYS) {
      if (props.contains(key)) {
        command.addJavaOption("-D" + key + "=" + props.value(key));
      }
    }
    // defaults of HTTPS are the same than HTTP defaults
    setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_HOST, HTTP_PROXY_HOST);
    setSystemPropertyToDefaultIfNotSet(command, props, HTTPS_PROXY_PORT, HTTP_PROXY_PORT);
    return command;
  }

  private static void setSystemPropertyToDefaultIfNotSet(JavaCommand command, Props props, String httpsProperty, String httpProperty) {
    if (!props.contains(httpsProperty) && props.contains(httpProperty)) {
      command.addJavaOption("-D" + httpsProperty + "=" + props.value(httpProperty));
    }
  }

  static String starPath(File homeDir, String relativePath) {
    File dir = new File(homeDir, relativePath);
    return FilenameUtils.concat(dir.getAbsolutePath(), "*");
  }

  public static void main(String[] args) throws InterruptedException {
    CommandLineParser cli = new CommandLineParser();
    Properties rawProperties = cli.parseArguments(args);
    Props props = new PropsBuilder(rawProperties, new JdbcSettings()).build();
    AppFileSystem appFileSystem = new AppFileSystem(props);
    appFileSystem.verifyProps();
    AppLogging logging = new AppLogging();
    logging.configure(props);

    // used by orchestrator
    boolean watchForHardStop = props.valueAsBoolean(ProcessProperties.ENABLE_STOP_COMMAND, false);
    App app = new App(appFileSystem, watchForHardStop);
    app.start(props);
  }

  @Override
  public void stopAsync() {
    monitor.stop();
  }

  private static class AppLifecycleListener implements Lifecycle.LifecycleListener {
    private static final Logger LOGGER = LoggerFactory.getLogger(App.class);

    @Override
    public void successfulTransition(State from, State to) {
      if (to == State.STARTED) {
        LOGGER.info("SonarQube is up");
      }
    }
  }
}

main方法中加载properties文件然后调用start方法,(创建ce,es,web进程)然后再次根据properties创建command去启动jvm,然后再调用minor中start方法。

minor类:minor类是sonar-process-monitor模块中的类


/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact 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.monitor;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import javax.annotation.CheckForNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonar.process.DefaultProcessCommands;
import org.sonar.process.Lifecycle;
import org.sonar.process.Lifecycle.State;
import org.sonar.process.ProcessId;
import org.sonar.process.ProcessUtils;
import org.sonar.process.SystemExit;

import static org.sonar.process.DefaultProcessCommands.reset;

public class Monitor {

  private static final Logger LOG = LoggerFactory.getLogger(Monitor.class);
  private static final Timeouts TIMEOUTS = new Timeouts();
  private static final long WATCH_DELAY_MS = 500L;

  private static int restartorInstanceCounter = 0;

  private final int processNumber;
  private final FileSystem fileSystem;
  private final SystemExit systemExit;
  private final boolean watchForHardStop;
  private final Thread shutdownHook = new Thread(new MonitorShutdownHook(), "Monitor Shutdown Hook");

  private final List<WatcherThread> watcherThreads = new CopyOnWriteArrayList<>();
  private final Lifecycle lifecycle;

  private final TerminatorThread terminator = new TerminatorThread();
  private final RestartRequestWatcherThread restartWatcher = new RestartRequestWatcherThread();
  @CheckForNull
  private List<JavaCommand> javaCommands;
  @CheckForNull
  private JavaProcessLauncher launcher;
  @CheckForNull
  private RestartorThread restartor;
  @CheckForNull
  HardStopWatcherThread hardStopWatcher;

  Monitor(int processNumber, FileSystem fileSystem, SystemExit exit, boolean watchForHardStop, Lifecycle.LifecycleListener... listeners) {
    this.processNumber = processNumber;
    this.fileSystem = fileSystem;
    this.systemExit = exit;
    this.watchForHardStop = watchForHardStop;
    this.lifecycle = new Lifecycle(listeners);
  }

  public static Monitor create(int processNumber, FileSystem fileSystem, boolean watchForHardStop) {
    return new Monitor(processNumber, fileSystem, new SystemExit(), watchForHardStop);
  }

  public static Monitor create(int processNumber, FileSystem fileSystem, boolean watchForHardStop, Lifecycle.LifecycleListener listener) {
    return new Monitor(processNumber, fileSystem, new SystemExit(), watchForHardStop, Objects.requireNonNull(listener));
  }

  /**
   * Starts commands and blocks current thread until all processes are in state {@link State#STARTED}.
   * @throws java.lang.IllegalArgumentException if commands list is empty
   * @throws java.lang.IllegalStateException if already started or if at least one process failed to start. In this case
   *   all processes are terminated. No need to execute {@link #stop()}
   */
  public void start(List<JavaCommand> commands) throws InterruptedException {
    if (commands.isEmpty()) {
      throw new IllegalArgumentException("At least one command is required");
    }

    if (lifecycle.getState() != State.INIT) {
      throw new IllegalStateException("Can not start multiple times");
    }

    // intercepts CTRL-C
    Runtime.getRuntime().addShutdownHook(shutdownHook);

    // start watching for restart requested by child process
    restartWatcher.start();

    javaCommands = commands;
    startProcesses();
  }

  private void startProcesses() throws InterruptedException {
    // do no start any child process if not in state INIT or RESTARTING (a stop could be in progress too)
    if (lifecycle.tryToMoveTo(State.STARTING)) {
      resetFileSystem();

      // start watching for stop requested by other process (eg. orchestrator) if enabled and not started yet
      if (watchForHardStop && hardStopWatcher == null) {
        hardStopWatcher = new HardStopWatcherThread();
        hardStopWatcher.start();
      }

      startAndMonitorProcesses();
      stopIfAnyProcessDidNotStart();
    }
  }

  private void resetFileSystem() {
    // since JavaLauncher depends on temp directory, which is reset below, we need to close it first
    closeJavaLauncher();
    try {
      fileSystem.reset();
    } catch (IOException e) {
      // failed to reset FileSystem
      throw new RuntimeException("Failed to reset file system", e);
    }
    // reset sharedmemory of App
    reset(fileSystem.getTempDir(), ProcessId.APP.getIpcIndex());
  }

  private void closeJavaLauncher() {
    if (this.launcher != null) {
      this.launcher.close();
      this.launcher = null;
    }
  }

  private void startAndMonitorProcesses() throws InterruptedException{
    File tempDir = fileSystem.getTempDir();
    this.launcher = new JavaProcessLauncher(TIMEOUTS, tempDir);
    for (JavaCommand command : javaCommands) {
      ProcessRef processRef = null;
      try {
        processRef = launcher.launch(command);
        monitor(processRef);
      } catch (InterruptedException | RuntimeException e) {
        if (processRef != null) {
          LOG.error("{} failed to start", processRef);
        }
        // fail to start or to monitor
        stop();
        throw e;
      }
    }
  }

  private void monitor(ProcessRef processRef) throws InterruptedException {
    // physically watch if process is alive
    WatcherThread watcherThread = new WatcherThread(processRef, this);
    watcherThread.start();
    watcherThreads.add(watcherThread);

    // wait for process to be ready (accept requests or so on)
    processRef.waitForUp();

    LOG.info("{} is up", processRef);
  }

  private void stopIfAnyProcessDidNotStart() {
    if (!lifecycle.tryToMoveTo(State.STARTED)) {
      // stopping or stopped during startup, for instance :
      // 1. A is started
      // 2. B starts
      // 3. A crashes while B is starting
      // 4. if B was not monitored during Terminator execution, then it's an alive orphan
      stop();
      throw new IllegalStateException("Stopped during startup");
    }
  }

  /**
   * Blocks until all processes are terminated
   */
  public void awaitTermination() {
    while (awaitChildProcessesTermination()) {
      trace("await termination of restartor...");
      ProcessUtils.awaitTermination(restartor);
    }
    cleanAfterTermination();
  }

  boolean waitForOneRestart() {
    boolean restartRequested = awaitChildProcessesTermination();
    trace("finished waiting, restartRequested={}", restartRequested);
    if (restartRequested) {
      trace("awaitTermination restartor={}", restartor);
      ProcessUtils.awaitTermination(restartor);
    }
    return restartRequested;
  }

  private boolean awaitChildProcessesTermination() {
    trace("await termination of child processes...");
    List<WatcherThread> watcherThreadsCopy = new ArrayList<>(this.watcherThreads);
    for (WatcherThread watcherThread : watcherThreadsCopy) {
      ProcessUtils.awaitTermination(watcherThread);
    }
    trace("all child processes done");
    return hasRestartBeenRequested(watcherThreadsCopy);
  }

  private static boolean hasRestartBeenRequested(List<WatcherThread> watcherThreads) {
    for (WatcherThread watcherThread : watcherThreads) {
      if (watcherThread.isAskedForRestart()) {
        trace("one child process requested restart");
        return true;
      }
    }
    trace("no child process requested restart");
    return false;
  }

  /**
   * Blocks until all processes are terminated.
   */
  public void stop() {
    trace("start hard stop async...");
    stopAsync(State.HARD_STOPPING);
    trace("await termination of terminator...");
    ProcessUtils.awaitTermination(terminator);
    cleanAfterTermination();
    trace("exit...");
    systemExit.exit(0);
  }

  private void cleanAfterTermination() {
    trace("go to STOPPED...");
    if (lifecycle.tryToMoveTo(State.STOPPED)) {
      trace("await termination of restartWatcher and hardStopWatcher...");
      // wait for restartWatcher and hardStopWatcher to cleanly stop
      ProcessUtils.awaitTermination(restartWatcher, hardStopWatcher);
      trace("restartWatcher done");
      // removing shutdown hook to avoid called stop() unnecessarily unless already in shutdownHook
      if (!systemExit.isInShutdownHook()) {
        trace("removing shutdown hook...");
        Runtime.getRuntime().removeShutdownHook(shutdownHook);
      }
      // cleanly close JavaLauncher
      closeJavaLauncher();
    }
  }

  /**
   * Asks for processes termination and returns without blocking until termination.
   * However, if a termination request is already under way (it's not supposed to happen, but, technically, it can occur),
   * this call will be blocking until the previous request finishes.
   */
  public void stopAsync() {
    stopAsync(State.STOPPING);
  }

  private void stopAsync(State stoppingState) {
    assert stoppingState == State.STOPPING || stoppingState == State.HARD_STOPPING;
    if (lifecycle.tryToMoveTo(stoppingState)) {
      terminator.start();
    }
  }

  public void restartAsync() {
    if (lifecycle.tryToMoveTo(State.RESTARTING)) {
      restartor = new RestartorThread();
      restartor.start();
    }
  }

  /**
   * Runs every time a restart request is detected.
   */
  private class RestartorThread extends Thread {

    private RestartorThread() {
      super("Restartor " + (restartorInstanceCounter++));
    }

    @Override
    public void run() {
      stopProcesses();
      try {
        startProcesses();
      } catch (InterruptedException e) {
        // Startup was interrupted. Processes are being stopped asynchronously.
        // Restoring the interruption state.
        Thread.currentThread().interrupt();
      }
    }
  }

  /**
   * Runs only once
   */
  private class TerminatorThread extends Thread {

    private TerminatorThread() {
      super("Terminator");
    }

    @Override
    public void run() {
      stopProcesses();
    }
  }

  /**
   * Watches for any child process requesting a restart of all children processes.
   * It runs once and as long as {@link #lifecycle} hasn't reached {@link Lifecycle.State#STOPPED} and holds its checks
   * when {@link #lifecycle} is not in state {@link Lifecycle.State#STARTED} to avoid taking the same request into account
   * twice.
   */
  public class RestartRequestWatcherThread extends Thread {
    public RestartRequestWatcherThread() {
      super("Restart watcher");
    }

    @Override
    public void run() {
      while (lifecycle.getState() != Lifecycle.State.STOPPED) {
        if (lifecycle.getState() == Lifecycle.State.STARTED && didAnyProcessRequestRestart()) {
          restartAsync();
        }
        try {
          Thread.sleep(WATCH_DELAY_MS);
        } catch (InterruptedException ignored) {
          // keep watching
        }
      }
    }

    private boolean didAnyProcessRequestRestart() {
      for (WatcherThread watcherThread : watcherThreads) {
        ProcessRef processRef = watcherThread.getProcessRef();
        if (processRef.getCommands().askedForRestart()) {
          LOG.info("Process [{}] requested restart", processRef.getKey());
          return true;
        }
      }
      return false;
    }

  }

  public class HardStopWatcherThread extends Thread {

    public HardStopWatcherThread() {
      super("Hard stop watcher");
    }

    @Override
    public void run() {
      while (lifecycle.getState() != Lifecycle.State.STOPPED) {
        if (askedForStop()) {
          trace("Stopping process");
          Monitor.this.stop();
        } else {
          delay();
        }
      }
    }

    private boolean askedForStop() {
      File tempDir = fileSystem.getTempDir();
      try (DefaultProcessCommands processCommands = DefaultProcessCommands.secondary(tempDir, processNumber)) {
        if (processCommands.askedForStop()) {
          return true;
        }
      }
      return false;
    }

    private void delay() {
      try {
        Thread.sleep(WATCH_DELAY_MS);
      } catch (InterruptedException ignored) {
        // keep watching
      }
    }

  }

  private void stopProcesses() {
    List<WatcherThread> watcherThreadsCopy = new ArrayList<>(this.watcherThreads);
    // create a copy and reverse it to terminate in reverse order of startup (dependency order)
    Collections.reverse(watcherThreadsCopy);

    for (WatcherThread watcherThread : watcherThreadsCopy) {
      ProcessRef ref = watcherThread.getProcessRef();
      if (!ref.isStopped()) {
        LOG.info("{} is stopping", ref);
        ref.askForGracefulAsyncStop();

        long killAt = System.currentTimeMillis() + TIMEOUTS.getTerminationTimeout();
        while (!ref.isStopped() && System.currentTimeMillis() < killAt) {
          try {
            Thread.sleep(10L);
          } catch (InterruptedException e) {
            // stop asking for graceful stops, Monitor will hardly kill all processes
            break;
          }
        }
        if (!ref.isStopped()) {
          LOG.info("{} failed to stop in a timely fashion. Killing it.", ref);
        }
        ref.stop();
        LOG.info("{} is stopped", ref);
      }
    }

    // all processes are stopped, no need to keep references to these WatcherThread anymore
    trace("all processes stopped, clean list of watcherThreads...");
    this.watcherThreads.clear();
  }

  public State getState() {
    return lifecycle.getState();
  }

  Thread getShutdownHook() {
    return shutdownHook;
  }

  private class MonitorShutdownHook implements Runnable {
    @Override
    public void run() {
      systemExit.setInShutdownHook();
      trace("calling stop from MonitorShutdownHook...");
      // blocks until everything is corrected terminated
      stop();
    }
  }

  private static void trace(String s) {
    LOG.trace(s);
  }

  private static void trace(String s, Object args) {
    LOG.trace(s, args);
  }

}

这个类中最为关键的就是startAndMonitorProcesses方法,然后调用JavaProcessLauncher lauccher方法,同时将对应的command转换ProcessRef bean对象。

 private void startAndMonitorProcesses() throws InterruptedException{
    File tempDir = fileSystem.getTempDir();
    this.launcher = new JavaProcessLauncher(TIMEOUTS, tempDir);
    for (JavaCommand command : javaCommands) {
      ProcessRef processRef = null;
      try {
        processRef = launcher.launch(command);
        monitor(processRef);
      } catch (InterruptedException | RuntimeException e) {
        if (processRef != null) {
          LOG.error("{} failed to start", processRef);
        }
        // fail to start or to monitor
        stop();
        throw e;
      }
    }
  }
/*
 * SonarQube
 * Copyright (C) 2009-2016 SonarSource SA
 * mailto:contact 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.monitor;

import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;
import org.apache.commons.lang.StringUtils;
import org.slf4j.LoggerFactory;
import org.sonar.process.AllProcessesCommands;
import org.sonar.process.ProcessCommands;
import org.sonar.process.ProcessEntryPoint;
import org.sonar.process.ProcessUtils;

import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_INDEX;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_PROCESS_KEY;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_SHARED_PATH;
import static org.sonar.process.ProcessEntryPoint.PROPERTY_TERMINATION_TIMEOUT;

class JavaProcessLauncher {

  private final Timeouts timeouts;
  private final File tempDir;
  private final AllProcessesCommands allProcessesCommands;

  JavaProcessLauncher(Timeouts timeouts, File tempDir) {
    this.timeouts = timeouts;
    this.tempDir = tempDir;
    this.allProcessesCommands = new AllProcessesCommands(tempDir);
  }

  public void close() {
    allProcessesCommands.close();
  }

  ProcessRef launch(JavaCommand command) {
    Process process = null;
    try {
      ProcessCommands commands = allProcessesCommands.createAfterClean(command.getProcessId().getIpcIndex());

      ProcessBuilder processBuilder = create(command);
      LoggerFactory.getLogger(getClass()).info("Launch process[{}]: {}",
        command.getProcessId().getKey(), StringUtils.join(processBuilder.command(), " "));
      process = processBuilder.start();
      StreamGobbler inputGobbler = new StreamGobbler(process.getInputStream(), command.getProcessId().getKey());
      inputGobbler.start();

      return new ProcessRef(command.getProcessId().getKey(), commands, process, inputGobbler);

    } catch (Exception e) {
      // just in case
      ProcessUtils.sendKillSignal(process);
      throw new IllegalStateException("Fail to launch [" + command.getProcessId().getKey() + "]", e);
    }
  }

  private ProcessBuilder create(JavaCommand javaCommand) {
    List<String> commands = new ArrayList<>();
    commands.add(buildJavaPath());
    commands.addAll(javaCommand.getJavaOptions());
    // TODO warning - does it work if temp dir contains a whitespace ?
    commands.add(String.format("-Djava.io.tmpdir=%s", tempDir.getAbsolutePath()));
    commands.add(getJmxAgentCommand());
    commands.addAll(buildClasspath(javaCommand));
    commands.add(javaCommand.getClassName());
    commands.add(buildPropertiesFile(javaCommand).getAbsolutePath());

    ProcessBuilder processBuilder = new ProcessBuilder();
    processBuilder.command(commands);
    processBuilder.directory(javaCommand.getWorkDir());
    processBuilder.environment().putAll(javaCommand.getEnvVariables());
    processBuilder.redirectErrorStream(true);
    return processBuilder;
  }

  /**
   * JVM option to enable the agent that allows inter-process communication through JMX without
   * opening new ports. The agent is available in JRE of OpenJDK/OracleJDK only.
   * @see ProcessEntryPoint
   */
  private static String getJmxAgentCommand() {
    return "-javaagent:" + System.getProperty("java.home") + File.separator + "lib" + File.separator + "management-agent.jar";
  }

  private String buildJavaPath() {
    String separator = System.getProperty("file.separator");
    return new File(new File(System.getProperty("java.home")), "bin" + separator + "java").getAbsolutePath();
  }

  private List<String> buildClasspath(JavaCommand javaCommand) {
    return Arrays.asList("-cp", StringUtils.join(javaCommand.getClasspath(), System.getProperty("path.separator")));
  }

  private File buildPropertiesFile(JavaCommand javaCommand) {
    File propertiesFile = null;
    try {
      propertiesFile = File.createTempFile("sq-process", "properties", tempDir);
      Properties props = new Properties();
      props.putAll(javaCommand.getArguments());
      props.setProperty(PROPERTY_PROCESS_KEY, javaCommand.getProcessId().getKey());
      props.setProperty(PROPERTY_PROCESS_INDEX, Integer.toString(javaCommand.getProcessId().getIpcIndex()));
      props.setProperty(PROPERTY_TERMINATION_TIMEOUT, String.valueOf(timeouts.getTerminationTimeout()));
      props.setProperty(PROPERTY_SHARED_PATH, tempDir.getAbsolutePath());
      try (OutputStream out = new FileOutputStream(propertiesFile)) {
        props.store(out, String.format("Temporary properties file for command [%s]", javaCommand.getProcessId().getKey()));
      }
      return propertiesFile;
    } catch (Exception e) {
      throw new IllegalStateException("Cannot write temporary settings to " + propertiesFile, e);
    }
  }
}

这个类调用jdk中ProcessBuilder拉起的jvm进程。




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值