diff --git a/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java b/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java index ced65cfc37f5..94d0a2bd858a 100644 --- a/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java +++ b/java/compiler/impl/src/com/intellij/compiler/CompileServerManager.java @@ -92,8 +92,9 @@ public class CompileServerManager implements ApplicationComponent{ private static final String DEFAULT_LOGGER_CONFIG = "defaultLogConfig.xml"; private volatile OSProcessHandler myProcessHandler; private final File mySystemDirectory; - private volatile CompileServerClient myClient = new CompileServerClient(); - private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(new SequentialTaskExecutor.AsyncTaskExecutor() { + @Nullable + private volatile CompileServerClient myClient; + private final SequentialTaskExecutor myTaskExecutor = new SequentialTaskExecutor(new AsyncTaskExecutor() { public void submit(Runnable runnable) { ApplicationManager.getApplication().executeOnPooledThread(runnable); } @@ -102,6 +103,12 @@ public class CompileServerManager implements ApplicationComponent{ private static final int MAKE_TRIGGER_DELAY = 5 * 1000 /*5 seconds*/; private final Map myAutomakeFutures = new HashMap(); private final CompileServerClasspathManager myClasspathManager = new CompileServerClasspathManager(); + private final AsyncTaskExecutor myAsyncExec = new AsyncTaskExecutor() { + @Override + public void submit(Runnable runnable) { + ApplicationManager.getApplication().executeOnPooledThread(runnable); + } + }; public CompileServerManager(final ProjectManager projectManager) { myProjectManager = projectManager; @@ -394,7 +401,8 @@ public class CompileServerManager implements ApplicationComponent{ } final int port = NetUtils.findAvailableSocketPort(); - final Process process = launchServer(port); + final long serverPingInterval = Registry.intValue("compiler.server.ping.interval", -1) * 1000L; + final Process process = launchServer(port, serverPingInterval); final OSProcessHandler processHandler = new OSProcessHandler(process, null) { protected boolean shouldDestroyProcessRecursively() { @@ -451,7 +459,7 @@ public class CompileServerManager implements ApplicationComponent{ throw new Exception("Server startup failed: " + startupMsg); } - CompileServerClient client = new CompileServerClient(); + CompileServerClient client = new CompileServerClient(serverPingInterval, myAsyncExec); boolean connected = false; try { connected = client.connect(NetUtils.getLocalHostString(), port); @@ -545,7 +553,7 @@ public class CompileServerManager implements ApplicationComponent{ // commandLine.add((launcherUsed? "-J" : "") + "-D" + CharsetToolkit.FILE_ENCODING_PROPERTY + "=" + CharsetToolkit.getDefaultSystemCharset().name()); //} - private Process launchServer(final int port) throws ExecutionException { + private Process launchServer(final int port, long pingInterval) throws ExecutionException { // validate tools.jar presence final JavaCompiler systemCompiler = ToolProvider.getSystemJavaCompiler(); if (systemCompiler == null) { @@ -561,7 +569,10 @@ public class CompileServerManager implements ApplicationComponent{ cmdLine.addParameter("-XX:ReservedCodeCacheSize=64m"); cmdLine.addParameter("-Xmx" + Registry.intValue("compiler.server.heap.size") + "m"); cmdLine.addParameter("-Djava.awt.headless=true"); - //cmdLine.addParameter("-DuseJavaUtilZip"); + //noinspection ConstantConditions + if (pingInterval > 0L) { + cmdLine.addParameter("-D" + GlobalOptions.PING_INTERVAL_MS_OPTION + "=" + pingInterval); + } final String additionalOptions = Registry.stringValue("compiler.server.vm.options"); if (!StringUtil.isEmpty(additionalOptions)) { final StringTokenizer tokenizer = new StringTokenizer(additionalOptions, " ", false); diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/AsyncTaskExecutor.java b/jps/jps-builders/src/org/jetbrains/jps/api/AsyncTaskExecutor.java new file mode 100644 index 000000000000..745834c7de69 --- /dev/null +++ b/jps/jps-builders/src/org/jetbrains/jps/api/AsyncTaskExecutor.java @@ -0,0 +1,16 @@ +package org.jetbrains.jps.api; + +/** +* @author Eugene Zhuravlev +* Date: 2/28/12 +*/ +public interface AsyncTaskExecutor { + AsyncTaskExecutor DEFAULT = new AsyncTaskExecutor() { + @Override + public void submit(Runnable runnable) { + new Thread(runnable).start(); + } + }; + + void submit(Runnable runnable); +} diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java index 3c3a59dcb7dd..547f8135d93d 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java @@ -10,6 +10,5 @@ public interface GlobalOptions { String USE_EXTERNAL_JAVAC_OPTION = "use.external.javac.process"; String HOSTNAME_OPTION = "localhost.name"; String VM_EXE_PATH_OPTION = "vm.executable.path"; - - long SERVER_PING_PERIOD = 2000L; // 2 sec + String PING_INTERVAL_MS_OPTION = "server.ping.interval"; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java b/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java index 38ce49434cab..de47b287cca2 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/SequentialTaskExecutor.java @@ -31,10 +31,6 @@ public class SequentialTaskExecutor { } }; - public interface AsyncTaskExecutor { - void submit(Runnable runnable); - } - public SequentialTaskExecutor(AsyncTaskExecutor executor) { myExecutor = executor; } diff --git a/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java b/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java index 2b731ae08aac..c7ab7dedfc46 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java @@ -18,15 +18,17 @@ import java.util.concurrent.TimeUnit; public class CompileServerClient extends SimpleProtobufClient { private static final ScheduledThreadPoolExecutor ourPingService = ConcurrencyUtil.newSingleScheduledThreadExecutor("Compile server ping thread", Thread.MIN_PRIORITY); private volatile ScheduledFuture myPingFuture; + private final long myServerPingInterval; - public CompileServerClient() { - super(JpsRemoteProto.Message.getDefaultInstance(), new UUIDGetter() { + public CompileServerClient(long serverPingInterval, final AsyncTaskExecutor asyncExec) { + super(JpsRemoteProto.Message.getDefaultInstance(), asyncExec, new UUIDGetter() { @NotNull public UUID getSessionUUID(@NotNull MessageEvent e) { final JpsRemoteProto.Message message = (JpsRemoteProto.Message)e.getMessage(); return ProtoUtil.fromProtoUUID(message.getSessionId()); } }); + myServerPingInterval = serverPingInterval; } @NotNull @@ -89,15 +91,17 @@ public class CompileServerClient extends SimpleProtobufClient 0L) { + myPingFuture = ourPingService.scheduleAtFixedRate(new Runnable() { + @Override + public void run() { + final JpsRemoteProto.Message.Request ping = ProtoUtil.createPingRequest(); + if (isConnected()) { + sendRequest(ping, null); + } } - } - }, GlobalOptions.SERVER_PING_PERIOD, GlobalOptions.SERVER_PING_PERIOD, TimeUnit.MILLISECONDS); + }, myServerPingInterval, myServerPingInterval, TimeUnit.MILLISECONDS); + } } @Override diff --git a/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java b/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java index 226c82b3ae06..9c6477f29512 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/ProtobufClientMessageHandler.java @@ -6,6 +6,7 @@ import org.jboss.netty.channel.ChannelStateEvent; import org.jboss.netty.channel.MessageEvent; import org.jboss.netty.channel.SimpleChannelHandler; import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.api.AsyncTaskExecutor; import org.jetbrains.jps.api.RequestFuture; import java.util.ArrayList; @@ -21,10 +22,12 @@ final class ProtobufClientMessageHandler exte @NotNull private final UUIDGetter myUuidGetter; private final SimpleProtobufClient myClient; + private final AsyncTaskExecutor myAsyncExec; - public ProtobufClientMessageHandler(@NotNull UUIDGetter uuidGetter, SimpleProtobufClient client) { + public ProtobufClientMessageHandler(@NotNull UUIDGetter uuidGetter, SimpleProtobufClient client, AsyncTaskExecutor asyncExec) { myUuidGetter = uuidGetter; myClient = client; + myAsyncExec = asyncExec; } public final void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception { @@ -89,7 +92,12 @@ final class ProtobufClientMessageHandler exte } finally { // make sure the client is in disconnected state - myClient.scheduleDisconnect(); + myAsyncExec.submit(new Runnable() { + @Override + public void run() { + myClient.disconnect(); + } + }); } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java b/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java index 6aeb15c3fd31..8c762adf18b8 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java @@ -10,6 +10,7 @@ import org.jboss.netty.handler.codec.protobuf.ProtobufEncoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32FrameDecoder; import org.jboss.netty.handler.codec.protobuf.ProtobufVarint32LengthFieldPrepender; import org.jetbrains.annotations.Nullable; +import org.jetbrains.jps.api.AsyncTaskExecutor; import org.jetbrains.jps.api.RequestFuture; import java.net.InetSocketAddress; @@ -36,8 +37,8 @@ public class SimpleProtobufClient { protected ChannelFuture myConnectFuture; private final ProtobufClientMessageHandler myMessageHandler; - public SimpleProtobufClient(final MessageLite msgDefaultInstance, final UUIDGetter uuidGetter) { - myMessageHandler = new ProtobufClientMessageHandler(uuidGetter, this); + public SimpleProtobufClient(final MessageLite msgDefaultInstance, final AsyncTaskExecutor asyncExec, final UUIDGetter uuidGetter) { + myMessageHandler = new ProtobufClientMessageHandler(uuidGetter, this, asyncExec); myChannelFactory = new NioClientSocketChannelFactory(ourExecutor, ourExecutor, 1); myPipelineFactory = new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { @@ -105,15 +106,6 @@ public class SimpleProtobufClient { protected void onDisconnect() { } - public final void scheduleDisconnect() { - ourExecutor.submit(new Runnable() { - @Override - public void run() { - disconnect(); - } - }); - } - public final void disconnect() { if (myState.compareAndSet(State.CONNECTED, State.DISCONNECTING)) { try { @@ -154,23 +146,31 @@ public class SimpleProtobufClient { public final RequestFuture sendMessage(final UUID messageId, MessageLite message, @Nullable final T responseHandler, @Nullable final RequestFuture.CancelAction cancelAction) { final RequestFuture requestFuture = new RequestFuture(responseHandler, messageId, cancelAction); myMessageHandler.registerFuture(messageId, requestFuture); - final ChannelFuture channelFuture = Channels.write(myConnectFuture.getChannel(), message); - channelFuture.addListener(new ChannelFutureListener() { - public void operationComplete(ChannelFuture future) throws Exception { - if (!future.isSuccess()) { - try { - myMessageHandler.removeFuture(messageId); - if (responseHandler != null) { - responseHandler.sessionTerminated(); - } - } - finally { - requestFuture.setDone(); + final Channel channel = myConnectFuture.getChannel(); + if (channel.isConnected()) { + Channels.write(channel, message).addListener(new ChannelFutureListener() { + public void operationComplete(ChannelFuture future) throws Exception { + if (!future.isSuccess()) { + notifyTerminated(messageId, requestFuture, responseHandler); } } - } - }); + }); + } + else { + notifyTerminated(messageId, requestFuture, responseHandler); + } return requestFuture; } + private void notifyTerminated(UUID messageId, RequestFuture requestFuture, @Nullable T responseHandler) { + try { + myMessageHandler.removeFuture(messageId); + if (responseHandler != null) { + responseHandler.sessionTerminated(); + } + } + finally { + requestFuture.setDone(); + } + } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java b/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java index a737e67c5481..75a3e7cb6e1d 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/javac/JavacServerClient.java @@ -2,21 +2,31 @@ package org.jetbrains.jps.javac; import org.jboss.netty.channel.MessageEvent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.jps.api.AsyncTaskExecutor; import org.jetbrains.jps.api.RequestFuture; import org.jetbrains.jps.client.SimpleProtobufClient; import org.jetbrains.jps.client.UUIDGetter; import java.io.File; import java.util.*; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** * @author Eugene Zhuravlev * Date: 1/22/12 */ public class JavacServerClient extends SimpleProtobufClient{ + private static final ExecutorService ourExecutors = Executors.newCachedThreadPool(); + private static final AsyncTaskExecutor ASYNC_EXEC = new AsyncTaskExecutor() { + @Override + public void submit(Runnable runnable) { + ourExecutors.submit(runnable); + } + }; public JavacServerClient() { - super(JavacRemoteProto.Message.getDefaultInstance(), new UUIDGetter() { + super(JavacRemoteProto.Message.getDefaultInstance(), ASYNC_EXEC, new UUIDGetter() { @NotNull public UUID getSessionUUID(@NotNull MessageEvent e) { final JavacRemoteProto.Message message = (JavacRemoteProto.Message)e.getMessage(); diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/Server.java b/jps/jps-builders/src/org/jetbrains/jps/server/Server.java index fad3563ebb70..715a70edd32b 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/Server.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/Server.java @@ -38,6 +38,7 @@ public class Server { public static final String SERVER_SUCCESS_START_MESSAGE = "Compile Server started successfully. Listening on port: "; public static final String SERVER_ERROR_START_MESSAGE = "Error starting Compile Server: "; private static final String LOG_FILE_NAME = "log.xml"; + private static final long PING_INTERVAL = Long.parseLong(System.getProperty(GlobalOptions.PING_INTERVAL_MS_OPTION, "-1")); private final ChannelGroup myAllOpenChannels = new DefaultChannelGroup("compile-server"); private final ChannelFactory myChannelFactory; @@ -45,6 +46,7 @@ public class Server { private final ExecutorService myBuildsExecutor; private volatile long myLastPingTime = -1L; private final ScheduledExecutorService myScheduler; + private final ServerMessageHandler myMessageHandler; public Server(File systemDir) { Paths.getInstance().setSystemRoot(systemDir); @@ -53,7 +55,7 @@ public class Server { myBuildsExecutor = Executors.newFixedThreadPool(MAX_SIMULTANEOUS_BUILD_SESSIONS); myChannelFactory = new NioServerSocketChannelFactory(threadPool, threadPool, 1); final ChannelRegistrar channelRegistrar = new ChannelRegistrar(); - final ServerMessageHandler messageHandler = new ServerMessageHandler(myBuildsExecutor, this); + myMessageHandler = new ServerMessageHandler(myBuildsExecutor, this); myPipelineFactory = new ChannelPipelineFactory() { public ChannelPipeline getPipeline() throws Exception { return Channels.pipeline( @@ -62,7 +64,7 @@ public class Server { new ProtobufDecoder(JpsRemoteProto.Message.getDefaultInstance()), new ProtobufVarint32LengthFieldPrepender(), new ProtobufEncoder(), - messageHandler + myMessageHandler ); } }; @@ -76,11 +78,14 @@ public class Server { final Channel serverChannel = bootstrap.bind(new InetSocketAddress(listenPort)); myAllOpenChannels.add(serverChannel); - startIdleMonitor(); + startActivityMonitor(); } - private void startIdleMonitor() { - final long allowedIdlePeriod = 2 * GlobalOptions.SERVER_PING_PERIOD; + private void startActivityMonitor() { + if (PING_INTERVAL <= 0L) { + return; + } + final long allowedIdlePeriod = 2 * PING_INTERVAL; myScheduler.scheduleAtFixedRate(new Runnable() { private long myStartTime; @Override @@ -90,16 +95,16 @@ public class Server { if (lastPing > 0L) { final long elapsed = now - lastPing; if (elapsed > allowedIdlePeriod) { - doStop(); + doStop(elapsed); } } else { final long start = myStartTime; if (start > 0) { final long elapsed = now - start; - if (elapsed > 5 * GlobalOptions.SERVER_PING_PERIOD) { + if (elapsed > 5 * PING_INTERVAL) { // no pings received since start - doStop(); + doStop(elapsed); } } else { @@ -108,12 +113,15 @@ public class Server { } } - private void doStop() { - try { - stop(); - } - finally { - System.exit(0); + private void doStop(long elapsedTime) { + if (!myMessageHandler.hasRunningBuilds()) { + try { + System.out.println("Stopping compile server; reason: no pings from client received in " + elapsedTime + " ms"); + stop(); + } + finally { + System.exit(0); + } } } }, allowedIdlePeriod, allowedIdlePeriod, TimeUnit.MILLISECONDS); diff --git a/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java b/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java index aec4665a0292..fa4f3e93db9b 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java @@ -36,6 +36,7 @@ class ServerMessageHandler extends SimpleChannelHandler { } public void messageReceived(final ChannelHandlerContext ctx, MessageEvent e) throws Exception { + myServer.pingReceived(); final JpsRemoteProto.Message message = (JpsRemoteProto.Message)e.getMessage(); final UUID sessionId = ProtoUtil.fromProtoUUID(message.getSessionId()); @@ -86,7 +87,6 @@ class ServerMessageHandler extends SimpleChannelHandler { break; case SHUTDOWN_COMMAND : - // todo pay attention to policy myBuildsExecutor.submit(new Runnable() { public void run() { final List futures = new ArrayList(); @@ -139,7 +139,6 @@ class ServerMessageHandler extends SimpleChannelHandler { reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createCommandCompletedEvent(null)); break; case PING: - myServer.pingReceived(); reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createCommandCompletedEvent(null)); default: reply = ProtoUtil.toMessage(sessionId, ProtoUtil.createFailure("Unknown request: " + message)); @@ -213,7 +212,7 @@ class ServerMessageHandler extends SimpleChannelHandler { synchronized (myTaskExecutors) { SequentialTaskExecutor executor = myTaskExecutors.get(projectId); if (executor == null) { - executor = new SequentialTaskExecutor(new SequentialTaskExecutor.AsyncTaskExecutor() { + executor = new SequentialTaskExecutor(new AsyncTaskExecutor() { @Override public void submit(Runnable runnable) { myBuildsExecutor.submit(runnable); @@ -226,12 +225,14 @@ class ServerMessageHandler extends SimpleChannelHandler { } public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception { - if (this == ctx.getPipeline().getLast()) { - LOG.error(e); - } + LOG.error(e); ctx.sendUpstream(e); } + public boolean hasRunningBuilds() { + return !myBuildsInProgress.isEmpty(); + } + private class CompilationTask implements Runnable, CanceledStatus { private final UUID mySessionId; diff --git a/platform/platform-resources-en/src/misc/registry.properties b/platform/platform-resources-en/src/misc/registry.properties index 7a709b1179ae..58b11b17dbb5 100644 --- a/platform/platform-resources-en/src/misc/registry.properties +++ b/platform/platform-resources-en/src/misc/registry.properties @@ -145,6 +145,9 @@ compiler.server.use.memory.temp.cache.description=Store temporary data in memory compiler.server.use.external.javac.process=true compiler.server.use.external.javac.process.description=Run javac compiler in external process (allows to run compile server with smaller heap size) +compiler.server.ping.interval=5 +compiler.server.ping.interval.description=Interval in seconds between ping requests the IDE periodically sends to server. If server does not receive pings for some time, it shuts down. Specify -1 to disable this feature. + compiler.server.debug.port=-1 #compiler.server.javac.debug.port=-1