do not rely on ExternalAnnotatorManager.wait(), wait for this particular task instead to fix IJPL-28996 Lock in ExternalAnnotatorManager.waitForAllExecuted

GitOrigin-RevId: 71a742c28d352e58e79b761195991412a60498ef
This commit is contained in:
Alexey Kudravtsev
2024-07-23 10:17:10 +02:00
committed by intellij-monorepo-bot
parent 3f22b0fdbe
commit 1536b4f8fe

View File

@@ -26,20 +26,21 @@ import com.intellij.openapi.progress.ProgressManager;
import com.intellij.openapi.progress.util.BackgroundTaskUtil; import com.intellij.openapi.progress.util.BackgroundTaskUtil;
import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.DumbService;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.vfs.VirtualFile; import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
import com.intellij.psi.FileViewProvider; import com.intellij.psi.FileViewProvider;
import com.intellij.psi.PsiFile; import com.intellij.psi.PsiFile;
import com.intellij.util.TimeoutUtil;
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.ui.update.Update; import com.intellij.util.ui.update.Update;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.function.Function; import java.util.function.Function;
public final class ExternalToolPass extends ProgressableTextEditorHighlightingPass implements DumbAware { public final class ExternalToolPass extends ProgressableTextEditorHighlightingPass implements DumbAware {
@@ -47,6 +48,7 @@ public final class ExternalToolPass extends ProgressableTextEditorHighlightingPa
private final List<MyData<?,?>> myAnnotationData = Collections.synchronizedList(new ArrayList<>()); private final List<MyData<?,?>> myAnnotationData = Collections.synchronizedList(new ArrayList<>());
private volatile @NotNull List<? extends HighlightInfo> myHighlightInfos = Collections.emptyList(); private volatile @NotNull List<? extends HighlightInfo> myHighlightInfos = Collections.emptyList();
private volatile boolean externalUpdateTaskCompleted; // true when the task in ExternalAnnotatorManager.getInstance().queue() was completed
private static final class MyData<K,V> { private static final class MyData<K,V> {
final @NotNull ExternalAnnotator<K,V> annotator; final @NotNull ExternalAnnotator<K,V> annotator;
@@ -145,31 +147,41 @@ public final class ExternalToolPass extends ProgressableTextEditorHighlightingPa
ExternalAnnotatorManager.getInstance().queue(new Update(myFile) { ExternalAnnotatorManager.getInstance().queue(new Update(myFile) {
@Override @Override
public void setRejected() { public void setRejected() {
super.setRejected(); try {
if (!myProject.isDisposed()) { // Project close in EDT might call MergeUpdateQueue.dispose which calls setRejected in EDT super.setRejected();
doFinish(); if (!myProject.isDisposed()) { // Project close in EDT might call MergeUpdateQueue.dispose which calls setRejected in EDT
doFinish();
}
}
finally {
externalUpdateTaskCompleted = true;
} }
} }
@Override @Override
public void run() { public void run() {
if (documentChanged(modificationStampBefore) || myProject.isDisposed()) { try {
return; if (documentChanged(modificationStampBefore) || myProject.isDisposed()) {
} return;
}
// have to instantiate new indicator because the old one (progress) might have already been canceled // have to instantiate new indicator because the old one (progress) might have already been canceled
DaemonProgressIndicator indicator = new DaemonProgressIndicator(); DaemonProgressIndicator indicator = new DaemonProgressIndicator();
BackgroundTaskUtil.runUnderDisposeAwareIndicator(myProject, () -> { BackgroundTaskUtil.runUnderDisposeAwareIndicator(myProject, () -> {
// run annotators outside the read action because they could start OSProcessHandler // run annotators outside the read action because they could start OSProcessHandler
runChangeAware(myDocument, () -> doAnnotate()); runChangeAware(myDocument, () -> doAnnotate());
ReadAction.run(() -> { ReadAction.run(() -> {
ProgressManager.checkCanceled(); ProgressManager.checkCanceled();
if (!documentChanged(modificationStampBefore)) { if (!documentChanged(modificationStampBefore)) {
doApply(); doApply();
doFinish(); doFinish();
} }
}); });
}, indicator); }, indicator);
}
finally {
externalUpdateTaskCompleted = true;
}
} }
}); });
} }
@@ -177,11 +189,13 @@ public final class ExternalToolPass extends ProgressableTextEditorHighlightingPa
@Override @Override
public @NotNull List<HighlightInfo> getInfos() { public @NotNull List<HighlightInfo> getInfos() {
ApplicationManager.getApplication().assertIsNonDispatchThread(); ApplicationManager.getApplication().assertIsNonDispatchThread();
try { long deadline = System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(60);
ExternalAnnotatorManager.getInstance().waitForAllExecuted(1, TimeUnit.MINUTES);
} while (!externalUpdateTaskCompleted) {
catch (ExecutionException | InterruptedException | TimeoutException e) { ProgressManager.checkCanceled();
throw new RuntimeException(e); TimeoutUtil.sleep(1);
Thread.yield();
if (System.currentTimeMillis() > deadline) break;
} }
//noinspection unchecked //noinspection unchecked
return (List<HighlightInfo>)myHighlightInfos; return (List<HighlightInfo>)myHighlightInfos;
@@ -231,16 +245,19 @@ public final class ExternalToolPass extends ProgressableTextEditorHighlightingPa
} }
} }
@RequiresBackgroundThread
private void doFinish() { private void doFinish() {
List<HighlightInfo> highlights = myAnnotationData.stream() List<HighlightInfo> highlights = myAnnotationData.stream()
.flatMap(data -> .flatMap(data ->
ContainerUtil.notNullize(data.annotationHolder).stream().map(annotation -> HighlightInfo.fromAnnotation(data.annotator, annotation))) ContainerUtil.notNullize(data.annotationHolder).stream().map(annotation -> HighlightInfo.fromAnnotation(data.annotator, annotation)))
.toList(); .toList();
MarkupModelEx markupModel = (MarkupModelEx)DocumentMarkupModel.forDocument(myDocument, myProject, true); MarkupModelEx markupModel = (MarkupModelEx)DocumentMarkupModel.forDocument(myDocument, myProject, true);
// use the method which doesn't retrieve a HighlightingSession from the indicator, because we likely destroyed the one already HighlightingSessionImpl.runInsideHighlightingSession(myFile, getColorsScheme(), ProperTextRange.create(myFile.getTextRange()), false, session -> {
BackgroundUpdateHighlightersUtil.setHighlightersInRange(myRestrictRange, highlights, markupModel, getId(), getHighlightingSession()); // use the method which doesn't retrieve a HighlightingSession from the indicator, because we likely destroyed the one already
DaemonCodeAnalyzerEx.getInstanceEx(myProject).getFileStatusMap().markFileUpToDate(myDocument, getId()); BackgroundUpdateHighlightersUtil.setHighlightersInRange(myRestrictRange, highlights, markupModel, getId(), session);
myHighlightInfos = highlights; DaemonCodeAnalyzerEx.getInstanceEx(myProject).getFileStatusMap().markFileUpToDate(myDocument, getId());
myHighlightInfos = highlights;
});
} }
private static void processError(@NotNull Throwable t, @NotNull ExternalAnnotator<?,?> annotator, @NotNull PsiFile root) { private static void processError(@NotNull Throwable t, @NotNull ExternalAnnotator<?,?> annotator, @NotNull PsiFile root) {