diff --git a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java index f543561e2ba2..fb2d56811233 100644 --- a/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java +++ b/java/java-tests/testSrc/com/intellij/psi/impl/smartPointers/SmartPsiElementPointersTest.java @@ -9,6 +9,7 @@ import com.intellij.ide.highlighter.HtmlFileType; import com.intellij.ide.highlighter.JavaFileType; import com.intellij.ide.highlighter.XmlFileType; import com.intellij.lang.FileASTNode; +import com.intellij.lang.java.JavaLanguage; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.command.WriteCommandAction; @@ -23,6 +24,7 @@ import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.fileTypes.PlainTextFileType; import com.intellij.openapi.fileTypes.StdFileTypes; +import com.intellij.openapi.util.Key; import com.intellij.openapi.util.Segment; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; @@ -43,6 +45,7 @@ import com.intellij.psi.xml.XmlTag; import com.intellij.testFramework.*; import com.intellij.util.FileContentUtil; import com.intellij.util.containers.ContainerUtil; +import com.intellij.util.ref.GCUtil; import gnu.trove.THashSet; import org.jetbrains.annotations.NotNull; import org.junit.Assert; @@ -1074,4 +1077,19 @@ public class SmartPsiElementPointersTest extends CodeInsightTestCase { } } + public void testDoNotLeakLightVirtualFileSmartPointersReachableViaDocument() { + Key> key = Key.create("smart pointer test"); + + Runnable createFileAndPointer = () -> { + PsiFile file = PsiFileFactory.getInstance(myProject).createFileFromText("a.java", JavaLanguage.INSTANCE, "class Foo {}", true, false); + SmartPointerEx pointer = createPointer(((PsiJavaFile)file).getClasses()[0]); + file.getViewProvider().getDocument().putUserData(key, pointer); + }; + createFileAndPointer.run(); + + GCUtil.tryGcSoftlyReachableObjects(); + SmartPointerTracker.processQueue(); + LeakHunter.checkLeak(LeakHunter.allRoots(), Document.class, d -> d.getUserData(key) != null); + } + } diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java index c751d5e123cf..9d2baf42f9aa 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerManagerImpl.java @@ -24,7 +24,6 @@ import com.intellij.openapi.editor.impl.FrozenDocument; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; -import com.intellij.openapi.util.LowMemoryWatcher; import com.intellij.openapi.util.ProperTextRange; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.vfs.VirtualFile; @@ -32,31 +31,26 @@ import com.intellij.psi.*; import com.intellij.psi.impl.PsiDocumentManagerBase; import com.intellij.psi.util.PsiUtilCore; import com.intellij.reference.SoftReference; -import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.TestOnly; import java.lang.ref.Reference; -import java.lang.ref.ReferenceQueue; import java.util.List; -import java.util.concurrent.ConcurrentMap; public class SmartPointerManagerImpl extends SmartPointerManager { private static final Logger LOG = Logger.getInstance("#com.intellij.psi.impl.smartPointers.SmartPointerManagerImpl"); private final Project myProject; + private final Key POINTERS_KEY; private final PsiDocumentManagerBase myPsiDocManager; - private final ReferenceQueue myQueue = new ReferenceQueue<>(); - private final ConcurrentMap myTrackerMap = ContainerUtil.newConcurrentMap(); public SmartPointerManagerImpl(Project project) { myProject = project; myPsiDocManager = (PsiDocumentManagerBase)PsiDocumentManager.getInstance(myProject); - LowMemoryWatcher.register(() -> processQueue(), project); + POINTERS_KEY = Key.create("SMART_POINTERS for "+project); } public void fastenBelts(@NotNull VirtualFile file) { - processQueue(); SmartPointerTracker pointers = getTracker(file); if (pointers != null) pointers.fastenBelts(); } @@ -86,7 +80,7 @@ public class SmartPointerManagerImpl extends SmartPointerManager { PsiUtilCore.ensureValid(element); LOG.error("Invalid element:" + element); } - processQueue(); + SmartPointerTracker.processQueue(); SmartPsiElementPointerImpl pointer = getCachedPointer(element); if (pointer != null && (!(pointer.getElementInfo() instanceof SelfElementInfo) || ((SelfElementInfo)pointer.getElementInfo()).isForInjected() == forInjected) && @@ -126,7 +120,7 @@ public class SmartPointerManagerImpl extends SmartPointerManager { @NotNull TextRange range, boolean forInjected) { PsiUtilCore.ensureValid(file); - processQueue(); + SmartPointerTracker.processQueue(); SmartPsiFileRangePointerImpl pointer = new SmartPsiFileRangePointerImpl(file, ProperTextRange.create(range), forInjected); trackPointer(pointer, file.getViewProvider().getVirtualFile()); @@ -137,9 +131,12 @@ public class SmartPointerManagerImpl extends SmartPointerManager { SmartPointerElementInfo info = pointer.getElementInfo(); if (!(info instanceof SelfElementInfo)) return; - SmartPointerTracker.PointerReference reference = new SmartPointerTracker.PointerReference(pointer, containingFile, myTrackerMap, myQueue); + SmartPointerTracker.PointerReference reference = new SmartPointerTracker.PointerReference(pointer, containingFile, POINTERS_KEY); while (true) { - SmartPointerTracker pointers = myTrackerMap.computeIfAbsent(containingFile, __ -> new SmartPointerTracker()); + SmartPointerTracker pointers = getTracker(containingFile); + if (pointers == null) { + pointers = containingFile.putUserDataIfAbsent(POINTERS_KEY, new SmartPointerTracker()); + } if (pointers.addReference(reference, pointer)) { break; } @@ -175,14 +172,14 @@ public class SmartPointerManagerImpl extends SmartPointerManager { SmartPointerTracker pointers = getTracker(vFile); SmartPointerTracker.PointerReference reference = ((SmartPsiElementPointerImpl)pointer).pointerReference; if (pointers != null && reference != null) { - pointers.removeReference(reference, myTrackerMap); + pointers.removeReference(reference, POINTERS_KEY); } } } @Nullable SmartPointerTracker getTracker(@NotNull VirtualFile containingFile) { - return myTrackerMap.get(containingFile); + return containingFile.getUserData(POINTERS_KEY); } @TestOnly @@ -208,19 +205,6 @@ public class SmartPointerManagerImpl extends SmartPointerManager { if (list != null) list.updatePointerTargetsAfterReparse(); } - private void processQueue() { - while (true) { - SmartPointerTracker.PointerReference reference = (SmartPointerTracker.PointerReference)myQueue.poll(); - if (reference == null) break; - - SmartPointerTracker pointers = myTrackerMap.get(reference.file); - if (pointers != null) { - pointers.removeReference(reference, reference.trackerMap); - } - } - } - - Project getProject() { return myProject; } diff --git a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java index 004b933ebb72..10e652ede273 100644 --- a/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java +++ b/platform/core-impl/src/com/intellij/psi/impl/smartPointers/SmartPointerTracker.java @@ -15,8 +15,11 @@ */ package com.intellij.psi.impl.smartPointers; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.editor.event.DocumentEvent; import com.intellij.openapi.editor.impl.FrozenDocument; +import com.intellij.openapi.util.Key; +import com.intellij.openapi.util.LowMemoryWatcher; import com.intellij.openapi.util.Segment; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiElement; @@ -33,24 +36,29 @@ import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Arrays; import java.util.List; -import java.util.Map; class SmartPointerTracker { + private static final ReferenceQueue ourQueue = new ReferenceQueue<>(); + private int nextAvailableIndex; private int size; private PointerReference[] references = new PointerReference[10]; private final MarkerCache markerCache = new MarkerCache(this); private boolean mySorted; + static { + LowMemoryWatcher.register(() -> processQueue(), ApplicationManager.getApplication()); + } + synchronized boolean addReference(@NotNull PointerReference reference, @NotNull SmartPsiElementPointerImpl pointer) { - if (!isActual(reference.file, reference.trackerMap)) { + if (!isActual(reference.file, reference.key)) { // this pointer list has been removed by another thread; clients should get/create an up-to-date list and try adding to it return false; } if (needsExpansion() || isTooSparse()) { resize(); - assert isActual(reference.file, reference.trackerMap); + assert isActual(reference.file, reference.key); } assert references[nextAvailableIndex] == null : references[nextAvailableIndex]; @@ -63,8 +71,8 @@ class SmartPointerTracker { return true; } - boolean isActual(VirtualFile file, @NotNull Map trackerMap) { - return trackerMap.get(file) == this; + boolean isActual(VirtualFile file, Key key) { + return file.getUserData(key) == this; } private boolean needsExpansion() { @@ -89,22 +97,24 @@ class SmartPointerTracker { nextAvailableIndex = index; } - synchronized void removeReference(@NotNull PointerReference reference, @NotNull Map trackerMap) { + synchronized void removeReference(@NotNull PointerReference reference, @NotNull Key expectedKey) { int index = reference.index; if (index < 0) return; - assertActual(trackerMap, reference.file); + assertActual(expectedKey, reference.file, reference.key); assert references[index] == reference : "At " + index + " expected " + reference + ", found " + references[index]; references[index].index = -1; references[index] = null; if (--size == 0) { - trackerMap.remove(reference.file, this); + reference.file.replace(reference.key, this, null); } } - private void assertActual(@NotNull Map trackerMap, VirtualFile file) { - assert isActual(file, trackerMap) : "Smart pointer list mismatch mismatch:" + - (trackerMap.containsKey(file) ? "; has another pointer list" : ""); + private void assertActual(Key expectedKey, VirtualFile file, Key refKey) { + assert isActual(file, refKey) : "Smart pointer list mismatch mismatch:" + + " ref.key=" + expectedKey + + ", manager.key=" + refKey + + (file.getUserData(refKey) != null ? "; has another pointer list" : ""); } private void processAlivePointers(@NotNull Processor> processor) { @@ -112,10 +122,10 @@ class SmartPointerTracker { PointerReference ref = references[i]; if (ref == null) continue; - assert isActual(ref.file, ref.trackerMap); + assert isActual(ref.file, ref.key); SmartPsiElementPointerImpl pointer = ref.get(); if (pointer == null) { - removeReference(ref, ref.trackerMap); + removeReference(ref, ref.key); continue; } @@ -167,6 +177,7 @@ class SmartPointerTracker { } synchronized void fastenBelts() { + processQueue(); processAlivePointers(pointer -> { pointer.getElementInfo().fastenBelt(); return true; @@ -229,19 +240,30 @@ class SmartPointerTracker { } static class PointerReference extends WeakReference { - @NotNull final VirtualFile file; - @NotNull final Map trackerMap; + @NotNull private final VirtualFile file; + @NotNull private final Key key; private int index = -2; PointerReference(@NotNull SmartPsiElementPointerImpl pointer, @NotNull VirtualFile containingFile, - @NotNull Map trackerMap, - @NotNull ReferenceQueue queue) { - super(pointer, queue); + @NotNull Key key) { + super(pointer, ourQueue); file = containingFile; - this.trackerMap = trackerMap; + this.key = key; pointer.pointerReference = this; } } + static void processQueue() { + while (true) { + PointerReference reference = (PointerReference)ourQueue.poll(); + if (reference == null) break; + + SmartPointerTracker pointers = reference.file.getUserData(reference.key); + if (pointers != null) { + pointers.removeReference(reference, reference.key); + } + } + } + }