From 1cdd4a9e10222dfd9f67535ee940fe7aef82839e Mon Sep 17 00:00:00 2001 From: Eugene Zhuravlev Date: Mon, 27 Feb 2012 17:08:43 +0400 Subject: [PATCH] shutdown server if IDE crashed --- jps/jps-builders/proto/jps_remote_proto.proto | 1 + .../org/jetbrains/jps/api/GlobalOptions.java | 2 + .../org/jetbrains/jps/api/JpsRemoteProto.java | 2 + .../src/org/jetbrains/jps/api/ProtoUtil.java | 4 ++ .../jps/client/CompileServerClient.java | 24 +++++++++ .../jps/client/SimpleProtobufClient.java | 19 +++++++ .../src/org/jetbrains/jps/server/Server.java | 53 +++++++++++++++++++ .../jps/server/ServerMessageHandler.java | 3 ++ 8 files changed, 108 insertions(+) diff --git a/jps/jps-builders/proto/jps_remote_proto.proto b/jps/jps-builders/proto/jps_remote_proto.proto index 730a59e33544..d994eda27f40 100644 --- a/jps/jps-builders/proto/jps_remote_proto.proto +++ b/jps/jps-builders/proto/jps_remote_proto.proto @@ -35,6 +35,7 @@ message Message { RELOAD_PROJECT_COMMAND = 4; FS_EVENT = 5; CANCEL_BUILD_COMMAND = 6; + PING = 7; } message CompilationRequest { 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 ee4e99bd02bc..3c3a59dcb7dd 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/GlobalOptions.java @@ -10,4 +10,6 @@ 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 } diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/JpsRemoteProto.java b/jps/jps-builders/src/org/jetbrains/jps/api/JpsRemoteProto.java index 424a1ef5e08c..341dfc20e912 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/JpsRemoteProto.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/JpsRemoteProto.java @@ -1019,6 +1019,7 @@ public final class JpsRemoteProto { RELOAD_PROJECT_COMMAND(3, 4), FS_EVENT(4, 5), CANCEL_BUILD_COMMAND(5, 6), + PING(6, 7), ; @@ -1032,6 +1033,7 @@ public final class JpsRemoteProto { case 4: return RELOAD_PROJECT_COMMAND; case 5: return FS_EVENT; case 6: return CANCEL_BUILD_COMMAND; + case 7: return PING; default: return null; } } diff --git a/jps/jps-builders/src/org/jetbrains/jps/api/ProtoUtil.java b/jps/jps-builders/src/org/jetbrains/jps/api/ProtoUtil.java index b93378dddacb..f6cfb5856280 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/api/ProtoUtil.java +++ b/jps/jps-builders/src/org/jetbrains/jps/api/ProtoUtil.java @@ -116,6 +116,10 @@ public class ProtoUtil { return JpsRemoteProto.Message.Request.newBuilder().setRequestType(JpsRemoteProto.Message.Request.Type.FS_EVENT).setFsEvent(builder.build()).build(); } + public static JpsRemoteProto.Message.Request createPingRequest() { + return JpsRemoteProto.Message.Request.newBuilder().setRequestType(JpsRemoteProto.Message.Request.Type.PING).build(); + } + public static JpsRemoteProto.Message.Request createSetupRequest(final Map pathVars, List sdkAndLibs, @Nullable String globalEncoding) { final JpsRemoteProto.Message.Request.SetupCommand.Builder cmdBuilder = JpsRemoteProto.Message.Request.SetupCommand.newBuilder(); 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 49b2e2a9a1d3..50dd38f9b493 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/CompileServerClient.java @@ -1,17 +1,23 @@ package org.jetbrains.jps.client; +import com.intellij.util.ConcurrencyUtil; import org.jboss.netty.channel.MessageEvent; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.jps.api.*; import java.util.*; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.ScheduledThreadPoolExecutor; +import java.util.concurrent.TimeUnit; /** * @author Eugene Zhuravlev * Date: 8/11/11 */ public class CompileServerClient extends SimpleProtobufClient { + private static final ScheduledThreadPoolExecutor ourPingService = ConcurrencyUtil.newSingleScheduledThreadExecutor("Compile server ping thread", Thread.MIN_PRIORITY); + private volatile ScheduledFuture myPingFuture; public CompileServerClient() { super(JpsRemoteProto.Message.getDefaultInstance(), new UUIDGetter() { @@ -81,4 +87,22 @@ public class CompileServerClient extends SimpleProtobufClient future = myPingFuture; + if (future != null) { + future.cancel(true); + myPingFuture = null; + } + } } 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 1d87ae643f07..53304c98364a 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java +++ b/jps/jps-builders/src/org/jetbrains/jps/client/SimpleProtobufClient.java @@ -1,6 +1,7 @@ package org.jetbrains.jps.client; import com.google.protobuf.MessageLite; +import com.intellij.openapi.diagnostic.Logger; import org.jboss.netty.bootstrap.ClientBootstrap; import org.jboss.netty.channel.*; import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; @@ -21,6 +22,7 @@ import java.util.concurrent.atomic.AtomicReference; * Date: 1/22/12 */ public class SimpleProtobufClient { + private static final Logger LOG = Logger.getInstance("#org.jetbrains.jps.client.SimpleProtobufClient"); private static enum State { DISCONNECTED, CONNECTING, CONNECTED, DISCONNECTING } @@ -69,6 +71,12 @@ public class SimpleProtobufClient { if (success) { myConnectFuture = future; + try { + onConnect(); + } + catch (Throwable e) { + LOG.error(e); + } } else { final Throwable reason = future.getCause(); @@ -87,6 +95,11 @@ public class SimpleProtobufClient { return true; } + protected void onConnect() { + } + protected void onDisconnect() { + } + public final void disconnect() { if (myState.compareAndSet(State.CONNECTED, State.DISCONNECTING)) { try { @@ -104,6 +117,12 @@ public class SimpleProtobufClient { finally { myConnectFuture = null; myState.compareAndSet(State.DISCONNECTING, State.DISCONNECTED); + try { + onDisconnect(); + } + catch (Throwable e) { + LOG.error(e); + } } } } 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 8cdde853f06c..fad3563ebb70 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/Server.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/Server.java @@ -2,6 +2,7 @@ package org.jetbrains.jps.server; //import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.diagnostic.Logger; +import com.intellij.util.ConcurrencyUtil; import org.apache.log4j.Level; import org.apache.log4j.xml.DOMConfigurator; import org.jboss.netty.bootstrap.ServerBootstrap; @@ -24,6 +25,8 @@ import java.io.File; import java.net.InetSocketAddress; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; /** * @author Eugene Zhuravlev @@ -40,10 +43,13 @@ public class Server { private final ChannelFactory myChannelFactory; private final ChannelPipelineFactory myPipelineFactory; private final ExecutorService myBuildsExecutor; + private volatile long myLastPingTime = -1L; + private final ScheduledExecutorService myScheduler; public Server(File systemDir) { Paths.getInstance().setSystemRoot(systemDir); final ExecutorService threadPool = Executors.newCachedThreadPool(); + myScheduler = ConcurrencyUtil.newSingleScheduledThreadExecutor("Client activity checker", Thread.MIN_PRIORITY); myBuildsExecutor = Executors.newFixedThreadPool(MAX_SIMULTANEOUS_BUILD_SESSIONS); myChannelFactory = new NioServerSocketChannelFactory(threadPool, threadPool, 1); final ChannelRegistrar channelRegistrar = new ChannelRegistrar(); @@ -69,10 +75,53 @@ public class Server { bootstrap.setOption("child.keepAlive", true); final Channel serverChannel = bootstrap.bind(new InetSocketAddress(listenPort)); myAllOpenChannels.add(serverChannel); + + startIdleMonitor(); + } + + private void startIdleMonitor() { + final long allowedIdlePeriod = 2 * GlobalOptions.SERVER_PING_PERIOD; + myScheduler.scheduleAtFixedRate(new Runnable() { + private long myStartTime; + @Override + public void run() { + final long now = System.currentTimeMillis(); + final long lastPing = myLastPingTime; + if (lastPing > 0L) { + final long elapsed = now - lastPing; + if (elapsed > allowedIdlePeriod) { + doStop(); + } + } + else { + final long start = myStartTime; + if (start > 0) { + final long elapsed = now - start; + if (elapsed > 5 * GlobalOptions.SERVER_PING_PERIOD) { + // no pings received since start + doStop(); + } + } + else { + myStartTime = now; + } + } + } + + private void doStop() { + try { + stop(); + } + finally { + System.exit(0); + } + } + }, allowedIdlePeriod, allowedIdlePeriod, TimeUnit.MILLISECONDS); } public void stop() { try { + myScheduler.shutdownNow(); myBuildsExecutor.shutdownNow(); final ChannelGroupFuture closeFuture = myAllOpenChannels.close(); closeFuture.awaitUninterruptibly(); @@ -82,6 +131,10 @@ public class Server { } } + public void pingReceived() { + myLastPingTime = System.currentTimeMillis(); + } + public static void main(String[] args) { try { int port = DEFAULT_SERVER_PORT; 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 c9ec9019c0f8..aec4665a0292 100644 --- a/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java +++ b/jps/jps-builders/src/org/jetbrains/jps/server/ServerMessageHandler.java @@ -138,6 +138,9 @@ 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)); }