mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
ExternalJavacManager support 'keep process alive' mode to reuse launched process for several compile sessions
This commit is contained in:
@@ -4,6 +4,8 @@ package com.intellij.compiler;
|
||||
import com.intellij.codeInspection.InspectionManager;
|
||||
import com.intellij.compiler.impl.*;
|
||||
import com.intellij.compiler.server.BuildManager;
|
||||
import com.intellij.execution.process.ProcessIOExecutorService;
|
||||
import com.intellij.ide.IdeEventQueue;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.compiler.Compiler;
|
||||
@@ -20,6 +22,7 @@ import com.intellij.openapi.projectRoots.*;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
|
||||
@@ -36,12 +39,10 @@ import org.jetbrains.annotations.TestOnly;
|
||||
import org.jetbrains.jps.api.CanceledStatus;
|
||||
import org.jetbrains.jps.builders.impl.java.JavacCompilerTool;
|
||||
import org.jetbrains.jps.incremental.BinaryContent;
|
||||
import org.jetbrains.jps.javac.DiagnosticOutputConsumer;
|
||||
import org.jetbrains.jps.javac.ExternalJavacManager;
|
||||
import org.jetbrains.jps.javac.OutputFileConsumer;
|
||||
import org.jetbrains.jps.javac.OutputFileObject;
|
||||
import org.jetbrains.jps.javac.*;
|
||||
|
||||
import javax.tools.*;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
@@ -52,6 +53,7 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class CompilerManagerImpl extends CompilerManager {
|
||||
private static final Logger LOG = Logger.getInstance("#com.intellij.compiler.CompilerManagerImpl");
|
||||
private static final int IDLE_PROCESSES_CHECK_PERIOD = 10000; // check idle javac processes every 10 second when IDE is idle
|
||||
|
||||
private final Project myProject;
|
||||
|
||||
@@ -367,7 +369,6 @@ public class CompilerManagerImpl extends CompilerManager {
|
||||
Collection<File> sourcePath,
|
||||
Collection<File> files,
|
||||
File outputDir) throws IOException, CompilationException {
|
||||
|
||||
final Pair<Sdk, JavaSdkVersion> runtime = BuildManager.getJavacRuntimeSdk(myProject);
|
||||
|
||||
final Sdk sdk = runtime.getFirst();
|
||||
@@ -407,10 +408,12 @@ public class CompilerManagerImpl extends CompilerManager {
|
||||
final Map<File, Set<File>> outs = Collections.singletonMap(outputDir, sourceRoots);
|
||||
|
||||
final ExternalJavacManager javacManager = getJavacManager();
|
||||
final CompilationPaths paths = CompilationPaths.create(platformCp, classpath, upgradeModulePath, modulePath, sourcePath);
|
||||
// do not keep process alive in tests since every test expects all spawned processes to terminate in teardown
|
||||
boolean compiledOk = javacManager != null && javacManager.forkJavac(
|
||||
javaHome, -1, Collections.emptyList(), options, platformCp, classpath, upgradeModulePath, modulePath, sourcePath, files, outs, diagnostic, outputCollector,
|
||||
new JavacCompilerTool(), CanceledStatus.NULL
|
||||
);
|
||||
javaHome, -1, Collections.emptyList(), options, paths, files, outs, diagnostic, outputCollector,
|
||||
new JavacCompilerTool(), CanceledStatus.NULL, !ApplicationManager.getApplication().isUnitTestMode()
|
||||
).get();
|
||||
|
||||
if (!compiledOk) {
|
||||
final List<CompilationException.Message> messages = new SmartList<>();
|
||||
@@ -460,9 +463,26 @@ public class CompilerManagerImpl extends CompilerManager {
|
||||
return null; // should not happen for real projects
|
||||
}
|
||||
final int listenPort = NetUtils.findAvailableSocketPort();
|
||||
manager = new ExternalJavacManager(compilerWorkingDir);
|
||||
manager = new ExternalJavacManager(
|
||||
compilerWorkingDir, ProcessIOExecutorService.INSTANCE, Registry.intValue("compiler.external.javac.keep.alive.timeout", 5*60*1000)
|
||||
);
|
||||
manager.start(listenPort);
|
||||
myExternalJavacManager = manager;
|
||||
IdeEventQueue.getInstance().addIdleListener(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final ExternalJavacManager manager;
|
||||
synchronized (CompilerManagerImpl.this) {
|
||||
manager = myExternalJavacManager;
|
||||
}
|
||||
if (manager != null) {
|
||||
manager.shutdownIdleProcesses();
|
||||
}
|
||||
else {
|
||||
IdeEventQueue.getInstance().removeIdleListener(this);
|
||||
}
|
||||
}
|
||||
}, IDLE_PROCESSES_CHECK_PERIOD);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@ import com.google.protobuf.MessageLite;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import org.jetbrains.jps.incremental.BinaryContent;
|
||||
|
||||
import javax.tools.*;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.File;
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
@@ -44,6 +45,10 @@ public class ExternalJavacMessageHandler {
|
||||
myEncodingName = encodingName;
|
||||
}
|
||||
|
||||
public DiagnosticOutputConsumer getDiagnosticSink() {
|
||||
return myDiagnosticSink;
|
||||
}
|
||||
|
||||
public boolean handleMessage(MessageLite message) {
|
||||
try {
|
||||
final JavacRemoteProto.Message msg = (JavacRemoteProto.Message)message;
|
||||
|
||||
@@ -19,12 +19,11 @@ import org.jetbrains.jps.api.CanceledStatus;
|
||||
import org.jetbrains.jps.builders.impl.java.JavacCompilerTool;
|
||||
import org.jetbrains.jps.builders.java.JavaCompilingTool;
|
||||
|
||||
import javax.tools.*;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.File;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
* @author Eugene Zhuravlev
|
||||
@@ -33,8 +32,9 @@ public class ExternalJavacProcess {
|
||||
public static final String JPS_JAVA_COMPILING_TOOL_PROPERTY = "jps.java.compiling.tool";
|
||||
private final ChannelInitializer myChannelInitializer;
|
||||
private final EventLoopGroup myEventLoopGroup;
|
||||
private final boolean myKeepRunning;
|
||||
private volatile ChannelFuture myConnectFuture;
|
||||
private volatile CancelHandler myCancelHandler;
|
||||
private final ConcurrentMap<UUID, Boolean> myCanceled = new ConcurrentHashMap<UUID, Boolean>();
|
||||
private final ExecutorService myThreadPool = Executors.newCachedThreadPool();
|
||||
|
||||
static {
|
||||
@@ -46,7 +46,8 @@ public class ExternalJavacProcess {
|
||||
InternalLoggerFactory.setDefaultFactory(new Log4JLoggerFactory());
|
||||
}
|
||||
|
||||
public ExternalJavacProcess() {
|
||||
public ExternalJavacProcess(boolean keepRunning) {
|
||||
myKeepRunning = keepRunning;
|
||||
final JavacRemoteProto.Message msgDefaultInstance = JavacRemoteProto.Message.getDefaultInstance();
|
||||
|
||||
myEventLoopGroup = new NioEventLoopGroup(1, myThreadPool);
|
||||
@@ -65,12 +66,16 @@ public class ExternalJavacProcess {
|
||||
|
||||
//static volatile long myGlobalStart;
|
||||
|
||||
/**
|
||||
* @param args: SessionUUID, host, port,
|
||||
*/
|
||||
public static void main(String[] args) {
|
||||
//myGlobalStart = System.currentTimeMillis();
|
||||
UUID uuid = null;
|
||||
String host = null;
|
||||
int port = -1;
|
||||
if (args.length > 0) {
|
||||
boolean keepRunning = false; // keep running after compilation ends until explicit shutdown
|
||||
if (args.length >= 3) {
|
||||
try {
|
||||
uuid = UUID.fromString(args[0]);
|
||||
}
|
||||
@@ -88,13 +93,17 @@ public class ExternalJavacProcess {
|
||||
System.err.println("Error parsing port: " + e.getMessage());
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
if (args.length > 3) {
|
||||
keepRunning = Boolean.valueOf(args[3]);
|
||||
}
|
||||
}
|
||||
else {
|
||||
System.err.println("Insufficient parameters");
|
||||
System.err.println("Insufficient number of parameters");
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
final ExternalJavacProcess process = new ExternalJavacProcess();
|
||||
final ExternalJavacProcess process = new ExternalJavacProcess(keepRunning);
|
||||
try {
|
||||
//final long connectStart = System.currentTimeMillis();
|
||||
if (process.connect(host, port)) {
|
||||
@@ -137,7 +146,7 @@ public class ExternalJavacProcess {
|
||||
Collection<File> sourcePath,
|
||||
Map<File, Set<File>> outs,
|
||||
final CanceledStatus canceledStatus) {
|
||||
//final long compileStart = System.currentTimeMillis();
|
||||
final long compileStart = System.currentTimeMillis();
|
||||
//System.err.println("Compile start; since global start: " + (compileStart - myGlobalStart));
|
||||
final DiagnosticOutputConsumer diagnostic = new DiagnosticOutputConsumer() {
|
||||
@Override
|
||||
@@ -188,10 +197,11 @@ public class ExternalJavacProcess {
|
||||
e.printStackTrace(System.err);
|
||||
return JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure(e.getMessage(), e));
|
||||
}
|
||||
//finally {
|
||||
// final long compileEnd = System.currentTimeMillis();
|
||||
// System.err.println("Compiled in " + (compileEnd - compileStart) + " ms; since global start: " + (compileEnd - myGlobalStart));
|
||||
//}
|
||||
finally {
|
||||
final long compileEnd = System.currentTimeMillis();
|
||||
System.err.println("Compiled in " + (compileEnd - compileStart) + " ms");
|
||||
//System.err.println("Compiled in " + (compileEnd - compileStart) + " ms; since global start: " + (compileEnd - myGlobalStart));
|
||||
}
|
||||
}
|
||||
|
||||
private static JavaCompilingTool getCompilingTool() {
|
||||
@@ -221,7 +231,7 @@ public class ExternalJavacProcess {
|
||||
final JavacRemoteProto.Message.Request request = message.getRequest();
|
||||
final JavacRemoteProto.Message.Request.Type requestType = request.getRequestType();
|
||||
if (requestType == JavacRemoteProto.Message.Request.Type.COMPILE) {
|
||||
if (myCancelHandler == null) { // if not running yet
|
||||
if (myCanceled.putIfAbsent(sessionId, Boolean.FALSE) == null) { // if not running yet
|
||||
final List<String> options = request.getOptionList();
|
||||
final List<File> files = toFiles(request.getFileList());
|
||||
final List<File> cp = toFiles(request.getClasspathList());
|
||||
@@ -238,20 +248,23 @@ public class ExternalJavacProcess {
|
||||
}
|
||||
outs.put(new File(outputGroup.getOutputRoot()), srcRoots);
|
||||
}
|
||||
|
||||
final CancelHandler cancelHandler = new CancelHandler();
|
||||
myCancelHandler = cancelHandler;
|
||||
myThreadPool.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
context.channel().writeAndFlush(
|
||||
compile(context, sessionId, options, files, cp, platformCp, modulePath, upgradeModulePath, srcPath, outs, cancelHandler)
|
||||
).awaitUninterruptibly();
|
||||
final JavacRemoteProto.Message result = compile(context, sessionId, options, files, cp, platformCp, modulePath, upgradeModulePath, srcPath, outs, new CanceledStatus() {
|
||||
@Override
|
||||
public boolean isCanceled() {
|
||||
return Boolean.TRUE.equals(myCanceled.get(sessionId));
|
||||
}
|
||||
});
|
||||
context.channel().writeAndFlush(result).awaitUninterruptibly();
|
||||
}
|
||||
finally {
|
||||
myCancelHandler = null;
|
||||
ExternalJavacProcess.this.stop();
|
||||
myCanceled.remove(sessionId); // state cleanup
|
||||
if (!myKeepRunning) { // todo: also check that no other process is running
|
||||
ExternalJavacProcess.this.stop();
|
||||
}
|
||||
Thread.interrupted(); // reset interrupted status
|
||||
}
|
||||
}
|
||||
@@ -259,10 +272,15 @@ public class ExternalJavacProcess {
|
||||
}
|
||||
}
|
||||
else if (requestType == JavacRemoteProto.Message.Request.Type.CANCEL){
|
||||
cancelBuild();
|
||||
cancelBuild(sessionId);
|
||||
}
|
||||
else if (requestType == JavacRemoteProto.Message.Request.Type.SHUTDOWN){
|
||||
cancelBuild();
|
||||
// cancel all running builds
|
||||
// todo: optionally wait for all builds to complete and only then shutdown
|
||||
for (UUID uuid : myCanceled.keySet()) {
|
||||
// todo: do we really need to wait for cancelled sessions to terminate?
|
||||
cancelBuild(uuid);
|
||||
}
|
||||
new Thread("StopThread") {
|
||||
@Override
|
||||
public void run() {
|
||||
@@ -313,26 +331,8 @@ public class ExternalJavacProcess {
|
||||
return files;
|
||||
}
|
||||
|
||||
public void cancelBuild() {
|
||||
final CancelHandler cancelHandler = myCancelHandler;
|
||||
if (cancelHandler != null) {
|
||||
cancelHandler.cancel();
|
||||
}
|
||||
public void cancelBuild(UUID sessionId) {
|
||||
myCanceled.replace(sessionId, Boolean.FALSE, Boolean.TRUE);
|
||||
}
|
||||
|
||||
private static class CancelHandler implements CanceledStatus {
|
||||
private volatile boolean myIsCanceled = false;
|
||||
|
||||
private CancelHandler() {
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
myIsCanceled = true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCanceled() {
|
||||
return myIsCanceled;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,7 +55,8 @@ import org.jetbrains.jps.model.serialization.PathMacroUtil;
|
||||
import org.jetbrains.jps.service.JpsServiceManager;
|
||||
import org.jetbrains.jps.service.SharedThreadPool;
|
||||
|
||||
import javax.tools.*;
|
||||
import javax.tools.Diagnostic;
|
||||
import javax.tools.JavaFileObject;
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.io.IOException;
|
||||
@@ -452,12 +453,11 @@ public class JavaBuilder extends ModuleLevelBuilder {
|
||||
else {
|
||||
updateCompilerUsageStatistics(context, "javac " + forkSdk.getSecond(), chunk);
|
||||
final ExternalJavacManager server = ensureJavacServerStarted(context);
|
||||
final CompilationPaths paths = CompilationPaths.create(platformCp, classPath, upgradeModulePath, modulePath, sourcePath);
|
||||
rc = server.forkJavac(
|
||||
forkSdk.getFirst(),
|
||||
Utils.suggestForkedCompilerHeapSize(),
|
||||
vmOptions, options, platformCp, classPath, upgradeModulePath, modulePath, sourcePath,
|
||||
files, outs, diagnosticSink, classesConsumer, compilingTool, context.getCancelStatus()
|
||||
);
|
||||
forkSdk.getFirst(), Utils.suggestForkedCompilerHeapSize(),
|
||||
vmOptions, options, paths, files, outs, diagnosticSink, classesConsumer, compilingTool, context.getCancelStatus(), false
|
||||
).get();
|
||||
}
|
||||
return rc;
|
||||
}
|
||||
@@ -671,13 +671,13 @@ public class JavaBuilder extends ModuleLevelBuilder {
|
||||
return server;
|
||||
}
|
||||
final int listenPort = findFreePort();
|
||||
server = new ExternalJavacManager(Utils.getSystemRoot()) {
|
||||
server = new ExternalJavacManager(Utils.getSystemRoot(), SharedThreadPool.getInstance()) {
|
||||
@Override
|
||||
protected ExternalJavacProcessHandler createProcessHandler(@NotNull Process process, @NotNull String commandLine) {
|
||||
return new ExternalJavacProcessHandler(process, commandLine) {
|
||||
@Override
|
||||
protected ExternalJavacProcessHandler createProcessHandler(UUID processId, @NotNull Process process, @NotNull String commandLine, boolean keepProcessAlive) {
|
||||
return new ExternalJavacProcessHandler(processId, process, commandLine, keepProcessAlive) {
|
||||
@NotNull
|
||||
protected Future<?> executeOnPooledThread(@NotNull Runnable task) {
|
||||
@Override
|
||||
public Future<?> executeTask(@NotNull Runnable task) {
|
||||
return SharedThreadPool.getInstance().executeOnPooledThread(task);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.jps.javac;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
/**
|
||||
* @author Eugene Zhuravlev
|
||||
* Date: 14-Nov-18
|
||||
*/
|
||||
public class CompilationPaths {
|
||||
private final Collection<File> myPlatformClasspath;
|
||||
private final Collection<File> myClasspath;
|
||||
private final Collection<File> myUpgradeModulePath;
|
||||
private final Collection<File> myModulePath;
|
||||
private final Collection<File> mySourcePath;
|
||||
|
||||
public CompilationPaths(Collection<File> platformClasspath, Collection<File> classpath, Collection<File> upgradeModulePath, Collection<File> modulePath, Collection<File> sourcePath) {
|
||||
myPlatformClasspath = constCollection(platformClasspath);
|
||||
myClasspath = constCollection(classpath);
|
||||
myUpgradeModulePath = constCollection(upgradeModulePath);
|
||||
myModulePath = constCollection(modulePath);
|
||||
mySourcePath = constCollection(sourcePath);
|
||||
}
|
||||
|
||||
private static <T> Collection<T> constCollection(Collection<T> col) {
|
||||
return col == null || col.isEmpty()? Collections.emptyList() : Collections.unmodifiableCollection(col);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<File> getPlatformClasspath() {
|
||||
return myPlatformClasspath;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<File> getClasspath() {
|
||||
return myClasspath;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<File> getUpgradeModulePath() {
|
||||
return myUpgradeModulePath;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<File> getModulePath() {
|
||||
return myModulePath;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public Collection<File> getSourcePath() {
|
||||
return mySourcePath;
|
||||
}
|
||||
|
||||
public interface Builder {
|
||||
CompilationPaths create();
|
||||
|
||||
Builder setPlatformClasspath(Collection<File> path);
|
||||
Builder setClasspath(Collection<File> path);
|
||||
Builder setUpgradeModulePath(Collection<File> path);
|
||||
Builder setModulePath(Collection<File> path);
|
||||
Builder setSourcePath(Collection<File> path);
|
||||
}
|
||||
|
||||
public static CompilationPaths create(@Nullable Collection<File> platformCp,
|
||||
@Nullable Collection<File> cp,
|
||||
@Nullable Collection<File> upgradeModCp,
|
||||
@Nullable Collection<File> modulePath,
|
||||
@Nullable Collection<File> sourcePath) {
|
||||
return new CompilationPaths(platformCp, cp, upgradeModCp, modulePath, sourcePath);
|
||||
}
|
||||
|
||||
public static Builder builder() {
|
||||
return new Builder() {
|
||||
private Collection<File> mySourcePath;
|
||||
private Collection<File> myModulePath;
|
||||
private Collection<File> myUpgradeModulePath;
|
||||
private Collection<File> myClasspath;
|
||||
private Collection<File> myPlatformCp;
|
||||
|
||||
@Override
|
||||
public CompilationPaths create() {
|
||||
return CompilationPaths.create(myPlatformCp, myClasspath, myUpgradeModulePath, myModulePath, mySourcePath);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setPlatformClasspath(Collection<File> path) {
|
||||
myPlatformCp = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setClasspath(Collection<File> path) {
|
||||
myClasspath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setUpgradeModulePath(Collection<File> path) {
|
||||
myUpgradeModulePath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setModulePath(Collection<File> path) {
|
||||
myModulePath = path;
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Builder setSourcePath(Collection<File> path) {
|
||||
mySourcePath = path;
|
||||
return this;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -9,7 +9,7 @@ import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.concurrency.Semaphore;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.io.BaseOutputReader;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.group.ChannelGroup;
|
||||
@@ -31,19 +31,21 @@ import org.jetbrains.jps.builders.java.JavaCompilingTool;
|
||||
import org.jetbrains.jps.cmdline.ClasspathBootstrap;
|
||||
import org.jetbrains.jps.incremental.GlobalContextKey;
|
||||
|
||||
import javax.tools.*;
|
||||
import javax.tools.Diagnostic;
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* @author Eugene Zhuravlev
|
||||
*/
|
||||
public class ExternalJavacManager {
|
||||
public class ExternalJavacManager extends ProcessAdapter {
|
||||
private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.javac.ExternalJavacServer");
|
||||
|
||||
public static final GlobalContextKey<ExternalJavacManager> KEY = GlobalContextKey.create("_external_javac_server_");
|
||||
@@ -51,18 +53,37 @@ public class ExternalJavacManager {
|
||||
public static final String STDOUT_LINE_PREFIX = "JAVAC_PROCESS[STDOUT]";
|
||||
public static final String STDERR_LINE_PREFIX = "JAVAC_PROCESS[STDERR]";
|
||||
|
||||
private static final AttributeKey<JavacProcessDescriptor> SESSION_DESCRIPTOR = AttributeKey.valueOf("ExternalJavacServer.JavacProcessDescriptor");
|
||||
private static final AttributeKey<UUID> PROCESS_ID_KEY = AttributeKey.valueOf("ExternalJavacServer.ProcessId");
|
||||
private static final Key<Integer> PROCESS_HASH = Key.create("ExternalJavacServer.SdkHomePath");
|
||||
|
||||
private final File myWorkingDir;
|
||||
private final ChannelRegistrar myChannelRegistrar;
|
||||
private final Map<UUID, JavacProcessDescriptor> myMessageHandlers = new HashMap<>();
|
||||
private int myListenPort = DEFAULT_SERVER_PORT;
|
||||
private final Set<ProcessHandler> myRunningHandlers = ContainerUtil.newConcurrentSet();
|
||||
private final ThreadPoolExecutor myExecutor = ConcurrencyUtil.newSingleThreadExecutor("Javac server event loop pool");
|
||||
private final Map<UUID, CompileSession> mySessions = Collections.synchronizedMap(new HashMap<>());
|
||||
private final Map<UUID, ExternalJavacProcessHandler> myRunningProcesses = Collections.synchronizedMap(new HashMap<>());
|
||||
private final Map<UUID, Channel> myConnections = Collections.synchronizedMap(new HashMap<>()); // processId->channel
|
||||
private final Executor myExecutor;
|
||||
private boolean myOwnExecutor;
|
||||
private final long myKeepAliveTimeout;
|
||||
|
||||
/**
|
||||
* @deprecated: use {@link #ExternalJavacManager(File, Executor)} instead with explicit executor parameter
|
||||
*/
|
||||
@Deprecated
|
||||
public ExternalJavacManager(@NotNull final File workingDir) {
|
||||
this(workingDir, ConcurrencyUtil.newSingleThreadExecutor("Javac server event loop pool"));
|
||||
myOwnExecutor = true;
|
||||
}
|
||||
|
||||
public ExternalJavacManager(@NotNull final File workingDir, @NotNull Executor executor) {
|
||||
this(workingDir, executor, 5 * 60 * 1000L /* 5 minutes default*/);
|
||||
}
|
||||
|
||||
public ExternalJavacManager(@NotNull final File workingDir, @NotNull Executor executor, long keepAliveTimeout) {
|
||||
myWorkingDir = workingDir;
|
||||
myChannelRegistrar = new ChannelRegistrar();
|
||||
myExecutor = executor;
|
||||
myKeepAliveTimeout = keepAliveTimeout;
|
||||
}
|
||||
|
||||
public void start(int listenPort) {
|
||||
@@ -93,6 +114,10 @@ public class ExternalJavacManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link #forkJavac(String, int, List, List, CompilationPaths, Collection, Map, DiagnosticOutputConsumer, OutputFileConsumer, JavaCompilingTool, CanceledStatus, boolean)} instead
|
||||
*/
|
||||
@Deprecated
|
||||
public boolean forkJavac(String javaHome,
|
||||
int heapSize,
|
||||
List<String> vmOptions,
|
||||
@@ -108,70 +133,54 @@ public class ExternalJavacManager {
|
||||
OutputFileConsumer outputSink,
|
||||
JavaCompilingTool compilingTool,
|
||||
CanceledStatus cancelStatus) {
|
||||
final ExternalJavacMessageHandler rh = new ExternalJavacMessageHandler(diagnosticSink, outputSink, getEncodingName(options));
|
||||
final JavacRemoteProto.Message.Request request = JavacProtoUtil.createCompilationRequest(
|
||||
options, files, classpath, platformCp, modulePath, upgradeModulePath, sourcePath, outs);
|
||||
final UUID uuid = UUID.randomUUID();
|
||||
final JavacProcessDescriptor processDescriptor = new JavacProcessDescriptor(uuid, rh, request);
|
||||
synchronized (myMessageHandlers) {
|
||||
myMessageHandlers.put(uuid, processDescriptor);
|
||||
}
|
||||
return forkJavac(
|
||||
javaHome, heapSize, vmOptions, options,
|
||||
CompilationPaths.create(platformCp, classpath, upgradeModulePath, modulePath, sourcePath),
|
||||
files, outs, diagnosticSink, outputSink, compilingTool, cancelStatus, false
|
||||
).get();
|
||||
}
|
||||
|
||||
|
||||
public ExternalJavacRunResult forkJavac(String javaHome,
|
||||
int heapSize,
|
||||
List<String> vmOptions,
|
||||
List<String> options,
|
||||
CompilationPaths paths,
|
||||
Collection<File> files,
|
||||
Map<File, Set<File>> outs,
|
||||
DiagnosticOutputConsumer diagnosticSink,
|
||||
OutputFileConsumer outputSink,
|
||||
JavaCompilingTool compilingTool,
|
||||
CanceledStatus cancelStatus, final boolean keepProcessAlive) {
|
||||
try {
|
||||
final ExternalJavacProcessHandler processHandler = launchExternalJavacProcess(
|
||||
uuid, javaHome, heapSize, myListenPort, myWorkingDir, vmOptions, compilingTool
|
||||
final ExternalJavacProcessHandler running = findRunningProcess(processHash(javaHome, vmOptions, compilingTool));
|
||||
final ExternalJavacProcessHandler processHandler = running != null && running.lock()? running : launchExternalJavacProcess(
|
||||
javaHome, heapSize, myListenPort, myWorkingDir, vmOptions, compilingTool, running == null && keepProcessAlive
|
||||
);
|
||||
myRunningHandlers.add(processHandler);
|
||||
processHandler.addProcessListener(new ProcessAdapter() {
|
||||
@Override
|
||||
public void processTerminated(@NotNull ProcessEvent event) {
|
||||
myRunningHandlers.remove(processHandler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
|
||||
final String text = event.getText();
|
||||
if (!StringUtil.isEmptyOrSpaces(text)) {
|
||||
String prefix = null;
|
||||
if (outputType == ProcessOutputTypes.STDOUT) {
|
||||
prefix = STDOUT_LINE_PREFIX;
|
||||
}
|
||||
else if (outputType == ProcessOutputTypes.STDERR) {
|
||||
prefix = STDERR_LINE_PREFIX;
|
||||
}
|
||||
if (prefix != null) {
|
||||
diagnosticSink.outputLineAvailable(prefix + ": " + text);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
processHandler.startNotify();
|
||||
|
||||
while (!processDescriptor.waitFor(300L)) {
|
||||
if (processHandler.isProcessTerminated() && processDescriptor.channel == null && processHandler.getExitCode() != 0) {
|
||||
// process terminated abnormally and no communication took place
|
||||
processDescriptor.setDone();
|
||||
break;
|
||||
}
|
||||
if (cancelStatus.isCanceled()) {
|
||||
processDescriptor.cancelBuild();
|
||||
}
|
||||
final Channel channel = lookupChannel(processHandler.getProcessId());
|
||||
if (channel != null) {
|
||||
final CompileSession session = new CompileSession(
|
||||
processHandler.getProcessId(), new ExternalJavacMessageHandler(diagnosticSink, outputSink, getEncodingName(options)), cancelStatus
|
||||
);
|
||||
mySessions.put(session.getId(), session);
|
||||
channel.writeAndFlush(JavacProtoUtil.toMessage(session.getId(), JavacProtoUtil.createCompilationRequest(
|
||||
options, files, paths.getClasspath(), paths.getPlatformClasspath(), paths.getModulePath(), paths.getUpgradeModulePath(), paths.getSourcePath(), outs
|
||||
)));
|
||||
return session;
|
||||
}
|
||||
return rh.isTerminatedSuccessfully();
|
||||
}
|
||||
catch (Throwable e) {
|
||||
LOG.info(e);
|
||||
diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, e.getMessage()));
|
||||
}
|
||||
finally {
|
||||
unregisterMessageHandler(uuid);
|
||||
}
|
||||
return false;
|
||||
return ExternalJavacRunResult.FAILURE;
|
||||
}
|
||||
|
||||
// returns true if all process handlers terminated
|
||||
@TestOnly
|
||||
public boolean waitForAllProcessHandlers(long time, @NotNull TimeUnit unit) {
|
||||
for (ProcessHandler handler : myRunningHandlers) {
|
||||
for (ProcessHandler handler : myRunningProcesses.values()) {
|
||||
if (!handler.waitFor(unit.toMillis(time))) {
|
||||
return false;
|
||||
}
|
||||
@@ -181,22 +190,47 @@ public class ExternalJavacManager {
|
||||
|
||||
@TestOnly
|
||||
public boolean awaitNettyThreadPoolTermination(long time, @NotNull TimeUnit unit) {
|
||||
try {
|
||||
return myExecutor.awaitTermination(time, unit);
|
||||
if (myOwnExecutor && myExecutor instanceof ExecutorService) {
|
||||
try {
|
||||
return ((ExecutorService)myExecutor).awaitTermination(time, unit);
|
||||
}
|
||||
catch (InterruptedException ignored) {
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ignored) {
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
private ExternalJavacProcessHandler findRunningProcess(int processHash) {
|
||||
List<ExternalJavacProcessHandler> idleProcesses = null;
|
||||
try {
|
||||
synchronized (myRunningProcesses) {
|
||||
for (Map.Entry<UUID, ExternalJavacProcessHandler> entry : myRunningProcesses.entrySet()) {
|
||||
final ExternalJavacProcessHandler process = entry.getValue();
|
||||
final Integer hash = PROCESS_HASH.get(process);
|
||||
if (hash != null && hash == processHash) {
|
||||
return process;
|
||||
}
|
||||
if (process.getIdleTime() > myKeepAliveTimeout) {
|
||||
if (idleProcesses == null) {
|
||||
idleProcesses = new ArrayList<>();
|
||||
}
|
||||
idleProcesses.add(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
if (idleProcesses != null) {
|
||||
for (ExternalJavacProcessHandler process : idleProcesses) {
|
||||
shutdownProcess(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void unregisterMessageHandler(UUID uuid) {
|
||||
final JavacProcessDescriptor descriptor;
|
||||
synchronized (myMessageHandlers) {
|
||||
descriptor = myMessageHandlers.remove(uuid);
|
||||
}
|
||||
if (descriptor != null) {
|
||||
descriptor.setDone();
|
||||
}
|
||||
private static int processHash(String sdkHomePath, List<String> vmOptions, JavaCompilingTool tool) {
|
||||
return Objects.hash(sdkHomePath.replace(File.separatorChar, '/'), vmOptions, tool.getId());
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -206,17 +240,54 @@ public class ExternalJavacManager {
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
synchronized (myConnections) {
|
||||
for (Map.Entry<UUID, Channel> entry : myConnections.entrySet()) {
|
||||
entry.getValue().writeAndFlush(JavacProtoUtil.toMessage(entry.getKey(), JavacProtoUtil.createShutdownRequest()));
|
||||
}
|
||||
}
|
||||
myChannelRegistrar.close().awaitUninterruptibly();
|
||||
myExecutor.shutdown();
|
||||
if (myOwnExecutor && myExecutor instanceof ExecutorService) {
|
||||
((ExecutorService)myExecutor).shutdown();
|
||||
}
|
||||
}
|
||||
|
||||
private ExternalJavacProcessHandler launchExternalJavacProcess(UUID uuid,
|
||||
String sdkHomePath,
|
||||
public void shutdownIdleProcesses() {
|
||||
List<ExternalJavacProcessHandler> idleProcesses = null;
|
||||
synchronized (myRunningProcesses) {
|
||||
for (ExternalJavacProcessHandler process : myRunningProcesses.values()) {
|
||||
final long idle = process.getIdleTime();
|
||||
if (idle > myKeepAliveTimeout) {
|
||||
if (idleProcesses == null) {
|
||||
idleProcesses = new ArrayList<>();
|
||||
}
|
||||
idleProcesses.add(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (idleProcesses != null) {
|
||||
for (ExternalJavacProcessHandler process : idleProcesses) {
|
||||
shutdownProcess(process);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shutdownProcess(ExternalJavacProcessHandler process) {
|
||||
final Channel conn = myConnections.get(process.getProcessId());
|
||||
if (conn != null && process.lock()) {
|
||||
conn.writeAndFlush(JavacProtoUtil.toMessage(process.getProcessId(), JavacProtoUtil.createShutdownRequest()));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private ExternalJavacProcessHandler launchExternalJavacProcess(String sdkHomePath,
|
||||
int heapSize,
|
||||
int port,
|
||||
File workingDir,
|
||||
List<String> vmOptions,
|
||||
JavaCompilingTool compilingTool) throws Exception {
|
||||
JavaCompilingTool compilingTool,
|
||||
final boolean keepProcessAlive) throws Exception {
|
||||
final UUID processId = UUID.randomUUID();
|
||||
final List<String> cmdLine = new ArrayList<>();
|
||||
|
||||
appendParam(cmdLine, getVMExecutablePath(sdkHomePath));
|
||||
@@ -262,24 +333,80 @@ public class ExternalJavacManager {
|
||||
appendParam(cmdLine, cp.stream().map(File::getPath).collect(Collectors.joining(File.pathSeparator)));
|
||||
|
||||
appendParam(cmdLine, ExternalJavacProcess.class.getName());
|
||||
appendParam(cmdLine, uuid.toString());
|
||||
appendParam(cmdLine, processId.toString());
|
||||
appendParam(cmdLine, "127.0.0.1");
|
||||
appendParam(cmdLine, Integer.toString(port));
|
||||
appendParam(cmdLine, Boolean.toString(keepProcessAlive)); // keep in memory after build finished
|
||||
|
||||
appendParam(cmdLine, FileUtil.toSystemIndependentName(workingDir.getPath()));
|
||||
|
||||
if (LOG.isDebugEnabled()) {
|
||||
LOG.debug("starting external compiler: " + cmdLine);
|
||||
}
|
||||
|
||||
FileUtil.createDirectory(workingDir);
|
||||
|
||||
Process process = new ProcessBuilder(cmdLine).directory(workingDir).start();
|
||||
return createProcessHandler(process, StringUtil.join(cmdLine, " "));
|
||||
final int processHash = processHash(sdkHomePath, vmOptions, compilingTool);
|
||||
final ExternalJavacProcessHandler processHandler = createProcessHandler(processId, new ProcessBuilder(cmdLine).directory(workingDir).start(), StringUtil.join(cmdLine, " "), keepProcessAlive);
|
||||
PROCESS_HASH.set(processHandler, processHash);
|
||||
processHandler.lock();
|
||||
myRunningProcesses.put(processId, processHandler);
|
||||
processHandler.addProcessListener(this);
|
||||
processHandler.startNotify();
|
||||
return processHandler;
|
||||
}
|
||||
|
||||
protected ExternalJavacProcessHandler createProcessHandler(@NotNull Process process, @NotNull String commandLine) {
|
||||
return new ExternalJavacProcessHandler(process, commandLine);
|
||||
@Override
|
||||
public void processTerminated(@NotNull ProcessEvent event) {
|
||||
final UUID processId = ((ExternalJavacProcessHandler)event.getProcessHandler()).getProcessId();
|
||||
myRunningProcesses.remove(processId);
|
||||
synchronized (mySessions) {
|
||||
for (Iterator<Map.Entry<UUID, CompileSession>> it = mySessions.entrySet().iterator(); it.hasNext(); ) {
|
||||
final CompileSession session = it.next().getValue();
|
||||
if (processId.equals(session.getProcessId())) {
|
||||
session.setDone();
|
||||
it.remove();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTextAvailable(@NotNull ProcessEvent event, @NotNull Key outputType) {
|
||||
final String text = event.getText();
|
||||
if (!StringUtil.isEmptyOrSpaces(text)) {
|
||||
String prefix = null;
|
||||
if (outputType == ProcessOutputTypes.STDOUT) {
|
||||
prefix = STDOUT_LINE_PREFIX;
|
||||
}
|
||||
else if (outputType == ProcessOutputTypes.STDERR) {
|
||||
prefix = STDERR_LINE_PREFIX;
|
||||
}
|
||||
if (prefix != null) {
|
||||
List<DiagnosticOutputConsumer> consumers = null;
|
||||
final UUID processId = ((ExternalJavacProcessHandler)event.getProcessHandler()).getProcessId();
|
||||
synchronized (mySessions) {
|
||||
for (CompileSession session : mySessions.values()) {
|
||||
if (processId.equals(session.getProcessId())) {
|
||||
if (consumers == null) {
|
||||
consumers = new ArrayList<>();
|
||||
}
|
||||
consumers.add(session.myHandler.getDiagnosticSink());
|
||||
}
|
||||
}
|
||||
}
|
||||
if (consumers != null) {
|
||||
final String msg = prefix + ": " + text;
|
||||
for (DiagnosticOutputConsumer consumer : consumers) {
|
||||
consumer.outputLineAvailable(msg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected ExternalJavacProcessHandler createProcessHandler(UUID processId, @NotNull Process process, @NotNull String commandLine, boolean keepProcessAlive) {
|
||||
return new ExternalJavacProcessHandler(processId, process, commandLine, keepProcessAlive);
|
||||
}
|
||||
|
||||
private static void appendParam(List<String> cmdLine, String parameter) {
|
||||
@@ -306,57 +433,78 @@ public class ExternalJavacManager {
|
||||
}
|
||||
|
||||
protected static class ExternalJavacProcessHandler extends BaseOSProcessHandler {
|
||||
private volatile int myExitCode;
|
||||
private long myIdleSince;
|
||||
private final UUID myProcessId;
|
||||
private final boolean myKeepProcessAlive;
|
||||
private boolean myIsBusy;
|
||||
|
||||
protected ExternalJavacProcessHandler(@NotNull Process process, @NotNull String commandLine) {
|
||||
protected ExternalJavacProcessHandler(UUID processId, @NotNull Process process, @NotNull String commandLine, boolean keepProcessAlive) {
|
||||
super(process, commandLine, null);
|
||||
addProcessListener(new ProcessAdapter() {
|
||||
@Override
|
||||
public void processTerminated(@NotNull ProcessEvent event) {
|
||||
myExitCode = event.getExitCode();
|
||||
}
|
||||
});
|
||||
myProcessId = processId;
|
||||
myKeepProcessAlive = keepProcessAlive;
|
||||
}
|
||||
|
||||
public UUID getProcessId() {
|
||||
return myProcessId;
|
||||
}
|
||||
|
||||
public synchronized long getIdleTime() {
|
||||
final long idleSince = myIdleSince;
|
||||
return idleSince <= 0L? 0L : (System.currentTimeMillis() - idleSince);
|
||||
}
|
||||
|
||||
public synchronized void unlock() {
|
||||
myIdleSince = System.currentTimeMillis();
|
||||
myIsBusy = false;
|
||||
}
|
||||
|
||||
public synchronized boolean lock() {
|
||||
myIdleSince = 0L;
|
||||
return !myIsBusy && (myIsBusy = true);
|
||||
}
|
||||
|
||||
public boolean isKeepProcessAlive() {
|
||||
return myKeepProcessAlive;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public Integer getExitCode() {
|
||||
return myExitCode;
|
||||
@Override
|
||||
protected BaseOutputReader.Options readerOptions() {
|
||||
// if keepAlive requested, that means the process will be waiting considerable periods of time without any output
|
||||
return myKeepProcessAlive? BaseOutputReader.Options.BLOCKING : super.readerOptions();
|
||||
}
|
||||
}
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private class CompilationRequestsHandler extends SimpleChannelInboundHandler<JavacRemoteProto.Message> {
|
||||
|
||||
@Override
|
||||
public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelRegistered(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelActive(@NotNull ChannelHandlerContext ctx) throws Exception {
|
||||
super.channelActive(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
|
||||
JavacProcessDescriptor descriptor = ctx.channel().attr(SESSION_DESCRIPTOR).getAndSet(null);
|
||||
if (descriptor != null) {
|
||||
descriptor.setDone();
|
||||
final Channel channel = ctx.channel();
|
||||
final UUID processId = channel.attr(PROCESS_ID_KEY).get();
|
||||
if (processId != null) {
|
||||
myConnections.remove(processId);
|
||||
}
|
||||
super.channelUnregistered(ctx);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void channelRead0(final ChannelHandlerContext context, JavacRemoteProto.Message message) throws Exception {
|
||||
JavacProcessDescriptor descriptor = context.channel().attr(SESSION_DESCRIPTOR).get();
|
||||
|
||||
UUID sessionId;
|
||||
if (descriptor == null) {
|
||||
// this is the first message for this session, so fill session data with missing info
|
||||
sessionId = JavacProtoUtil.fromProtoUUID(message.getSessionId());
|
||||
|
||||
descriptor = myMessageHandlers.get(sessionId);
|
||||
if (descriptor != null) {
|
||||
descriptor.channel = context.channel();
|
||||
context.channel().attr(SESSION_DESCRIPTOR).set(descriptor);
|
||||
}
|
||||
}
|
||||
else {
|
||||
sessionId = descriptor.sessionId;
|
||||
}
|
||||
|
||||
final ExternalJavacMessageHandler handler = descriptor != null? descriptor.handler : null;
|
||||
// in case of REQUEST_ACK this is a process ID, otherwise this is a sessionId
|
||||
final UUID msgUuid = JavacProtoUtil.fromProtoUUID(message.getSessionId());
|
||||
|
||||
CompileSession session = mySessions.get(msgUuid);
|
||||
final ExternalJavacMessageHandler handler = session != null? session.myHandler : null;
|
||||
final JavacRemoteProto.Message.Type messageType = message.getMessageType();
|
||||
|
||||
JavacRemoteProto.Message reply = null;
|
||||
@@ -364,27 +512,38 @@ public class ExternalJavacManager {
|
||||
if (messageType == JavacRemoteProto.Message.Type.RESPONSE) {
|
||||
final JavacRemoteProto.Message.Response response = message.getResponse();
|
||||
final JavacRemoteProto.Message.Response.Type responseType = response.getResponseType();
|
||||
if (handler != null) {
|
||||
if (responseType == JavacRemoteProto.Message.Response.Type.REQUEST_ACK) {
|
||||
final JavacRemoteProto.Message.Request request = descriptor.request;
|
||||
if (request != null) {
|
||||
reply = JavacProtoUtil.toMessage(sessionId, request);
|
||||
descriptor.request = null;
|
||||
if (responseType == JavacRemoteProto.Message.Response.Type.REQUEST_ACK) {
|
||||
// in this case msgUuid is a process ID, so we need to save the channel, associated with the process
|
||||
final Channel channel = context.channel();
|
||||
channel.attr(PROCESS_ID_KEY).set(msgUuid);
|
||||
synchronized (myConnections) {
|
||||
myConnections.put(msgUuid, channel);
|
||||
myConnections.notifyAll();
|
||||
}
|
||||
}
|
||||
else if (handler != null) {
|
||||
final boolean terminateOk = handler.handleMessage(message);
|
||||
if (terminateOk) {
|
||||
session.setDone();
|
||||
mySessions.remove(session.getId());
|
||||
final ExternalJavacProcessHandler process = myRunningProcesses.get(session.getProcessId());
|
||||
if (process != null) {
|
||||
process.unlock();
|
||||
// todo: submit running process GC task?
|
||||
//if (process.isKeepProcessAlive()) {
|
||||
//}
|
||||
}
|
||||
}
|
||||
else {
|
||||
final boolean terminateOk = handler.handleMessage(message);
|
||||
if (terminateOk) {
|
||||
descriptor.setDone();
|
||||
}
|
||||
else if (session.isCancelRequested()) {
|
||||
reply = JavacProtoUtil.toMessage(msgUuid, JavacProtoUtil.createCancelRequest());
|
||||
}
|
||||
}
|
||||
else {
|
||||
reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createCancelRequest());
|
||||
reply = JavacProtoUtil.toMessage(msgUuid, JavacProtoUtil.createCancelRequest());
|
||||
}
|
||||
}
|
||||
else {
|
||||
reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure("Unsupported message: " + messageType.name(), null));
|
||||
reply = JavacProtoUtil.toMessage(msgUuid, JavacProtoUtil.createFailure("Unsupported message: " + messageType.name(), null));
|
||||
}
|
||||
}
|
||||
finally {
|
||||
@@ -395,6 +554,25 @@ public class ExternalJavacManager {
|
||||
}
|
||||
}
|
||||
|
||||
private Channel lookupChannel(UUID processId) {
|
||||
Channel channel = null;
|
||||
synchronized (myConnections) {
|
||||
channel = myConnections.get(processId);
|
||||
while (channel == null) {
|
||||
if (!myRunningProcesses.containsKey(processId)) {
|
||||
break; // the process is already gone
|
||||
}
|
||||
try {
|
||||
myConnections.wait(300L);
|
||||
}
|
||||
catch (InterruptedException ignored) {
|
||||
}
|
||||
channel = myConnections.get(processId);
|
||||
}
|
||||
}
|
||||
return channel;
|
||||
}
|
||||
|
||||
@ChannelHandler.Sharable
|
||||
private static final class ChannelRegistrar extends ChannelInboundHandlerAdapter {
|
||||
private final ChannelGroup openChannels = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
|
||||
@@ -434,34 +612,93 @@ public class ExternalJavacManager {
|
||||
}
|
||||
}
|
||||
|
||||
private static class JavacProcessDescriptor {
|
||||
private final UUID sessionId;
|
||||
private final ExternalJavacMessageHandler handler;
|
||||
private volatile JavacRemoteProto.Message.Request request;
|
||||
private volatile Channel channel;
|
||||
private class CompileSession extends ExternalJavacRunResult{
|
||||
private final UUID myId;
|
||||
private final UUID myProcessId;
|
||||
private final CanceledStatus myCancelStatus;
|
||||
private final ExternalJavacMessageHandler myHandler;
|
||||
private final Semaphore myDone = new Semaphore();
|
||||
|
||||
JavacProcessDescriptor(@NotNull UUID sessionId,
|
||||
@NotNull ExternalJavacMessageHandler handler,
|
||||
@NotNull JavacRemoteProto.Message.Request request) {
|
||||
this.sessionId = sessionId;
|
||||
this.handler = handler;
|
||||
this.request = request;
|
||||
CompileSession(@NotNull UUID processId, @NotNull ExternalJavacMessageHandler handler, CanceledStatus cancelStatus) {
|
||||
myProcessId = processId;
|
||||
myCancelStatus = cancelStatus;
|
||||
myId = UUID.randomUUID();
|
||||
myHandler = handler;
|
||||
myDone.down();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public UUID getId() {
|
||||
return myId;
|
||||
}
|
||||
|
||||
public void cancelBuild() {
|
||||
if (channel != null) {
|
||||
channel.writeAndFlush(JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createCancelRequest()));
|
||||
}
|
||||
@NotNull
|
||||
public UUID getProcessId() {
|
||||
return myProcessId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return myDone.isUp();
|
||||
}
|
||||
|
||||
public void setDone() {
|
||||
myDone.up();
|
||||
}
|
||||
|
||||
public boolean waitFor(long timeout) {
|
||||
return myDone.waitFor(timeout);
|
||||
public boolean isTerminatedSuccessfully() {
|
||||
return myHandler.isTerminatedSuccessfully();
|
||||
}
|
||||
|
||||
boolean isCancelRequested() {
|
||||
return myCancelStatus.isCanceled();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Boolean get() {
|
||||
while (true) {
|
||||
try {
|
||||
if (myDone.waitForUnsafe(300L)) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (InterruptedException ignored) {
|
||||
}
|
||||
if (checkStopConditions()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
return isTerminatedSuccessfully();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Boolean get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, TimeoutException {
|
||||
if (!myDone.waitForUnsafe(unit.toMillis(timeout))) {
|
||||
if (!checkStopConditions()) {
|
||||
// if execution continues, just notify about timeout
|
||||
throw new TimeoutException();
|
||||
}
|
||||
}
|
||||
return isTerminatedSuccessfully();
|
||||
}
|
||||
|
||||
private boolean checkStopConditions() {
|
||||
if (!myRunningProcesses.containsKey(myProcessId)) {
|
||||
// process terminated
|
||||
setDone();
|
||||
mySessions.remove(myId);
|
||||
return true;
|
||||
}
|
||||
if (isCancelRequested()) {
|
||||
final Channel channel = myConnections.get(myProcessId);
|
||||
if (channel != null) {
|
||||
channel.writeAndFlush(JavacProtoUtil.toMessage(myId, JavacProtoUtil.createCancelRequest()));
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package org.jetbrains.jps.javac;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
|
||||
public abstract class ExternalJavacRunResult implements Future<Boolean> {
|
||||
public static final ExternalJavacRunResult FAILURE = new ExternalJavacRunResult() {
|
||||
@Override
|
||||
public boolean isDone() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Boolean get() {
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public Boolean get(long timeout, @NotNull TimeUnit unit){
|
||||
return Boolean.FALSE;
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public final boolean cancel(boolean mayInterruptIfRunning) {
|
||||
return false; // not supported
|
||||
}
|
||||
|
||||
@Override
|
||||
public final boolean isCancelled() {
|
||||
return false; // not supported, as cancel is handled via CancelStatus
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public abstract Boolean get();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public abstract Boolean get(long timeout, @NotNull TimeUnit unit) throws InterruptedException, TimeoutException;
|
||||
}
|
||||
@@ -492,6 +492,9 @@ compiler.automake.allow.parallel=true
|
||||
compiler.automake.allow.parallel.description=The option allows to force automatically started builds to run in single-threaded mode even if 'Compile independent modules in parallel' is on\n\
|
||||
This might help to save CPU resources for the foreground processes.
|
||||
|
||||
compiler.external.javac.keep.alive.timeout=300000
|
||||
compiler.external.javac.keep.alive.timeout.description=If not used for the specified period of time or longer, the cached javac compilation process will be shut down by the IDE
|
||||
|
||||
vcs.annotations.preload=false
|
||||
vcs.showConsole=true
|
||||
vcs.log.bek.sort.disabled=false
|
||||
|
||||
Reference in New Issue
Block a user