mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Cleanup (leaner code in start-up utils; typos; formatting)
GitOrigin-RevId: 7364ef3a09389e516f798862c3a8a60eded0d221
This commit is contained in:
committed by
intellij-monorepo-bot
parent
26e938e533
commit
cb69f4c7d3
@@ -26,11 +26,9 @@ public final class Main {
|
||||
public static final int LICENSE_ERROR = 7;
|
||||
public static final int PLUGIN_ERROR = 8;
|
||||
public static final int OUT_OF_MEMORY = 9;
|
||||
@SuppressWarnings("unused") // left for compatibility and reserved for future use
|
||||
public static final int UNSUPPORTED_JAVA_VERSION = 10;
|
||||
@SuppressWarnings("unused") public static final int UNSUPPORTED_JAVA_VERSION = 10; // left for compatibility/reserved for future use
|
||||
public static final int PRIVACY_POLICY_REJECTION = 11;
|
||||
public static final int INSTALLATION_CORRUPTED = 12;
|
||||
// External cmdline and IDE activation
|
||||
public static final int ACTIVATE_WRONG_TOKEN_CODE = 13;
|
||||
public static final int ACTIVATE_LISTENER_NOT_INITIALIZED = 14;
|
||||
public static final int ACTIVATE_RESPONSE_TIMEOUT = 15;
|
||||
|
||||
@@ -12,15 +12,15 @@ import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.application.JetBrainsProtocolHandler;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Disposer;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.io.FileUtilRt;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.util.ArrayUtilRt;
|
||||
import com.intellij.util.concurrency.AppExecutorUtil;
|
||||
import com.intellij.util.containers.ContainerUtil;
|
||||
import com.intellij.util.containers.MultiMap;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufOutputStream;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
@@ -44,19 +44,18 @@ import java.nio.file.attribute.PosixFileAttributeView;
|
||||
import java.nio.file.attribute.PosixFilePermission;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import static com.intellij.openapi.util.Pair.pair;
|
||||
|
||||
public final class SocketLock {
|
||||
public enum ActivateStatus {ACTIVATED, NO_INSTANCE, CANNOT_ACTIVATE}
|
||||
public enum ActivationStatus {ACTIVATED, NO_INSTANCE, CANNOT_ACTIVATE}
|
||||
|
||||
private static final String PATHS_EOT_RESPONSE = "---";
|
||||
private static final String PORT_FILE = "port";
|
||||
private static final String PORT_LOCK_FILE = "port.lock";
|
||||
private static final String TOKEN_FILE = "token";
|
||||
@@ -64,12 +63,13 @@ public final class SocketLock {
|
||||
private static final String ACTIVATE_COMMAND = "activate ";
|
||||
private static final String PID_COMMAND = "pid";
|
||||
private static final String OK_RESPONSE = "ok";
|
||||
private static final String PATHS_EOT_RESPONSE = "---";
|
||||
|
||||
private final AtomicReference<CliRequestProcessor> myActivateListener = new AtomicReference<>();
|
||||
private final AtomicReference<Function<List<String>, Future<CliResult>>> myCommandProcessorRef = new AtomicReference<>();
|
||||
private final String myConfigPath;
|
||||
private final String mySystemPath;
|
||||
private final BlockingQueue<FileLock> myLockedFiles = new LinkedBlockingQueue<>();
|
||||
@Nullable private Future<? extends Disposable> myBuiltinServerFuture;
|
||||
private final List<FileLock> myLockedFiles = new ArrayList<>(2);
|
||||
private @Nullable Future<? extends Disposable> myBuiltinServerFuture;
|
||||
private volatile String myToken;
|
||||
private BuiltInServer myServer;
|
||||
|
||||
@@ -81,8 +81,17 @@ public final class SocketLock {
|
||||
}
|
||||
}
|
||||
|
||||
public void setExternalInstanceListener(@Nullable CliRequestProcessor processor) {
|
||||
myActivateListener.set(processor);
|
||||
private static String canonicalPath(String path) {
|
||||
try {
|
||||
return new File(path).getCanonicalPath();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
public void setCommandProcessor(@Nullable Function<List<String>, Future<CliResult>> processor) {
|
||||
myCommandProcessorRef.set(processor);
|
||||
}
|
||||
|
||||
public void dispose() {
|
||||
@@ -110,48 +119,40 @@ public final class SocketLock {
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
BuiltInServer getServer() {
|
||||
@Nullable BuiltInServer getServer() {
|
||||
return myServer;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ActivateStatusAndResponse lockAndTryActivate() throws Exception {
|
||||
return lockAndTryActivate(ArrayUtilRt.EMPTY_STRING_ARRAY);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public ActivateStatusAndResponse lockAndTryActivate(@NotNull String[] args) throws Exception {
|
||||
public @NotNull Pair<ActivationStatus, CliResult> lockAndTryActivate(@NotNull String[] args) throws IOException {
|
||||
log("enter: lock(config=%s system=%s)", myConfigPath, mySystemPath);
|
||||
|
||||
lockPortFiles();
|
||||
File portMarkerC = new File(myConfigPath, PORT_FILE);
|
||||
File portMarkerS = new File(mySystemPath, PORT_FILE);
|
||||
|
||||
MultiMap<Integer, String> portToPath = MultiMap.createSmart();
|
||||
addExistingPort(portMarkerC, myConfigPath, portToPath);
|
||||
addExistingPort(portMarkerS, mySystemPath, portToPath);
|
||||
readPort(myConfigPath, portToPath);
|
||||
readPort(mySystemPath, portToPath);
|
||||
if (!portToPath.isEmpty()) {
|
||||
args = JetBrainsProtocolHandler.checkForJetBrainsProtocolCommand(args);
|
||||
for (Map.Entry<Integer, Collection<String>> entry : portToPath.entrySet()) {
|
||||
ActivateStatusAndResponse status = tryActivate(entry.getKey(), entry.getValue(), args);
|
||||
if (status.getActivateStatus() != ActivateStatus.NO_INSTANCE) {
|
||||
log("exit: lock(): " + status.getActivateStatus());
|
||||
Pair<ActivationStatus, CliResult> status = tryActivate(entry.getKey(), entry.getValue(), args);
|
||||
if (status.first != ActivationStatus.NO_INSTANCE) {
|
||||
log("exit: lock(): " + status.first);
|
||||
unlockPortFiles();
|
||||
return status;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isShutdownCommand()) {
|
||||
if (JetBrainsProtocolHandler.isShutdownCommand()) {
|
||||
unlockPortFiles();
|
||||
System.exit(0);
|
||||
}
|
||||
|
||||
myBuiltinServerFuture = AppExecutorUtil.getAppExecutorService().submit(() -> {
|
||||
Activity builtinServerLaunch = ParallelActivity.PREPARE_APP_INIT.start("builtin server launch");
|
||||
Activity activity = ParallelActivity.PREPARE_APP_INIT.start("builtin server launch");
|
||||
|
||||
myToken = UUID.randomUUID().toString();
|
||||
Path tokenFile = Paths.get(mySystemPath, TOKEN_FILE);
|
||||
// parent directories are already created (see underLocks)
|
||||
Files.write(tokenFile, myToken.getBytes(StandardCharsets.UTF_8));
|
||||
PosixFileAttributeView view = Files.getFileAttributeView(tokenFile, PosixFileAttributeView.class);
|
||||
if (view != null) {
|
||||
@@ -164,41 +165,45 @@ public final class SocketLock {
|
||||
}
|
||||
|
||||
try {
|
||||
return launchBuiltinServer(new String[]{myConfigPath, mySystemPath});
|
||||
String[] lockedPaths = {myConfigPath, mySystemPath};
|
||||
Supplier<ChannelHandler> handlerSupplier = () -> new MyChannelInboundHandler(lockedPaths, myCommandProcessorRef, myToken);
|
||||
myServer = BuiltInServer.startNioOrOio(BuiltInServer.getRecommendedWorkerCount(), 6942, 50, false, handlerSupplier);
|
||||
writePort(myServer.getPort());
|
||||
unlockPortFiles();
|
||||
return myServer;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log(e);
|
||||
return null;
|
||||
}
|
||||
finally {
|
||||
builtinServerLaunch.end();
|
||||
activity.end();
|
||||
}
|
||||
});
|
||||
|
||||
log("exit: lock(): succeed");
|
||||
return ActivateStatusAndResponse.emptyResponse(ActivateStatus.NO_INSTANCE);
|
||||
return pair(ActivationStatus.NO_INSTANCE, null);
|
||||
}
|
||||
|
||||
void writePortAndUnlockFiles(int port) {
|
||||
File portMarkerC = new File(myConfigPath, PORT_FILE);
|
||||
File portMarkerS = new File(mySystemPath, PORT_FILE);
|
||||
byte[] portBytes = Integer.toString(port).getBytes(StandardCharsets.UTF_8);
|
||||
|
||||
private void writePort(int port) {
|
||||
byte[] data = Integer.toString(port).getBytes(StandardCharsets.UTF_8);
|
||||
try {
|
||||
FileUtil.writeToFile(portMarkerC, portBytes);
|
||||
FileUtil.writeToFile(portMarkerS, portBytes);
|
||||
FileUtil.writeToFile(new File(myConfigPath, PORT_FILE), data);
|
||||
FileUtil.writeToFile(new File(mySystemPath, PORT_FILE), data);
|
||||
}
|
||||
catch (IOException e) {
|
||||
log(e);
|
||||
}
|
||||
|
||||
unlockPortFiles();
|
||||
}
|
||||
|
||||
/**
|
||||
<p> According to https://stackoverflow.com/a/12652718/3463676, file locks should be removed on process segfault.
|
||||
According to {@link FileLock} javadoc, file locks are marked as invalid on JVM termination.
|
||||
|
||||
<p> Unlocking of port files (via {@link #unlockPortFiles}) happens either after builtin server init, or on app termination.
|
||||
|
||||
<p> Because of that, we do not care about non-starting netty leading to infinite lock handling, as we are not ready to
|
||||
accept connections anyway; on app termination the locks will be released anyway.
|
||||
* <p>According to https://stackoverflow.com/a/12652718/3463676, file locks should be removed on process segfault.
|
||||
* According to {@link FileLock} documentation, file locks are marked as invalid on JVM termination.</p>
|
||||
*
|
||||
* <p>Unlocking of port files (via {@link #unlockPortFiles}) happens either after builtin server init, or on app termination.</p>
|
||||
*
|
||||
* <p>Because of that, we do not care about non-starting Netty leading to infinite lock handling, as the IDE is not ready to
|
||||
* accept connections anyway; on app termination the locks will be released.</p>
|
||||
*/
|
||||
private synchronized void lockPortFiles() throws IOException {
|
||||
if (!myLockedFiles.isEmpty()) {
|
||||
@@ -206,12 +211,12 @@ public final class SocketLock {
|
||||
}
|
||||
|
||||
OpenOption[] options = {StandardOpenOption.CREATE, StandardOpenOption.APPEND};
|
||||
FileUtilRt.createDirectory(new File(myConfigPath));
|
||||
FileUtil.createDirectory(new File(myConfigPath));
|
||||
FileChannel cc = FileChannel.open(Paths.get(myConfigPath, PORT_LOCK_FILE), options);
|
||||
FileLock cl = cc.lock();
|
||||
myLockedFiles.add(cl);
|
||||
|
||||
FileUtilRt.createDirectory(new File(mySystemPath));
|
||||
FileUtil.createDirectory(new File(mySystemPath));
|
||||
FileChannel sc = FileChannel.open(Paths.get(mySystemPath, PORT_LOCK_FILE), options);
|
||||
FileLock sl = sc.lock();
|
||||
myLockedFiles.add(sl);
|
||||
@@ -226,67 +231,63 @@ public final class SocketLock {
|
||||
try {
|
||||
lock.release();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
}
|
||||
catch (IOException ignore) { }
|
||||
}
|
||||
myLockedFiles.clear();
|
||||
}
|
||||
|
||||
private static void addExistingPort(@NotNull File portMarker, @NotNull String path, @NotNull MultiMap<Integer, String> portToPath) {
|
||||
if (portMarker.exists()) {
|
||||
private static void readPort(String path, MultiMap<Integer, String> portToPath) {
|
||||
File portFile = new File(path, PORT_FILE);
|
||||
if (portFile.exists()) {
|
||||
try {
|
||||
portToPath.putValue(Integer.parseInt(FileUtilRt.loadFile(portMarker)), path);
|
||||
portToPath.putValue(Integer.parseInt(FileUtil.loadFile(portFile)), path);
|
||||
}
|
||||
catch (Exception e) {
|
||||
log(e);
|
||||
// don't delete - we overwrite it on write in any case
|
||||
log(e); // no need to delete - it would be overwritten
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private ActivateStatusAndResponse tryActivate(int portNumber, @NotNull Collection<String> paths, @NotNull String[] args) {
|
||||
private Pair<ActivationStatus, CliResult> tryActivate(int portNumber, Collection<String> paths, String[] args) {
|
||||
log("trying: port=%s", portNumber);
|
||||
args = checkForJetBrainsProtocolCommand(args);
|
||||
try {
|
||||
try (Socket socket = new Socket(InetAddress.getLoopbackAddress(), portNumber)) {
|
||||
socket.setSoTimeout(5000);
|
||||
|
||||
DataInputStream in = new DataInputStream(socket.getInputStream());
|
||||
final List<String> stringList = readStringSequence(in);
|
||||
// Backward compatibility: it required at least one path to match
|
||||
boolean result = ContainerUtil.intersects(paths, stringList);
|
||||
try (Socket socket = new Socket(InetAddress.getLoopbackAddress(), portNumber)) {
|
||||
socket.setSoTimeout(5000);
|
||||
|
||||
if (result) {
|
||||
// Update property right now, without scheduling on AWT. This allows to avoid shown-and-immediately-hidden splash in some cases.
|
||||
System.setProperty(SplashManager.NO_SPLASH, "true");
|
||||
EventQueue.invokeLater(() -> {
|
||||
Runnable hideSplashTask = SplashManager.getHideTask();
|
||||
if (hideSplashTask != null) hideSplashTask.run();
|
||||
});
|
||||
DataInputStream in = new DataInputStream(socket.getInputStream());
|
||||
List<String> stringList = readStringSequence(in);
|
||||
// backward compatibility: requires at least one path to match
|
||||
boolean result = ContainerUtil.intersects(paths, stringList);
|
||||
|
||||
try {
|
||||
String token = FileUtil.loadFile(new File(mySystemPath, TOKEN_FILE));
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataOutputStream out = new DataOutputStream(socket.getOutputStream());
|
||||
out.writeUTF(ACTIVATE_COMMAND + token + "\0" + new File(".").getAbsolutePath() + "\0" + StringUtil.join(args, "\0"));
|
||||
out.flush();
|
||||
if (result) {
|
||||
// update property right now, without scheduling to EDT - in some cases, allows to avoid a splash flickering
|
||||
System.setProperty(SplashManager.NO_SPLASH, "true");
|
||||
EventQueue.invokeLater(() -> {
|
||||
Runnable hideSplashTask = SplashManager.getHideTask();
|
||||
if (hideSplashTask != null) hideSplashTask.run();
|
||||
});
|
||||
|
||||
socket.setSoTimeout(0);
|
||||
List<String> response = readStringSequence(in);
|
||||
log("read: response=%s", StringUtil.join(response, ";"));
|
||||
if (OK_RESPONSE.equals(ContainerUtil.getFirstItem(response))) {
|
||||
if (isShutdownCommand()) {
|
||||
printPID(portNumber);
|
||||
}
|
||||
return new ActivateStatusAndResponse(ActivateStatus.ACTIVATED, mapResponseToCliResult(response));
|
||||
try {
|
||||
String token = FileUtil.loadFile(new File(mySystemPath, TOKEN_FILE));
|
||||
@SuppressWarnings("IOResourceOpenedButNotSafelyClosed") DataOutputStream out = new DataOutputStream(socket.getOutputStream());
|
||||
out.writeUTF(ACTIVATE_COMMAND + token + '\0' + new File(".").getAbsolutePath() + '\0' + StringUtil.join(args, "\0"));
|
||||
out.flush();
|
||||
|
||||
socket.setSoTimeout(0);
|
||||
List<String> response = readStringSequence(in);
|
||||
log("read: response=%s", StringUtil.join(response, ";"));
|
||||
if (OK_RESPONSE.equals(ContainerUtil.getFirstItem(response))) {
|
||||
if (JetBrainsProtocolHandler.isShutdownCommand()) {
|
||||
printPID(portNumber);
|
||||
}
|
||||
return pair(ActivationStatus.ACTIVATED, mapResponseToCliResult(response));
|
||||
}
|
||||
catch (IOException | IllegalArgumentException e) {
|
||||
log(e);
|
||||
}
|
||||
|
||||
return ActivateStatusAndResponse.emptyResponse(ActivateStatus.CANNOT_ACTIVATE);
|
||||
}
|
||||
catch (IOException | IllegalArgumentException e) {
|
||||
log(e);
|
||||
}
|
||||
|
||||
return pair(ActivationStatus.CANNOT_ACTIVATE, null);
|
||||
}
|
||||
}
|
||||
catch (ConnectException e) {
|
||||
@@ -296,24 +297,7 @@ public final class SocketLock {
|
||||
log(e);
|
||||
}
|
||||
|
||||
return ActivateStatusAndResponse.emptyResponse(ActivateStatus.NO_INSTANCE);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Disposable launchBuiltinServer(String[] lockedPaths) {
|
||||
try {
|
||||
myServer = BuiltInServer.startNioOrOio(BuiltInServer.getRecommendedWorkerCount(), 6942, 50, false, () -> {
|
||||
//noinspection CodeBlock2Expr
|
||||
// should be not inlined because handler created for each connected channel
|
||||
return new MyChannelInboundHandler(lockedPaths, myActivateListener, myToken);
|
||||
});
|
||||
writePortAndUnlockFiles(myServer.getPort());
|
||||
return myServer;
|
||||
}
|
||||
catch (Exception e) {
|
||||
log(e);
|
||||
return null;
|
||||
}
|
||||
return pair(ActivationStatus.NO_INSTANCE, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("ALL")
|
||||
@@ -332,44 +316,28 @@ public final class SocketLock {
|
||||
pid = Integer.parseInt(s.substring(0, s.indexOf('@')));
|
||||
System.err.println(pid);
|
||||
}
|
||||
}catch (IOException e) {
|
||||
}
|
||||
catch (IOException e) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isShutdownCommand() {
|
||||
return "shutdown".equals(JetBrainsProtocolHandler.getCommand());
|
||||
}
|
||||
|
||||
private static String[] checkForJetBrainsProtocolCommand(String[] args) {
|
||||
final String jbUrl = System.getProperty(JetBrainsProtocolHandler.class.getName());
|
||||
if (jbUrl != null) {
|
||||
return new String[]{jbUrl};
|
||||
}
|
||||
return args;
|
||||
}
|
||||
@FunctionalInterface
|
||||
public interface CliRequestProcessor {
|
||||
Future<CliResult> process(@NotNull List<String> args);
|
||||
catch (Exception ignore) { }
|
||||
}
|
||||
|
||||
private static final class MyChannelInboundHandler extends MessageDecoder {
|
||||
|
||||
private enum State {HEADER, CONTENT}
|
||||
|
||||
private final String[] myLockedPaths;
|
||||
private final AtomicReference<? extends CliRequestProcessor> myActivateListener;
|
||||
private final AtomicReference<Function<List<String>, Future<CliResult>>> myCommandProcessorRef;
|
||||
private final String myToken;
|
||||
private State myState = State.HEADER;
|
||||
|
||||
MyChannelInboundHandler(@NotNull String[] lockedPaths,
|
||||
@NotNull AtomicReference<? extends CliRequestProcessor> activateListener,
|
||||
@NotNull AtomicReference<Function<List<String>, Future<CliResult>>> commandProcessorRef,
|
||||
@NotNull String token) {
|
||||
myLockedPaths = lockedPaths;
|
||||
myActivateListener = activateListener;
|
||||
myCommandProcessorRef = commandProcessorRef;
|
||||
myToken = token;
|
||||
}
|
||||
|
||||
@@ -377,24 +345,22 @@ public final class SocketLock {
|
||||
public void channelActive(ChannelHandlerContext context) throws Exception {
|
||||
sendStringSequence(context, Arrays.asList(myLockedPaths));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void messageReceived(@NotNull ChannelHandlerContext context, @NotNull ByteBuf input) throws Exception {
|
||||
while (true) {
|
||||
switch (myState) {
|
||||
case HEADER: {
|
||||
ByteBuf buffer = getBufferIfSufficient(input, 2, context);
|
||||
if (buffer == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (buffer == null) return;
|
||||
contentLength = buffer.readUnsignedShort();
|
||||
if (contentLength > 8192) {
|
||||
context.close();
|
||||
return;
|
||||
}
|
||||
myState = State.CONTENT;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
||||
case CONTENT: {
|
||||
CharSequence command = readChars(input);
|
||||
@@ -415,7 +381,7 @@ public final class SocketLock {
|
||||
String data = command.subSequence(ACTIVATE_COMMAND.length(), command.length()).toString();
|
||||
List<String> args = StringUtil.split(data, data.contains("\0") ? "\0" : "\uFFFD");
|
||||
|
||||
final CliResult result;
|
||||
CliResult result;
|
||||
boolean tokenOK = !args.isEmpty() && myToken.equals(args.get(0));
|
||||
if (!tokenOK) {
|
||||
log(new UnsupportedOperationException("unauthorized request: " + command));
|
||||
@@ -427,23 +393,22 @@ public final class SocketLock {
|
||||
result = new CliResult(Main.ACTIVATE_WRONG_TOKEN_CODE, IdeBundle.message("activation.auth.message"));
|
||||
}
|
||||
else {
|
||||
CliRequestProcessor listener = myActivateListener.get();
|
||||
Function<List<String>, Future<CliResult>> listener = myCommandProcessorRef.get();
|
||||
if (listener != null) {
|
||||
result = CliResult.getOrWrapFailure(listener.process(args.subList(1, args.size())), Main.ACTIVATE_RESPONSE_TIMEOUT);
|
||||
result = CliResult.getOrWrapFailure(listener.apply(args.subList(1, args.size())), Main.ACTIVATE_RESPONSE_TIMEOUT);
|
||||
}
|
||||
else {
|
||||
result = new CliResult(Main.ACTIVATE_LISTENER_NOT_INITIALIZED, IdeBundle.message("activation.not.initialized"));
|
||||
}
|
||||
}
|
||||
|
||||
sendStringSequence(context,
|
||||
Stream.of(OK_RESPONSE, String.valueOf(result.getReturnCode()), result.getMessage())
|
||||
.filter(Objects::nonNull)
|
||||
.collect(Collectors.toList()));
|
||||
List<String> response = new ArrayList<>();
|
||||
ContainerUtil.addAllNotNull(response, OK_RESPONSE, String.valueOf(result.getReturnCode()), result.getMessage());
|
||||
sendStringSequence(context, response);
|
||||
}
|
||||
context.close();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -465,7 +430,6 @@ public final class SocketLock {
|
||||
context.writeAndFlush(buffer);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static List<String> readStringSequence(DataInputStream in) {
|
||||
List<String> result = new ArrayList<>();
|
||||
while (true) {
|
||||
@@ -485,13 +449,12 @@ public final class SocketLock {
|
||||
return result;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static CliResult mapResponseToCliResult(@NotNull List<String> responseParts) throws IllegalArgumentException {
|
||||
private static CliResult mapResponseToCliResult(List<String> responseParts) throws IllegalArgumentException {
|
||||
if (responseParts.size() > 3 || responseParts.size() < 2) {
|
||||
throw new IllegalArgumentException("bad response: " + StringUtil.join(responseParts, ";"));
|
||||
}
|
||||
|
||||
final int code;
|
||||
int code;
|
||||
try {
|
||||
code = Integer.parseInt(responseParts.get(1));
|
||||
}
|
||||
@@ -499,50 +462,18 @@ public final class SocketLock {
|
||||
throw new IllegalArgumentException("Second part is not a parsable return code", e);
|
||||
}
|
||||
|
||||
final String message = responseParts.size() == 3 ? responseParts.get(2) : null;
|
||||
String message = responseParts.size() == 3 ? responseParts.get(2) : null;
|
||||
return new CliResult(code, message);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static String canonicalPath(@NotNull String configPath) {
|
||||
try {
|
||||
return new File(configPath).getCanonicalPath();
|
||||
}
|
||||
catch (IOException ignore) {
|
||||
return configPath;
|
||||
}
|
||||
}
|
||||
private static void log(Exception e) {
|
||||
Logger.getInstance(SocketLock.class).warn(e);
|
||||
}
|
||||
|
||||
private static void log(String format, Object... args) {
|
||||
Logger logger = Logger.getInstance(SocketLock.class);
|
||||
if (logger.isDebugEnabled()) {
|
||||
logger.debug(String.format(format, args));
|
||||
}
|
||||
}
|
||||
public static class ActivateStatusAndResponse {
|
||||
@NotNull
|
||||
private final ActivateStatus myActivateStatus;
|
||||
@Nullable
|
||||
private final CliResult myResponse;
|
||||
|
||||
public ActivateStatusAndResponse(@NotNull ActivateStatus status, @Nullable CliResult response) {
|
||||
myActivateStatus = status;
|
||||
myResponse = response;
|
||||
}
|
||||
@NotNull
|
||||
public static ActivateStatusAndResponse emptyResponse(@NotNull ActivateStatus status) {
|
||||
return new ActivateStatusAndResponse(status, null);
|
||||
}
|
||||
@NotNull
|
||||
public ActivateStatus getActivateStatus() {
|
||||
return myActivateStatus;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CliResult getResponse() {
|
||||
return myResponse;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,7 @@ import com.intellij.openapi.application.impl.ApplicationImpl;
|
||||
import com.intellij.openapi.application.impl.ApplicationInfoImpl;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.IconLoader;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.ShutDownTracker;
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
@@ -67,6 +68,7 @@ import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.intellij.diagnostic.LoadingPhase.LAF_INITIALIZED;
|
||||
import static java.nio.file.attribute.PosixFilePermission.*;
|
||||
@@ -87,15 +89,14 @@ public final class StartupUtil {
|
||||
|
||||
private static final Thread.UncaughtExceptionHandler HANDLER = (t, e) -> MainRunner.processException(e);
|
||||
|
||||
public static synchronized void addExternalInstanceListener(@Nullable SocketLock.CliRequestProcessor processor) {
|
||||
// method called by app after startup
|
||||
/* called by the app after startup */
|
||||
public static synchronized void addExternalInstanceListener(@Nullable Function<List<String>, Future<CliResult>> processor) {
|
||||
if (ourSocketLock != null) {
|
||||
ourSocketLock.setExternalInstanceListener(processor);
|
||||
ourSocketLock.setCommandProcessor(processor);
|
||||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static synchronized BuiltInServer getServer() {
|
||||
public static synchronized @Nullable BuiltInServer getServer() {
|
||||
return ourSocketLock == null ? null : ourSocketLock.getServer();
|
||||
}
|
||||
|
||||
@@ -103,24 +104,23 @@ public final class StartupUtil {
|
||||
Thread.currentThread().setUncaughtExceptionHandler(HANDLER);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface AppStarter {
|
||||
// called in Idea Main thread
|
||||
/* called from IDE init thread */
|
||||
void start(@NotNull Future<?> initUiTask);
|
||||
|
||||
// not called in EDT
|
||||
/* called from IDE init thread */
|
||||
default void beforeImportConfigs() {}
|
||||
|
||||
// called in EDT
|
||||
/* called from EDT */
|
||||
default void beforeStartupWizard() {}
|
||||
|
||||
// called in EDT
|
||||
/* called from EDT */
|
||||
default void startupWizardFinished(@NotNull CustomizeIDEWizardStepsProvider provider) {}
|
||||
|
||||
// not called in EDT
|
||||
/* called from IDE init thread */
|
||||
default void importFinished(@NotNull Path newConfigDir) {}
|
||||
|
||||
// called in EDT
|
||||
/* called from EDT */
|
||||
default int customizeIdeWizardDialog(@NotNull List<AbstractCustomizeWizardStep> steps) {
|
||||
return -1;
|
||||
}
|
||||
@@ -140,8 +140,7 @@ public final class StartupUtil {
|
||||
}
|
||||
}
|
||||
|
||||
static void prepareAndStart(@NotNull String[] args, @NotNull AppStarter appStarter)
|
||||
throws InvocationTargetException, InterruptedException, ExecutionException {
|
||||
static void prepareAndStart(@NotNull String[] args, @NotNull AppStarter appStarter) throws Exception {
|
||||
Activity fjp = StartUpMeasurer.start("setupForkJoinCommonPool");
|
||||
IdeaForkJoinWorkerThreadFactory.setupForkJoinCommonPool(Main.isHeadless(args));
|
||||
fjp.end();
|
||||
@@ -150,23 +149,30 @@ public final class StartupUtil {
|
||||
|
||||
Future<?> initUiTask = scheduleInitUi(args);
|
||||
|
||||
configureLogging();
|
||||
configureLog4j();
|
||||
|
||||
if (!checkJdkVersion()) {
|
||||
System.exit(Main.JDK_CHECK_FAILED);
|
||||
}
|
||||
|
||||
// this check must be performed before system directories are locked
|
||||
boolean newConfigFolder = !Main.isHeadless() && !Files.exists(Paths.get(PathManager.getConfigPath()));
|
||||
boolean configImportNeeded = !Main.isHeadless() && !Files.exists(Paths.get(PathManager.getConfigPath()));
|
||||
|
||||
Logger log = lockDirsAndConfigureLogger(args);
|
||||
Activity dirsAndLogs = StartUpMeasurer.start(Phases.CHECK_SYSTEM_DIR);
|
||||
if (!checkSystemDirs()) { // note: uses config directory!
|
||||
System.exit(Main.DIR_CHECK_FAILED);
|
||||
}
|
||||
dirsAndLogs = dirsAndLogs.endAndStart(Phases.LOCK_SYSTEM_DIRS);
|
||||
lockSystemDirs(args);
|
||||
dirsAndLogs = dirsAndLogs.endAndStart("configure file logger");
|
||||
Logger log = setupLogger(); // log initialization should happen only after locking the system directory
|
||||
dirsAndLogs.end();
|
||||
|
||||
ExecutorService executorService = AppExecutorUtil.getAppExecutorService();
|
||||
|
||||
// no need to wait
|
||||
executorService.submit(() -> {
|
||||
executorService.execute(() -> {
|
||||
ApplicationInfo appInfo = ApplicationInfoImpl.getShadowInstance();
|
||||
Activity activity = ParallelActivity.PREPARE_APP_INIT.start("log essential info about ide");
|
||||
Activity activity = ParallelActivity.PREPARE_APP_INIT.start("log essential info about the IDE");
|
||||
logEssentialInfoAboutIde(log, appInfo);
|
||||
activity.end();
|
||||
});
|
||||
@@ -180,24 +186,22 @@ public final class StartupUtil {
|
||||
activity.end();
|
||||
}));
|
||||
|
||||
if (!newConfigFolder) {
|
||||
if (!configImportNeeded) {
|
||||
installPluginUpdates();
|
||||
runPreAppClass(log);
|
||||
}
|
||||
|
||||
// no need to wait
|
||||
executorService.execute(() -> loadSystemLibraries(log));
|
||||
|
||||
Activity waitTaskActivity = StartUpMeasurer.start(Phases.WAIT_TASKS);
|
||||
for (Future<?> future : futures) {
|
||||
future.get();
|
||||
}
|
||||
for (Future<?> future : futures) future.get();
|
||||
waitTaskActivity.end();
|
||||
futures.clear();
|
||||
|
||||
if (!Main.isHeadless()) {
|
||||
Activity activity = StartUpMeasurer.start(Phases.IMPORT_CONFIGS);
|
||||
if (newConfigFolder) {
|
||||
|
||||
if (configImportNeeded) {
|
||||
appStarter.beforeImportConfigs();
|
||||
Path newConfigDir = Paths.get(PathManager.getConfigPath());
|
||||
runInEdtAndWait(log, () -> ConfigImportHelper.importConfigsTo(newConfigDir, log), initUiTask);
|
||||
@@ -206,11 +210,11 @@ public final class StartupUtil {
|
||||
|
||||
showUserAgreementAndConsentsIfNeeded(log, initUiTask);
|
||||
|
||||
if (newConfigFolder && !ConfigImportHelper.isConfigImported()) {
|
||||
// exception handler is already set by ConfigImportHelper
|
||||
// event queue and icons already initialized as part of old config import
|
||||
if (configImportNeeded && !ConfigImportHelper.isConfigImported()) {
|
||||
// exception handler is already set by ConfigImportHelper; event queue and icons already initialized as part of old config import
|
||||
EventQueue.invokeAndWait(() -> runStartupWizard(appStarter));
|
||||
}
|
||||
|
||||
activity.end();
|
||||
}
|
||||
|
||||
@@ -225,9 +229,10 @@ public final class StartupUtil {
|
||||
@NotNull
|
||||
private static Future<?> scheduleInitUi(@NotNull String[] args) {
|
||||
Activity initLafAsync = StartUpMeasurer.start("schedule LaF init");
|
||||
|
||||
// mainly call sun.util.logging.PlatformLogger.getLogger - it takes enormous time (up to 500 ms)
|
||||
// Before lockDirsAndConfigureLogger can be executed only tasks that do not require log,
|
||||
// because we don't want to complicate logging. It is ok, because lockDirsAndConfigureLogger is not so heavy-weight as UI tasks.
|
||||
// because we don't want to complicate logging. It is OK, because lockDirsAndConfigureLogger is not so heavy-weight as UI tasks.
|
||||
Future<?> future = AppExecutorUtil.getAppExecutorService().submit(() -> {
|
||||
installExceptionHandler();
|
||||
|
||||
@@ -255,8 +260,8 @@ public final class StartupUtil {
|
||||
|
||||
LoadingPhase.setCurrentPhase(LoadingPhase.SPLASH);
|
||||
|
||||
// can be expensive (~200 ms), so, configure only after showing splash (not required for splash)
|
||||
StartupUiUtil.configureHtmlKitStylesheet();
|
||||
// may be expensive (~200 ms), so configure only after showing the splash
|
||||
StartupUiUtil.configureHtmlKitStylesheet();
|
||||
});
|
||||
}
|
||||
catch (InterruptedException | InvocationTargetException e) {
|
||||
@@ -284,85 +289,31 @@ public final class StartupUtil {
|
||||
}
|
||||
|
||||
LoadingPhase.setCurrentPhase(LAF_INITIALIZED);
|
||||
|
||||
});
|
||||
|
||||
initLafAsync.end();
|
||||
return future;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static Logger lockDirsAndConfigureLogger(@NotNull String[] args) {
|
||||
Activity activity = StartUpMeasurer.start(Phases.CHECK_SYSTEM_DIR);
|
||||
// note: uses config folder!
|
||||
if (!checkSystemFolders()) {
|
||||
System.exit(Main.DIR_CHECK_FAILED);
|
||||
}
|
||||
|
||||
activity = activity.endAndStart(Phases.LOCK_SYSTEM_DIRS);
|
||||
|
||||
SocketLock.ActivateStatusAndResponse result = lockSystemFolders(args);
|
||||
if (result.getActivateStatus() == SocketLock.ActivateStatus.ACTIVATED) {
|
||||
final CliResult cliOutput = Objects.requireNonNull(result.getResponse(), "guaranteed by SocketLock.mapResponseToCliResult");
|
||||
if (cliOutput.getMessage() != null) {
|
||||
//noinspection UseOfSystemOutOrSystemErr
|
||||
System.out.println(cliOutput.getMessage());
|
||||
}
|
||||
System.exit(cliOutput.getReturnCode());
|
||||
}
|
||||
if (result.getActivateStatus() == SocketLock.ActivateStatus.CANNOT_ACTIVATE) {
|
||||
System.exit(Main.INSTANCE_CHECK_FAILED);
|
||||
}
|
||||
|
||||
activity = activity.endAndStart("configure file logger");
|
||||
|
||||
// the log initialization should happen only after locking the system directory
|
||||
Logger.setFactory(new LoggerFactory());
|
||||
Logger log = Logger.getInstance(Main.class);
|
||||
|
||||
log.info("------------------------------------------------------ IDE STARTED ------------------------------------------------------");
|
||||
ShutDownTracker.getInstance().registerShutdownTask(() -> {
|
||||
log.info("------------------------------------------------------ IDE SHUTDOWN ------------------------------------------------------");
|
||||
});
|
||||
activity.end();
|
||||
return log;
|
||||
}
|
||||
|
||||
private static void configureLogging() {
|
||||
private static void configureLog4j() {
|
||||
Activity activity = StartUpMeasurer.start(Phases.CONFIGURE_LOGGING);
|
||||
// avoiding "log4j:WARN No appenders could be found"
|
||||
System.setProperty("log4j.defaultInitOverride", "true");
|
||||
try {
|
||||
org.apache.log4j.Logger root = org.apache.log4j.Logger.getRootLogger();
|
||||
if (!root.getAllAppenders().hasMoreElements()) {
|
||||
root.setLevel(Level.WARN);
|
||||
root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN)));
|
||||
}
|
||||
}
|
||||
catch (Throwable e) {
|
||||
//noinspection CallToPrintStackTrace
|
||||
e.printStackTrace();
|
||||
org.apache.log4j.Logger root = org.apache.log4j.Logger.getRootLogger();
|
||||
if (!root.getAllAppenders().hasMoreElements()) {
|
||||
root.setLevel(Level.WARN);
|
||||
root.addAppender(new ConsoleAppender(new PatternLayout(PatternLayout.DEFAULT_CONVERSION_PATTERN)));
|
||||
}
|
||||
activity.end();
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the program can run under the JDK it was started with.
|
||||
*/
|
||||
private static boolean checkJdkVersion() {
|
||||
if ("true".equals(System.getProperty("idea.jre.check"))) {
|
||||
try {
|
||||
// try to find a JDK class
|
||||
Class.forName("com.sun.jdi.Field", false, StartupUtil.class.getClassLoader());
|
||||
Class.forName("com.sun.jdi.Field", false, StartupUtil.class.getClassLoader()); // trying to find a JDK class
|
||||
}
|
||||
catch (ClassNotFoundException e) {
|
||||
String message = "JDK classes seem to be not on " + ApplicationNamesInfo.getInstance().getProductName() + " classpath.\n" +
|
||||
"Please ensure you run the IDE on JDK rather than JRE.";
|
||||
Main.showMessage("JDK Required", message, true);
|
||||
return false;
|
||||
}
|
||||
catch (LinkageError e) {
|
||||
String message = "Cannot load a JDK class: " + e.getMessage() + "\n" +
|
||||
"Please ensure you run the IDE on JDK rather than JRE.";
|
||||
catch (ClassNotFoundException | LinkageError e) {
|
||||
String message = "Cannot load a JDK class: " + e.getMessage() + "\nPlease ensure you run the IDE on JDK rather than JRE.";
|
||||
Main.showMessage("JDK Required", message, true);
|
||||
return false;
|
||||
}
|
||||
@@ -370,7 +321,7 @@ public final class StartupUtil {
|
||||
|
||||
if ("true".equals(System.getProperty("idea.64bit.check"))) {
|
||||
if (PlatformUtils.isCidr() && !SystemInfo.is64Bit) {
|
||||
String message = "32-bit JVM is not supported. Please use 64-bit version.";
|
||||
String message = "32-bit JVM is not supported. Please use a 64-bit version.";
|
||||
Main.showMessage("Unsupported JVM", message, true);
|
||||
return false;
|
||||
}
|
||||
@@ -391,7 +342,7 @@ public final class StartupUtil {
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized boolean checkSystemFolders() {
|
||||
private static synchronized boolean checkSystemDirs() {
|
||||
String configPath = PathManager.getConfigPath();
|
||||
PathManager.ensureConfigFolderExists();
|
||||
if (!checkDirectory(configPath, "Config", PathManager.PROPERTY_CONFIG_PATH, true, true, false)) {
|
||||
@@ -464,7 +415,7 @@ public final class StartupUtil {
|
||||
? "The application seems to be trans-located by macOS and cannot be used in this state.\n" +
|
||||
"Please use Finder to move it to another location."
|
||||
: "If you have modified the '" + property + "' property, please make sure it is correct,\n" +
|
||||
"otherwise please re-install the IDE.";
|
||||
"otherwise, please re-install the IDE.";
|
||||
String message = "The IDE " + problem + ".\nPossible reason: " + reason + ".\n\n" + advice +
|
||||
"\n\n-----\nLocation: " + path + "\n" + e.getClass().getName() + ": " + e.getMessage();
|
||||
Main.showMessage(title, message, true);
|
||||
@@ -478,25 +429,13 @@ public final class StartupUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static synchronized SocketLock.ActivateStatusAndResponse lockSystemFolders(@NotNull String[] args) {
|
||||
if (ourSocketLock != null) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
private static synchronized void lockSystemDirs(String[] args) throws IOException {
|
||||
if (ourSocketLock != null) throw new AssertionError();
|
||||
ourSocketLock = new SocketLock(PathManager.getConfigPath(), PathManager.getSystemPath());
|
||||
|
||||
SocketLock.ActivateStatusAndResponse status;
|
||||
try {
|
||||
status = ourSocketLock.lockAndTryActivate(args);
|
||||
}
|
||||
catch (Exception e) {
|
||||
Main.showMessage("Cannot Lock System Folders", e);
|
||||
return SocketLock.ActivateStatusAndResponse.emptyResponse(SocketLock.ActivateStatus.CANNOT_ACTIVATE);
|
||||
}
|
||||
|
||||
switch (status.getActivateStatus()) {
|
||||
case NO_INSTANCE:
|
||||
Pair<SocketLock.ActivationStatus, CliResult> status = ourSocketLock.lockAndTryActivate(args);
|
||||
switch (status.first) {
|
||||
case NO_INSTANCE: {
|
||||
ShutDownTracker.getInstance().registerShutdownTask(() -> {
|
||||
//noinspection SynchronizeOnThis
|
||||
synchronized (StartupUtil.class) {
|
||||
@@ -505,15 +444,33 @@ public final class StartupUtil {
|
||||
}
|
||||
});
|
||||
break;
|
||||
case ACTIVATED:
|
||||
}
|
||||
|
||||
case ACTIVATED: {
|
||||
CliResult result = status.second;
|
||||
String message = result.getMessage();
|
||||
if (message == null) message = "Already running";
|
||||
//noinspection UseOfSystemOutOrSystemErr
|
||||
System.out.println("Already running");
|
||||
break;
|
||||
case CANNOT_ACTIVATE:
|
||||
System.out.println(message);
|
||||
System.exit(result.getReturnCode());
|
||||
}
|
||||
|
||||
case CANNOT_ACTIVATE: {
|
||||
String message = "Only one instance of " + ApplicationNamesInfo.getInstance().getProductName() + " can be run at a time.";
|
||||
Main.showMessage("Too Many Instances", message, true);
|
||||
System.exit(Main.INSTANCE_CHECK_FAILED);
|
||||
}
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
private static Logger setupLogger() {
|
||||
Logger.setFactory(new LoggerFactory());
|
||||
Logger log = Logger.getInstance(Main.class);
|
||||
log.info("------------------------------------------------------ IDE STARTED ------------------------------------------------------");
|
||||
ShutDownTracker.getInstance().registerShutdownTask(() -> {
|
||||
log.info("------------------------------------------------------ IDE SHUTDOWN ------------------------------------------------------");
|
||||
});
|
||||
return log;
|
||||
}
|
||||
|
||||
private static void fixProcessEnvironment(Logger log) {
|
||||
@@ -526,7 +483,6 @@ public final class StartupUtil {
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("SpellCheckingInspection")
|
||||
private static void setupSystemLibraries() {
|
||||
String ideTempPath = PathManager.getTempPath();
|
||||
|
||||
@@ -647,7 +603,7 @@ public final class StartupUtil {
|
||||
//
|
||||
// At the same time, http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=6209673 seems to be now fixed.
|
||||
//
|
||||
// This matters only if swing.bufferPerWindow = true and we don't invoke JComponent.getGraphics() directly.
|
||||
// This matters only if {@code swing.bufferPerWindow = true} and we don't invoke JComponent.getGraphics() directly.
|
||||
//
|
||||
// True double buffering is needed to eliminate tearing on blit-accelerated scrolling and to restore
|
||||
// frame buffer content without the usual repainting, even when the EDT is blocked.
|
||||
@@ -699,8 +655,7 @@ public final class StartupUtil {
|
||||
IconLoader.activate();
|
||||
// we don't set AppUIUtil.updateForDarcula(false) because light is default
|
||||
}
|
||||
catch (Exception ignore) {
|
||||
}
|
||||
catch (Exception ignore) { }
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -11,9 +11,7 @@ import java.util.concurrent.Future;
|
||||
public final class MainImpl {
|
||||
private MainImpl() { }
|
||||
|
||||
/**
|
||||
* Called from PluginManager via reflection.
|
||||
*/
|
||||
/** Called via reflection from {@code MainRunner#start}. */
|
||||
public static void start(@NotNull String[] args) throws Exception {
|
||||
System.setProperty(PlatformUtils.PLATFORM_PREFIX_KEY, PlatformUtils.getPlatformPrefix(PlatformUtils.IDEA_CE_PREFIX));
|
||||
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
// Copyright 2000-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.idea;
|
||||
|
||||
import com.intellij.openapi.util.SystemInfo;
|
||||
import com.intellij.testFramework.rules.TempDirectory;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.TimeoutUtil;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
|
||||
@@ -29,8 +30,8 @@ public class LockSupportTest {
|
||||
SocketLock lock1 = new SocketLock(path1 + "/c", path1 + "/s");
|
||||
SocketLock lock2 = new SocketLock(path2 + "/c", path2 + "/s");
|
||||
try {
|
||||
assertEquals(SocketLock.ActivateStatus.NO_INSTANCE, tryActivate(lock1));
|
||||
assertEquals(SocketLock.ActivateStatus.ACTIVATED, tryActivate(lock2));
|
||||
assertEquals(SocketLock.ActivationStatus.NO_INSTANCE, tryActivate(lock1));
|
||||
assertEquals(SocketLock.ActivationStatus.ACTIVATED, tryActivate(lock2));
|
||||
}
|
||||
finally {
|
||||
lock1.dispose();
|
||||
@@ -42,7 +43,7 @@ public class LockSupportTest {
|
||||
public void testLock() throws Exception {
|
||||
SocketLock lock = new SocketLock(tempDir.getRoot().getPath() + "/c", tempDir.getRoot().getPath() + "/s");
|
||||
try {
|
||||
assertEquals(SocketLock.ActivateStatus.NO_INSTANCE, lock.lockAndTryActivate().getActivateStatus());
|
||||
assertEquals(SocketLock.ActivationStatus.NO_INSTANCE, lock.lockAndTryActivate(ArrayUtil.EMPTY_STRING_ARRAY).first);
|
||||
}
|
||||
finally {
|
||||
lock.dispose();
|
||||
@@ -53,41 +54,35 @@ public class LockSupportTest {
|
||||
public void testTwoLocks() throws Exception {
|
||||
List<SocketLock> toClose = new ArrayList<>();
|
||||
try {
|
||||
assertEquals(SocketLock.ActivateStatus.NO_INSTANCE, createLockAndTryActivate(toClose, tempDir.getRoot(), "c1", "s1"));
|
||||
assertEquals(SocketLock.ActivateStatus.NO_INSTANCE, createLockAndTryActivate(toClose, tempDir.getRoot(), "c2", "s2"));
|
||||
assertEquals(SocketLock.ActivateStatus.NO_INSTANCE, createLockAndTryActivate(toClose, tempDir.getRoot(), "c3", "s3"));
|
||||
assertEquals(SocketLock.ActivationStatus.NO_INSTANCE, createLockAndTryActivate(toClose, tempDir.getRoot(), "c1", "s1"));
|
||||
assertEquals(SocketLock.ActivationStatus.NO_INSTANCE, createLockAndTryActivate(toClose, tempDir.getRoot(), "c2", "s2"));
|
||||
assertEquals(SocketLock.ActivationStatus.NO_INSTANCE, createLockAndTryActivate(toClose, tempDir.getRoot(), "c3", "s3"));
|
||||
|
||||
assertEquals(SocketLock.ActivateStatus.ACTIVATED, createLockAndTryActivate(toClose, tempDir.getRoot(), "c1", "s1"));
|
||||
assertEquals(SocketLock.ActivateStatus.ACTIVATED, createLockAndTryActivate(toClose, tempDir.getRoot(), "c2", "s2"));
|
||||
assertEquals(SocketLock.ActivateStatus.ACTIVATED, createLockAndTryActivate(toClose, tempDir.getRoot(), "c3", "s3"));
|
||||
assertEquals(SocketLock.ActivationStatus.ACTIVATED, createLockAndTryActivate(toClose, tempDir.getRoot(), "c1", "s1"));
|
||||
assertEquals(SocketLock.ActivationStatus.ACTIVATED, createLockAndTryActivate(toClose, tempDir.getRoot(), "c2", "s2"));
|
||||
assertEquals(SocketLock.ActivationStatus.ACTIVATED, createLockAndTryActivate(toClose, tempDir.getRoot(), "c3", "s3"));
|
||||
}
|
||||
finally {
|
||||
toClose.forEach(SocketLock::dispose);
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static SocketLock.ActivateStatus createLockAndTryActivate(@NotNull List<SocketLock> toClose, @NotNull File dir, @NotNull String cfg, @NotNull String sys)
|
||||
throws Exception {
|
||||
SocketLock lock = new SocketLock(dir.getPath() + "/" + cfg, dir.getPath() + "/" + sys);
|
||||
private static SocketLock.ActivationStatus createLockAndTryActivate(List<SocketLock> toClose, File dir, String cfg, String sys) throws Exception {
|
||||
SocketLock lock = new SocketLock(dir.getPath() + '/' + cfg, dir.getPath() + '/' + sys);
|
||||
toClose.add(lock);
|
||||
|
||||
return tryActivate(lock);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static SocketLock.ActivateStatus tryActivate(@NotNull SocketLock lock) throws Exception {
|
||||
for (int attempt = 0; attempt < 10; attempt++) {
|
||||
private static SocketLock.ActivationStatus tryActivate(SocketLock lock) throws Exception {
|
||||
// on a same JVM, locking an already locked file results in `OverlappingFileLockException` instead of blocking
|
||||
while (true) {
|
||||
try {
|
||||
return lock.lockAndTryActivate().getActivateStatus();
|
||||
return lock.lockAndTryActivate(ArrayUtil.EMPTY_STRING_ARRAY).first;
|
||||
}
|
||||
// The case of creating two parallel locks on one JVM
|
||||
catch (OverlappingFileLockException e) {
|
||||
//noinspection BusyWait
|
||||
Thread.sleep(1000);
|
||||
TimeoutUtil.sleep(10);
|
||||
}
|
||||
}
|
||||
throw new AssertionError("Count not finish waiting for the config/system files to unlock");
|
||||
}
|
||||
|
||||
@Test(timeout = 30000)
|
||||
@@ -95,17 +90,17 @@ public class LockSupportTest {
|
||||
SocketLock lock1 = new SocketLock(tempDir.getRoot().getPath() + "/c", tempDir.getRoot().getPath() + "/s");
|
||||
SocketLock lock2 = new SocketLock(tempDir.getRoot().getPath() + "/c", tempDir.getRoot().getPath() + "/s");
|
||||
|
||||
assertEquals(SocketLock.ActivateStatus.NO_INSTANCE, tryActivate(lock1));
|
||||
assertEquals(SocketLock.ActivateStatus.ACTIVATED, tryActivate(lock2));
|
||||
assertEquals(SocketLock.ActivationStatus.NO_INSTANCE, tryActivate(lock1));
|
||||
assertEquals(SocketLock.ActivationStatus.ACTIVATED, tryActivate(lock2));
|
||||
|
||||
lock1.dispose();
|
||||
assertEquals(SocketLock.ActivateStatus.NO_INSTANCE, tryActivate(lock2));
|
||||
assertEquals(SocketLock.ActivationStatus.NO_INSTANCE, tryActivate(lock2));
|
||||
lock2.dispose();
|
||||
}
|
||||
|
||||
@Test(timeout = 30000, expected = IllegalArgumentException.class)
|
||||
public void testPathCollision() {
|
||||
String path = tempDir.getRoot().getPath() + "/d";
|
||||
new SocketLock(path, path).dispose();
|
||||
new SocketLock(path, path);
|
||||
}
|
||||
}
|
||||
@@ -79,7 +79,7 @@ public final class ActivityImpl implements Activity {
|
||||
return parallelActivity;
|
||||
}
|
||||
|
||||
// and how do we can sort correctly, when parent item equals to child (start and end) and also there is another child with start equals to end?
|
||||
// and how do we can sort correctly, when parent item equals to child (start and end), also there is another child with start equals to end?
|
||||
// so, parent added to API but as it was not enough, decided to measure time in nanoseconds instead of ms to mitigate such situations
|
||||
@Override
|
||||
@NotNull
|
||||
@@ -124,7 +124,7 @@ public final class ActivityImpl implements Activity {
|
||||
if (description != null) {
|
||||
this.description = description;
|
||||
}
|
||||
assert end == 0;
|
||||
assert end == 0 : "not started or already ended";
|
||||
end = System.nanoTime();
|
||||
StartUpMeasurer.add(this);
|
||||
}
|
||||
@@ -140,4 +140,4 @@ public final class ActivityImpl implements Activity {
|
||||
public String toString() {
|
||||
return "ActivityImpl(name=" + name + ", start=" + TimeUnit.NANOSECONDS.toMillis(start) + ", end=" + TimeUnit.NANOSECONDS.toMillis(end) + ")";
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,10 +20,10 @@ import java.util.Map;
|
||||
public final class JetBrainsProtocolHandler {
|
||||
public static final String PROTOCOL = "jetbrains://";
|
||||
public static final String FRAGMENT_PARAM_NAME = "__fragment";
|
||||
public static final String REQUIRED_PLUGINS_KEY = "idea.required.plugins.id";
|
||||
|
||||
private static String ourMainParameter = null;
|
||||
private static String ourCommand = null;
|
||||
public static final String REQUIRED_PLUGINS_KEY = "idea.required.plugins.id";
|
||||
private static Map<String, String> ourParameters = Collections.emptyMap();
|
||||
private static boolean initialized = false;
|
||||
|
||||
@@ -111,4 +111,14 @@ public final class JetBrainsProtocolHandler {
|
||||
init();
|
||||
return ourParameters;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isShutdownCommand() {
|
||||
return "shutdown".equals(getCommand());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public static String[] checkForJetBrainsProtocolCommand(@NotNull String[] args) {
|
||||
String property = System.getProperty(JetBrainsProtocolHandler.class.getName());
|
||||
return property != null ? new String[]{property} : args;
|
||||
}
|
||||
}
|
||||
@@ -9,13 +9,13 @@ import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
/**
|
||||
* Encapsulates EDT-related checks and processing. The general idea is that intellij threading model is tightly bound with EDT
|
||||
* <p>Encapsulates EDT-related checks and processing. The general idea is that IntelliJ threading model is tightly bound with EDT
|
||||
* (e.g. write access is allowed from EDT only and any task executed from EDT is implicitly granted read access). That makes
|
||||
* a huge bottleneck in non-intellij environments like upsource - every vcs revision there is represented as a separate ide project
|
||||
* object, hence, global shared write lock and single EDT become a problem.
|
||||
* <p/>
|
||||
* That's why it should be possible to change that model in non-intellij environment - that involves either custom read/write locks
|
||||
* processing or custom EDT processing as well. This interface covers EDT part.
|
||||
* a huge bottleneck in non-IntelliJ environments like Upsource - every vcs revision there is represented as a separate ide project
|
||||
* object, hence, global shared write lock and single EDT become a problem.</p>
|
||||
*
|
||||
* <p>That's why it should be possible to change that model in non-IntelliJ environment - that involves either custom read/write locks
|
||||
* processing or custom EDT processing as well. This interface covers EDT part.</p>
|
||||
*/
|
||||
public abstract class EdtInvocationManager {
|
||||
private static final AtomicReference<EdtInvocationManager> ourInstance = new AtomicReference<>();
|
||||
@@ -38,7 +38,7 @@ public abstract class EdtInvocationManager {
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // Used in upsource
|
||||
@SuppressWarnings("unused") // Used in Upsource
|
||||
public static void setEdtInvocationManager(@NotNull EdtInvocationManager edtInvocationManager) {
|
||||
ourInstance.set(edtInvocationManager);
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public abstract class EdtInvocationManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* The default {@link EdtInvocationManager} implementation which works with the EDT via SwingUtilities.
|
||||
* The default {@link EdtInvocationManager} implementation that uses {@link EventQueue}.
|
||||
*/
|
||||
public static class SwingEdtInvocationManager extends EdtInvocationManager {
|
||||
@Override
|
||||
@@ -88,7 +88,6 @@ public abstract class EdtInvocationManager {
|
||||
|
||||
@Override
|
||||
public void invokeLater(@NotNull Runnable task) {
|
||||
//noinspection SSBasedInspection
|
||||
EventQueue.invokeLater(task);
|
||||
}
|
||||
|
||||
@@ -97,4 +96,4 @@ public abstract class EdtInvocationManager {
|
||||
EventQueue.invokeAndWait(task);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user