summaryrefslogtreecommitdiff
path: root/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacServer.java
diff options
context:
space:
mode:
Diffstat (limited to 'jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacServer.java')
-rw-r--r--jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacServer.java310
1 files changed, 310 insertions, 0 deletions
diff --git a/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacServer.java b/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacServer.java
new file mode 100644
index 000000000000..f5ff5b175bd8
--- /dev/null
+++ b/jps/jps-builders/src/org/jetbrains/jps/javac/ExternalJavacServer.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2000-2014 JetBrains s.r.o.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.jetbrains.jps.javac;
+
+import com.intellij.openapi.diagnostic.Logger;
+import com.intellij.util.concurrency.Semaphore;
+import io.netty.bootstrap.ServerBootstrap;
+import io.netty.channel.*;
+import io.netty.channel.group.ChannelGroup;
+import io.netty.channel.group.ChannelGroupFuture;
+import io.netty.channel.group.DefaultChannelGroup;
+import io.netty.channel.nio.NioEventLoopGroup;
+import io.netty.channel.socket.nio.NioServerSocketChannel;
+import io.netty.handler.codec.protobuf.ProtobufDecoder;
+import io.netty.handler.codec.protobuf.ProtobufEncoder;
+import io.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder;
+import io.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender;
+import io.netty.util.AttributeKey;
+import io.netty.util.concurrent.ImmediateEventExecutor;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+import org.jetbrains.jps.builders.java.JavaCompilingTool;
+import org.jetbrains.jps.incremental.CompileContext;
+import org.jetbrains.jps.incremental.GlobalContextKey;
+import org.jetbrains.jps.incremental.Utils;
+import org.jetbrains.jps.model.JpsProject;
+import org.jetbrains.jps.model.java.JpsJavaExtensionService;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerConfiguration;
+import org.jetbrains.jps.model.java.compiler.JpsJavaCompilerOptions;
+import org.jetbrains.jps.service.SharedThreadPool;
+
+import javax.tools.*;
+import java.io.File;
+import java.util.*;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * @author Eugene Zhuravlev
+ * Date: 1/22/12
+ */
+@SuppressWarnings("UseOfSystemOutOrSystemErr")
+public class ExternalJavacServer {
+ private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.javac.ExternalJavacServer");
+ public static final GlobalContextKey<ExternalJavacServer> KEY = GlobalContextKey.create("_external_javac_server_");
+
+ public static final int DEFAULT_SERVER_PORT = 7878;
+ private static final AttributeKey<JavacProcessDescriptor> SESSION_DESCRIPTOR = AttributeKey.valueOf("ExternalJavacServer.JavacProcessDescriptor");
+
+ private ChannelRegistrar myChannelRegistrar;
+ private final Map<UUID, JavacProcessDescriptor> myMessageHandlers = new HashMap<UUID, JavacProcessDescriptor>();
+ private int myListenPort = DEFAULT_SERVER_PORT;
+
+ public void start(int listenPort) {
+ final ServerBootstrap bootstrap = new ServerBootstrap().group(new NioEventLoopGroup(1, SharedThreadPool.getInstance())).channel(NioServerSocketChannel.class);
+ bootstrap.childOption(ChannelOption.TCP_NODELAY, true).childOption(ChannelOption.SO_KEEPALIVE, true);
+ myChannelRegistrar = new ChannelRegistrar();
+ final ChannelHandler compilationRequestsHandler = new CompilationRequestsHandler();
+ bootstrap.childHandler(new ChannelInitializer() {
+ @Override
+ protected void initChannel(Channel channel) throws Exception {
+ channel.pipeline().addLast(myChannelRegistrar,
+ new ProtobufVarint32FrameDecoder(),
+ new ProtobufDecoder(JavacRemoteProto.Message.getDefaultInstance()),
+ new ProtobufVarint32LengthFieldPrepender(),
+ new ProtobufEncoder(),
+ compilationRequestsHandler);
+ }
+ });
+ myChannelRegistrar.add(bootstrap.bind(listenPort).syncUninterruptibly().channel());
+ myListenPort = listenPort;
+ }
+
+ private static int getExternalJavacHeapSize(CompileContext context) {
+ final JpsProject project = context.getProjectDescriptor().getProject();
+ final JpsJavaCompilerConfiguration config = JpsJavaExtensionService.getInstance().getOrCreateCompilerConfiguration(project);
+ final JpsJavaCompilerOptions options = config.getCurrentCompilerOptions();
+ return options.MAXIMUM_HEAP_SIZE;
+ }
+
+
+ public boolean forkJavac(CompileContext context, List<String> options,
+ List<String> vmOptions, Collection<File> files,
+ Collection<File> classpath,
+ Collection<File> platformCp,
+ Collection<File> sourcePath,
+ Map<File, Set<File>> outs,
+ DiagnosticOutputConsumer diagnosticSink,
+ OutputFileConsumer outputSink,
+ final String javaHome, final JavaCompilingTool compilingTool) {
+ final ExternalJavacMessageHandler rh = new ExternalJavacMessageHandler(diagnosticSink, outputSink, getEncodingName(options));
+ final JavacRemoteProto.Message.Request request = JavacProtoUtil.createCompilationRequest(options, files, classpath, platformCp, sourcePath, outs);
+ final UUID uuid = UUID.randomUUID();
+ final JavacProcessDescriptor processDescriptor = new JavacProcessDescriptor(uuid, rh, request);
+ synchronized (myMessageHandlers) {
+ myMessageHandlers.put(uuid, processDescriptor);
+ }
+ try {
+ final JavacServerBootstrap.ExternalJavacProcessHandler processHandler = JavacServerBootstrap.launchExternalJavacProcess(
+ uuid, javaHome, getExternalJavacHeapSize(context), myListenPort, Utils.getSystemRoot(), vmOptions, compilingTool
+ );
+
+ 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 (context.getCancelStatus().isCanceled()) {
+ processDescriptor.cancelBuild();
+ }
+ }
+
+ return rh.isTerminatedSuccessfully();
+ }
+ catch (Throwable e) {
+ LOG.info(e);
+ diagnosticSink.report(new PlainMessageDiagnostic(Diagnostic.Kind.ERROR, e.getMessage()));
+ }
+ finally {
+ unregisterMessageHandler(uuid);
+ }
+ return false;
+ }
+
+ private void unregisterMessageHandler(UUID uuid) {
+ final JavacProcessDescriptor descriptor;
+ synchronized (myMessageHandlers) {
+ descriptor = myMessageHandlers.remove(uuid);
+ }
+ if (descriptor != null) {
+ descriptor.setDone();
+ }
+ }
+
+ @Nullable
+ private static String getEncodingName(List<String> options) {
+ boolean found = false;
+ for (String option : options) {
+ if (found) {
+ return option;
+ }
+ if ("-encoding".equalsIgnoreCase(option)) {
+ found = true;
+ }
+ }
+ return null;
+ }
+
+ public void stop() {
+ myChannelRegistrar.close().awaitUninterruptibly();
+ }
+
+ @ChannelHandler.Sharable
+ private class CompilationRequestsHandler extends SimpleChannelInboundHandler<JavacRemoteProto.Message> {
+ @Override
+ public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
+ JavacProcessDescriptor descriptor = ctx.attr(SESSION_DESCRIPTOR).get();
+ if (descriptor != null) {
+ descriptor.setDone();
+ }
+ super.channelUnregistered(ctx);
+ }
+
+ @Override
+ public void channelRead0(final ChannelHandlerContext context, JavacRemoteProto.Message message) throws Exception {
+ JavacProcessDescriptor descriptor = context.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.attr(SESSION_DESCRIPTOR).set(descriptor);
+ }
+ }
+ else {
+ sessionId = descriptor.sessionId;
+ }
+
+ final ExternalJavacMessageHandler handler = descriptor != null? descriptor.handler : null;
+
+ final JavacRemoteProto.Message.Type messageType = message.getMessageType();
+
+ JavacRemoteProto.Message reply = null;
+ try {
+ 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;
+ }
+ }
+ else {
+ final boolean terminateOk = handler.handleMessage(message);
+ if (terminateOk) {
+ descriptor.setDone();
+ }
+ }
+ }
+ else {
+ reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createCancelRequest());
+ }
+ }
+ else {
+ reply = JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createFailure("Unsupported message: " + messageType.name(), null));
+ }
+ }
+ finally {
+ if (reply != null) {
+ context.channel().writeAndFlush(reply);
+ }
+ }
+ }
+ }
+
+ @ChannelHandler.Sharable
+ private static final class ChannelRegistrar extends ChannelInboundHandlerAdapter {
+ private final ChannelGroup openChannels = new DefaultChannelGroup(ImmediateEventExecutor.INSTANCE);
+
+ public boolean isEmpty() {
+ return openChannels.isEmpty();
+ }
+
+ public void add(@NotNull Channel serverChannel) {
+ assert serverChannel instanceof ServerChannel;
+ openChannels.add(serverChannel);
+ }
+
+ @Override
+ public void channelActive(ChannelHandlerContext context) throws Exception {
+ // we don't need to remove channel on close - ChannelGroup do it
+ openChannels.add(context.channel());
+ super.channelActive(context);
+ }
+
+ public ChannelGroupFuture close() {
+ EventLoopGroup eventLoopGroup = null;
+ for (Channel channel : openChannels) {
+ if (channel instanceof ServerChannel) {
+ eventLoopGroup = channel.eventLoop().parent();
+ break;
+ }
+ }
+
+ ChannelGroupFuture future;
+ try {
+ future = openChannels.close();
+ }
+ finally {
+ assert eventLoopGroup != null;
+ eventLoopGroup.shutdownGracefully(0, 15, TimeUnit.SECONDS);
+ }
+ return future;
+ }
+ }
+
+ private static class JavacProcessDescriptor {
+ @NotNull
+ final UUID sessionId;
+ @NotNull
+ final ExternalJavacMessageHandler handler;
+ volatile JavacRemoteProto.Message.Request request;
+ volatile Channel channel;
+ private final Semaphore myDone = new Semaphore();
+
+ public JavacProcessDescriptor(@NotNull UUID sessionId, @NotNull ExternalJavacMessageHandler handler, @NotNull JavacRemoteProto.Message.Request request) {
+ this.sessionId = sessionId;
+ this.handler = handler;
+ this.request = request;
+ myDone.down();
+ }
+
+ public void cancelBuild() {
+ if (channel != null) {
+ channel.writeAndFlush(JavacProtoUtil.toMessage(sessionId, JavacProtoUtil.createCancelRequest()));
+ }
+ }
+
+ public void setDone() {
+ myDone.up();
+ }
+
+
+ public boolean waitFor(long timeout) {
+ return myDone.waitFor(timeout);
+ }
+
+ }
+
+} \ No newline at end of file