mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
ping interval configurable vi registry;
do not auto-shut down server if there are active builds
This commit is contained in:
@@ -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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user