PY-51096 Don't get list of sdks inside inspections

(cherry picked from commit 3f0c00cbf6c583211d7cbc576a04bfb35d5edd1d)

IJ-MR-15111

GitOrigin-RevId: 055b6649689e75fa5039f43c6ab4c9f61a314ad6
This commit is contained in:
Vladimir Lagunov
2021-10-11 17:09:42 +07:00
committed by intellij-monorepo-bot
parent 286a57eef9
commit 7501339247
3 changed files with 75 additions and 18 deletions

View File

@@ -79,5 +79,6 @@
<orderEntry type="library" name="jcef" level="project" />
<orderEntry type="library" name="Velocity" level="project" />
<orderEntry type="library" name="jna" level="project" />
<orderEntry type="library" name="caffeine" level="project" />
</component>
</module>

View File

@@ -13,6 +13,15 @@
<!-- Enable command-line language -->
<xi:include href="/META-INF/command-line.xml" xpointer="xpointer(/idea-plugin/*)"/>
<projectListeners>
<listener
class="com.jetbrains.python.inspections.PyInterpreterInspection$Visitor$CacheCleaner"
topic="com.intellij.openapi.roots.ModuleRootListener"/>
<listener
class="com.jetbrains.python.inspections.PyInterpreterInspection$Visitor$CacheCleaner"
topic="com.intellij.openapi.projectRoots.ProjectJdkTable$Listener"/>
</projectListeners>
<extensions defaultExtensionNs="com.intellij">
<library.type implementation="com.jetbrains.python.library.PythonLibraryType"/>
<renameHandler implementation="com.jetbrains.python.magicLiteral.PyMagicLiteralRenameHandler"/>

View File

@@ -1,6 +1,8 @@
// Copyright 2000-2020 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.jetbrains.python.inspections;
import com.github.benmanes.caffeine.cache.AsyncLoadingCache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.intellij.codeInspection.LocalInspectionToolSession;
import com.intellij.codeInspection.LocalQuickFix;
import com.intellij.codeInspection.ProblemDescriptor;
@@ -9,18 +11,19 @@ import com.intellij.codeInspection.util.InspectionMessage;
import com.intellij.codeInspection.util.IntentionFamilyName;
import com.intellij.codeInspection.util.IntentionName;
import com.intellij.ide.actions.ShowSettingsUtilImpl;
import com.intellij.openapi.application.ex.ApplicationUtil;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.module.Module;
import com.intellij.openapi.module.ModuleManager;
import com.intellij.openapi.module.ModuleUtilCore;
import com.intellij.openapi.options.ex.ConfigurableExtensionPointUtil;
import com.intellij.openapi.options.ex.ConfigurableVisitor;
import com.intellij.openapi.progress.ProcessCanceledException;
import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.*;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.projectRoots.ProjectJdkTable;
import com.intellij.openapi.projectRoots.Sdk;
import com.intellij.openapi.projectRoots.impl.SdkConfigurationUtil;
import com.intellij.openapi.roots.ModuleRootEvent;
import com.intellij.openapi.roots.ModuleRootListener;
import com.intellij.openapi.roots.ModuleRootManager;
import com.intellij.openapi.roots.ProjectRootManager;
import com.intellij.openapi.roots.ui.configuration.ProjectSettingsService;
@@ -31,6 +34,7 @@ import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiElementVisitor;
import com.intellij.util.PathUtil;
import com.intellij.util.PlatformUtils;
import com.intellij.util.concurrency.AppExecutorUtil;
import com.intellij.util.containers.ContainerUtil;
import com.jetbrains.python.PyPsiBundle;
import com.jetbrains.python.PythonIdeLanguageCustomization;
@@ -48,10 +52,10 @@ import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.time.Duration;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -73,6 +77,21 @@ public final class PyInterpreterInspection extends PyInspection {
}
public static class Visitor extends PyInspectionVisitor {
/** Invalidated by {@link CacheCleaner}. */
private static final AsyncLoadingCache<Module, List<PyDetectedSdk>> DETECTED_ASSOCIATED_ENVS_CACHE = Caffeine.newBuilder()
.executor(AppExecutorUtil.getAppExecutorService())
// Even though various listeners invalidate the cache on many actions, it's unfeasible to track for venv/conda interpreters
// creation performed outside the IDE.
// 20 seconds timeout is taken at random.
.expireAfterWrite(Duration.ofSeconds(20))
.weakKeys()
.buildAsync(module -> {
final List<Sdk> existingSdks = getExistingSdks();
final UserDataHolderBase context = new UserDataHolderBase();
return PySdkExtKt.detectAssociatedEnvironments(module, existingSdks, context);
});
public Visitor(@Nullable ProblemsHolder holder,
@NotNull TypeEvalContext context) {
@@ -171,17 +190,19 @@ public final class PyInterpreterInspection extends PyInspection {
final UserDataHolderBase context = new UserDataHolderBase();
List<PyDetectedSdk> detectedAssociatedEnvs = Collections.emptyList();
try {
detectedAssociatedEnvs = ApplicationUtil.runWithCheckCanceled(
() -> PySdkExtKt.detectAssociatedEnvironments(module, existingSdks, context),
ProgressManager.getInstance().getProgressIndicator()
);
}
catch (ProcessCanceledException e) {
throw e;
}
catch (Exception e) {
LOGGER.warn(e);
while (true) {
try {
// Beware that this thread holds the read lock. Shouldn't wait too much.
detectedAssociatedEnvs = DETECTED_ASSOCIATED_ENVS_CACHE.get(module).get(10, TimeUnit.MILLISECONDS);
break;
}
catch (InterruptedException | TimeoutException ignored) {
ProgressManager.checkCanceled();
}
catch (Exception e) {
LOGGER.warn("Failed to get suitable sdk fix for name " + name + " and module " + module, e);
break;
}
}
final var detectedAssociatedSdk = ContainerUtil.getFirstItem(detectedAssociatedEnvs);
if (detectedAssociatedSdk != null) return new UseDetectedInterpreterFix(detectedAssociatedSdk, existingSdks, true, module);
@@ -285,6 +306,32 @@ public final class PyInterpreterInspection extends PyInspection {
private static String getEnvRootName(@Nullable File envRoot) {
return envRoot == null ? null : PathUtil.getFileName(envRoot.getPath());
}
private static class CacheCleaner implements ModuleRootListener, ProjectJdkTable.Listener {
@Override
public void beforeRootsChange(@NotNull ModuleRootEvent event) {
invalidate();
}
@Override
public void jdkAdded(@NotNull Sdk jdk) {
invalidate();
}
@Override
public void jdkRemoved(@NotNull Sdk jdk) {
invalidate();
}
@Override
public void jdkNameChanged(@NotNull Sdk jdk, @NotNull String previousName) {
invalidate();
}
private void invalidate() {
DETECTED_ASSOCIATED_ENVS_CACHE.synchronous().invalidateAll();
}
}
}
@Nullable