Cleanup (leaner code in start-up utils; typos; formatting)

GitOrigin-RevId: 7364ef3a09389e516f798862c3a8a60eded0d221
This commit is contained in:
Roman Shevchenko
2019-09-04 19:20:39 +00:00
committed by intellij-monorepo-bot
parent 26e938e533
commit cb69f4c7d3
8 changed files with 246 additions and 360 deletions
@@ -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);
}
}
}
}