ping interval configurable vi registry;

do not auto-shut down server if there are active builds
This commit is contained in:
Eugene Zhuravlev
2012-02-28 13:19:11 +04:00
parent 6c72162109
commit cc9caa13ba
11 changed files with 126 additions and 70 deletions
@@ -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<RequestFuture, Project> myAutomakeFutures = new HashMap<RequestFuture, Project>();
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);
@@ -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);
}
@@ -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";
}
@@ -31,10 +31,6 @@ public class SequentialTaskExecutor {
}
};
public interface AsyncTaskExecutor {
void submit(Runnable runnable);
}
public SequentialTaskExecutor(AsyncTaskExecutor executor) {
myExecutor = executor;
}
@@ -18,15 +18,17 @@ import java.util.concurrent.TimeUnit;
public class CompileServerClient extends SimpleProtobufClient<JpsServerResponseHandler> {
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<JpsServerResponseH
@Override
protected void onConnect() {
myPingFuture = ourPingService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
final JpsRemoteProto.Message.Request ping = ProtoUtil.createPingRequest();
if (isConnected()) {
sendRequest(ping, null);
if (myServerPingInterval > 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
@@ -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<T extends ProtobufResponseHandler> 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<T extends ProtobufResponseHandler> exte
}
finally {
// make sure the client is in disconnected state
myClient.scheduleDisconnect();
myAsyncExec.submit(new Runnable() {
@Override
public void run() {
myClient.disconnect();
}
});
}
}
@@ -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<T extends ProtobufResponseHandler> {
protected ChannelFuture myConnectFuture;
private final ProtobufClientMessageHandler<T> myMessageHandler;
public SimpleProtobufClient(final MessageLite msgDefaultInstance, final UUIDGetter uuidGetter) {
myMessageHandler = new ProtobufClientMessageHandler<T>(uuidGetter, this);
public SimpleProtobufClient(final MessageLite msgDefaultInstance, final AsyncTaskExecutor asyncExec, final UUIDGetter uuidGetter) {
myMessageHandler = new ProtobufClientMessageHandler<T>(uuidGetter, this, asyncExec);
myChannelFactory = new NioClientSocketChannelFactory(ourExecutor, ourExecutor, 1);
myPipelineFactory = new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() throws Exception {
@@ -105,15 +106,6 @@ public class SimpleProtobufClient<T extends ProtobufResponseHandler> {
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<T extends ProtobufResponseHandler> {
public final RequestFuture<T> sendMessage(final UUID messageId, MessageLite message, @Nullable final T responseHandler, @Nullable final RequestFuture.CancelAction<T> cancelAction) {
final RequestFuture<T> requestFuture = new RequestFuture<T>(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<T> requestFuture, @Nullable T responseHandler) {
try {
myMessageHandler.removeFuture(messageId);
if (responseHandler != null) {
responseHandler.sessionTerminated();
}
}
finally {
requestFuture.setDone();
}
}
}
@@ -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<JavacServerResponseHandler>{
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();
@@ -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);
@@ -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<RunnableFuture> futures = new ArrayList<RunnableFuture>();
@@ -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;
@@ -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