首先看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进程。