From cb69f4c7d3e50d1cb34ebcaa5f2f2928eb5cc7d1 Mon Sep 17 00:00:00 2001 From: Roman Shevchenko Date: Wed, 4 Sep 2019 22:07:02 +0300 Subject: [PATCH] Cleanup (leaner code in start-up utils; typos; formatting) GitOrigin-RevId: 7364ef3a09389e516f798862c3a8a60eded0d221 --- .../bootstrap/src/com/intellij/idea/Main.java | 4 +- .../src/com/intellij/idea/SocketLock.java | 305 +++++++----------- .../src/com/intellij/idea/StartupUtil.java | 203 +++++------- .../src/com/intellij/idea/MainImpl.java | 4 +- .../com/intellij/idea/LockSupportTest.java | 51 ++- .../com/intellij/diagnostic/ActivityImpl.java | 6 +- .../application/JetBrainsProtocolHandler.java | 14 +- .../util/ui/EdtInvocationManager.java | 19 +- 8 files changed, 246 insertions(+), 360 deletions(-) diff --git a/platform/bootstrap/src/com/intellij/idea/Main.java b/platform/bootstrap/src/com/intellij/idea/Main.java index 54c8e79d7999..b94829dc48b0 100644 --- a/platform/bootstrap/src/com/intellij/idea/Main.java +++ b/platform/bootstrap/src/com/intellij/idea/Main.java @@ -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; diff --git a/platform/platform-impl/src/com/intellij/idea/SocketLock.java b/platform/platform-impl/src/com/intellij/idea/SocketLock.java index db190e891d7e..812e969c9cc3 100644 --- a/platform/platform-impl/src/com/intellij/idea/SocketLock.java +++ b/platform/platform-impl/src/com/intellij/idea/SocketLock.java @@ -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 myActivateListener = new AtomicReference<>(); + private final AtomicReference, Future>> myCommandProcessorRef = new AtomicReference<>(); private final String myConfigPath; private final String mySystemPath; - private final BlockingQueue myLockedFiles = new LinkedBlockingQueue<>(); - @Nullable private Future myBuiltinServerFuture; + private final List myLockedFiles = new ArrayList<>(2); + private @Nullable Future 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, Future> 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 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 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> entry : portToPath.entrySet()) { - ActivateStatusAndResponse status = tryActivate(entry.getKey(), entry.getValue(), args); - if (status.getActivateStatus() != ActivateStatus.NO_INSTANCE) { - log("exit: lock(): " + status.getActivateStatus()); + Pair 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 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(); } /** -

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. - -

Unlocking of port files (via {@link #unlockPortFiles}) happens either after builtin server init, or on app termination. - -

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. + *

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.

+ * + *

Unlocking of port files (via {@link #unlockPortFiles}) happens either after builtin server init, or on app termination.

+ * + *

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.

*/ 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 portToPath) { - if (portMarker.exists()) { + private static void readPort(String path, MultiMap 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 paths, @NotNull String[] args) { + private Pair tryActivate(int portNumber, Collection 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 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 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 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 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 process(@NotNull List args); + catch (Exception ignore) { } } private static final class MyChannelInboundHandler extends MessageDecoder { - private enum State {HEADER, CONTENT} + private final String[] myLockedPaths; - private final AtomicReference myActivateListener; + private final AtomicReference, Future>> myCommandProcessorRef; private final String myToken; private State myState = State.HEADER; MyChannelInboundHandler(@NotNull String[] lockedPaths, - @NotNull AtomicReference activateListener, + @NotNull AtomicReference, Future>> 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 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, Future> 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 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 readStringSequence(DataInputStream in) { List result = new ArrayList<>(); while (true) { @@ -485,13 +449,12 @@ public final class SocketLock { return result; } - @NotNull - private static CliResult mapResponseToCliResult(@NotNull List responseParts) throws IllegalArgumentException { + private static CliResult mapResponseToCliResult(List 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; - } - } } \ No newline at end of file diff --git a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java index 309ee73b57e9..f5d485656b46 100644 --- a/platform/platform-impl/src/com/intellij/idea/StartupUtil.java +++ b/platform/platform-impl/src/com/intellij/idea/StartupUtil.java @@ -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, Future> 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 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 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) { } }); } diff --git a/platform/platform-main/src/com/intellij/idea/MainImpl.java b/platform/platform-main/src/com/intellij/idea/MainImpl.java index 063dc1cac94d..77f4977227f2 100644 --- a/platform/platform-main/src/com/intellij/idea/MainImpl.java +++ b/platform/platform-main/src/com/intellij/idea/MainImpl.java @@ -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)); diff --git a/platform/platform-tests/testSrc/com/intellij/idea/LockSupportTest.java b/platform/platform-tests/testSrc/com/intellij/idea/LockSupportTest.java index 2baa4d218f0a..a447ff3624b2 100644 --- a/platform/platform-tests/testSrc/com/intellij/idea/LockSupportTest.java +++ b/platform/platform-tests/testSrc/com/intellij/idea/LockSupportTest.java @@ -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 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 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 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); } } \ No newline at end of file diff --git a/platform/util/src/com/intellij/diagnostic/ActivityImpl.java b/platform/util/src/com/intellij/diagnostic/ActivityImpl.java index 3960b8529b33..42cc14abc1ed 100644 --- a/platform/util/src/com/intellij/diagnostic/ActivityImpl.java +++ b/platform/util/src/com/intellij/diagnostic/ActivityImpl.java @@ -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) + ")"; } -} +} \ No newline at end of file diff --git a/platform/util/src/com/intellij/openapi/application/JetBrainsProtocolHandler.java b/platform/util/src/com/intellij/openapi/application/JetBrainsProtocolHandler.java index 37536506462b..7469cd149a26 100644 --- a/platform/util/src/com/intellij/openapi/application/JetBrainsProtocolHandler.java +++ b/platform/util/src/com/intellij/openapi/application/JetBrainsProtocolHandler.java @@ -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 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; + } +} \ No newline at end of file diff --git a/platform/util/ui/src/com/intellij/util/ui/EdtInvocationManager.java b/platform/util/ui/src/com/intellij/util/ui/EdtInvocationManager.java index a57c84d7d630..b05d68914e39 100644 --- a/platform/util/ui/src/com/intellij/util/ui/EdtInvocationManager.java +++ b/platform/util/ui/src/com/intellij/util/ui/EdtInvocationManager.java @@ -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 + *

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. - *

- * 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.

+ * + *

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.

*/ public abstract class EdtInvocationManager { private static final AtomicReference 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); } } -} +} \ No newline at end of file