Revert: avoid smart pointer leaks after project closing by not storing them in VFS user data at all

because of leaks when using light files (IDEA-187382)
This commit is contained in:
peter
2018-03-01 18:44:05 +01:00
parent 9ba9a1fd32
commit 359b37fd0d
3 changed files with 70 additions and 46 deletions
@@ -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<SmartPointerEx<PsiClass>> key = Key.create("smart pointer test");
Runnable createFileAndPointer = () -> {
PsiFile file = PsiFileFactory.getInstance(myProject).createFileFromText("a.java", JavaLanguage.INSTANCE, "class Foo {}", true, false);
SmartPointerEx<PsiClass> 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);
}
}
@@ -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<SmartPointerTracker> POINTERS_KEY;
private final PsiDocumentManagerBase myPsiDocManager;
private final ReferenceQueue<SmartPsiElementPointerImpl> myQueue = new ReferenceQueue<>();
private final ConcurrentMap<VirtualFile, SmartPointerTracker> 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<E> 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;
}
@@ -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<SmartPsiElementPointerImpl> 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<VirtualFile, SmartPointerTracker> trackerMap) {
return trackerMap.get(file) == this;
boolean isActual(VirtualFile file, Key<SmartPointerTracker> 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<VirtualFile, SmartPointerTracker> trackerMap) {
synchronized void removeReference(@NotNull PointerReference reference, @NotNull Key<SmartPointerTracker> 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<VirtualFile, SmartPointerTracker> trackerMap, VirtualFile file) {
assert isActual(file, trackerMap) : "Smart pointer list mismatch mismatch:" +
(trackerMap.containsKey(file) ? "; has another pointer list" : "");
private void assertActual(Key<SmartPointerTracker> expectedKey, VirtualFile file, Key<SmartPointerTracker> 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<SmartPsiElementPointerImpl<?>> 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<SmartPsiElementPointerImpl> {
@NotNull final VirtualFile file;
@NotNull final Map<VirtualFile, SmartPointerTracker> trackerMap;
@NotNull private final VirtualFile file;
@NotNull private final Key<SmartPointerTracker> key;
private int index = -2;
PointerReference(@NotNull SmartPsiElementPointerImpl<?> pointer,
@NotNull VirtualFile containingFile,
@NotNull Map<VirtualFile, SmartPointerTracker> trackerMap,
@NotNull ReferenceQueue<SmartPsiElementPointerImpl> queue) {
super(pointer, queue);
@NotNull Key<SmartPointerTracker> 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);
}
}
}
}