Is it safe to cache any PSI element if I use PsiModificationTracker.MODIFICATION_COUNT

Answered

Hi,
I am curious is it safe to cache project-wide PSI elements if I use PsiModificationTracker.MODIFICATION_COUNT. If I try to cache a PSI element from another file, I get the following error:

... is retaining PSI, causing memory leaks and possible invalid element access.

The code looks like this:

val key = Key<CachedValue<FormPropertyArray<T>?>>("solar.element.array.$requiredPropertyName")
return CachedValuesManager.getCachedValue(this, key) {
val arrayElement = if(requiredPropertyName == name)
FormPropertyArray(this, valueArray, contentsClass)
else
null

CachedValueProvider.Result(
arrayElement,
PsiModificationTracker.MODIFICATION_COUNT
)
}

, where valueArray is a JsonArray (which is causing the problem).
I did a workaround by not caching it directly but reevaluating in FormPropertyArray one more time. I actually cache a lot of project-wide PSI in my project and never experienced any problems while using MODIFICATION_COUNT tracker. I know that this is a memory-hungry approach, but it is still better than searching and traversing multiple JSON files every time to find the required element.
So, do I need to care about this error and stop caching PSI, or can I just ignore that?
There is a link to repository with code, if it helps.

0
3 comments

Hi,

Could you please share the full error stacktrace and message, so we can better understand the case?

0

Of course, here it is:

com.intellij.testFramework.TestLoggerFactory$TestLoggerAssertionError: Provider 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40@c27f758' is retaining PSI, causing memory leaks and possible invalid element access.
via 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40.arg$3'; Value: 'JsonArray' of class com.intellij.json.psi.impl.JsonArrayImpl
via 'com.intellij.psi.util.CachedValuesManager$1.val$provider'; Value: 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40@c27f758' of class com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40
via '?'; Value: 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40@c27f758' of class com.intellij.psi.util.CachedValuesManager$1
 (from CachedValueProvider solar.element.array.groups)
	at com.intellij.testFramework.TestLoggerFactory$TestLogger.error(TestLoggerFactory.java:400)
	at com.intellij.openapi.diagnostic.Logger.error(Logger.java:202)
	at com.intellij.util.CachedValueLeakChecker.lambda$findReferencedPsi$1(CachedValueLeakChecker.java:62)
	at com.intellij.util.ref.DebugReflectionUtil.walkObjects(DebugReflectionUtil.java:145)
	at com.intellij.util.CachedValueLeakChecker.findReferencedPsi(CachedValueLeakChecker.java:61)
	at com.intellij.util.CachedValueLeakChecker.checkProviderDoesNotLeakPSI(CachedValueLeakChecker.java:40)
	at com.intellij.util.CachedValuesManagerImpl.freshCachedValue(CachedValuesManagerImpl.java:106)
	at com.intellij.util.CachedValuesManagerImpl.getCachedValue(CachedValuesManagerImpl.java:70)
	at com.intellij.psi.util.CachedValuesManager.getCachedValue(CachedValuesManager.java:155)
	at com.solanteq.solar.plugin.element.FormElementFactoryKt.toFormArrayElement$tryCreateElement(FormElementFactory.kt:70)
	at com.solanteq.solar.plugin.element.FormElementFactoryKt.toFormArrayElement(FormElementFactory.kt:86)
	at com.solanteq.solar.plugin.element.FormTopLevelFile$groups$2.invoke(FormTopLevelFile.kt:291)
	at com.solanteq.solar.plugin.element.FormTopLevelFile$groups$2.invoke(FormTopLevelFile.kt:91)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at com.solanteq.solar.plugin.element.FormTopLevelFile.getGroups(FormTopLevelFile.kt:91)
	at com.solanteq.solar.plugin.element.FormTopLevelFile$allGroups$2.invoke(FormTopLevelFile.kt:107)
	at com.solanteq.solar.plugin.element.FormTopLevelFile$allGroups$2.invoke(FormTopLevelFile.kt:102)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at com.solanteq.solar.plugin.element.FormTopLevelFile.getAllGroups(FormTopLevelFile.kt:102)
	at com.solanteq.solar.plugin.l10n.FormL10n$referencedGroupElement$2.invoke(FormL10n.kt:154)
	at com.solanteq.solar.plugin.l10n.FormL10n$referencedGroupElement$2.invoke(FormL10n.kt:152)
	at kotlin.SynchronizedLazyImpl.getValue(LazyJVM.kt:74)
	at com.solanteq.solar.plugin.l10n.FormL10n.getReferencedGroupElement(FormL10n.kt:152)
	at com.solanteq.solar.plugin.l10n.field.L10nFieldPsiReferenceProvider.getReferencesByElement(L10nFieldPsiReferenceProvider.kt:21)
	at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.getReferences(ReferenceProvidersRegistryImpl.java:182)
	at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.mapNotEmptyReferencesFromProviders(ReferenceProvidersRegistryImpl.java:163)
	at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistryImpl.doGetReferencesFromProviders(ReferenceProvidersRegistryImpl.java:142)
	at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry.lambda$getReferencesFromProviders$0(ReferenceProvidersRegistry.java:39)
	at com.intellij.psi.util.CachedValuesManager$1.compute(CachedValuesManager.java:158)
	at com.intellij.psi.impl.PsiCachedValueImpl.doCompute(PsiCachedValueImpl.java:39)
	at com.intellij.util.CachedValueBase.lambda$getValueWithLock$3(CachedValueBase.java:231)
	at com.intellij.util.CachedValueBase.computeData(CachedValueBase.java:41)
	at com.intellij.util.CachedValueBase.lambda$getValueWithLock$4(CachedValueBase.java:231)
	at com.intellij.openapi.util.RecursionManager$1.computePreventingRecursion(RecursionManager.java:112)
	at com.intellij.openapi.util.RecursionGuard.doPreventingRecursion(RecursionGuard.java:42)
	at com.intellij.openapi.util.RecursionManager.doPreventingRecursion(RecursionManager.java:66)
	at com.intellij.util.CachedValueBase.getValueWithLock(CachedValueBase.java:232)
	at com.intellij.psi.impl.PsiCachedValueImpl.getValue(PsiCachedValueImpl.java:28)
	at com.intellij.util.CachedValuesManagerImpl.getCachedValue(CachedValuesManagerImpl.java:72)
	at com.intellij.psi.util.CachedValuesManager.getCachedValue(CachedValuesManager.java:155)
	at com.intellij.psi.util.CachedValuesManager.getCachedValue(CachedValuesManager.java:121)
	at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry.getReferencesFromProviders(ReferenceProvidersRegistry.java:38)
	at com.intellij.psi.impl.source.resolve.reference.ReferenceProvidersRegistry.getReferencesFromProviders(ReferenceProvidersRegistry.java:31)
	at com.intellij.json.psi.impl.JsonLiteralMixin.getReferences(JsonLiteralMixin.java:19)
	at com.intellij.json.psi.impl.JsonLiteralImpl.getReferences(JsonLiteralImpl.java:13)
	at com.intellij.psi.impl.SharedPsiElementImplUtil.addReferences(SharedPsiElementImplUtil.java:59)
	at com.intellij.psi.impl.SharedPsiElementImplUtil.findReferenceAt(SharedPsiElementImplUtil.java:33)
	at com.intellij.psi.impl.SharedPsiElementImplUtil.findReferenceAt(SharedPsiElementImplUtil.java:51)
	at com.intellij.psi.impl.PsiElementBase.findReferenceAt(PsiElementBase.java:75)
	at com.solanteq.solar.plugin.l10n.field.L10nFieldSymbolReferenceProvider.getReferences(L10nFieldSymbolReferenceProvider.kt:25)
	at com.solanteq.solar.plugin.l10n.field.L10nFieldSymbolReferenceProvider.getReferences(L10nFieldSymbolReferenceProvider.kt:15)
	at com.intellij.model.psi.impl.PsiSymbolReferenceServiceImpl.doGetExternalReferences(PsiSymbolReferenceServiceImpl.java:68)
	at com.intellij.model.psi.impl.PsiSymbolReferenceServiceImpl.getReferences(PsiSymbolReferenceServiceImpl.java:45)
	at com.intellij.model.psi.impl.PsiSymbolReferenceServiceImpl.getReferences(PsiSymbolReferenceServiceImpl.java:19)
	at com.solanteq.solar.plugin.FormTestBase.getFormSymbolReferenceAtCaret(FormTestBase.kt:102)
	at com.solanteq.solar.plugin.FormTestBase.assertReferencedSymbolNameEquals(FormTestBase.kt:41)
	at com.solanteq.solar.plugin.L10nTest.test l10n reference to fake field in included form(L10nTest.kt:381)
	at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base@17.0.3/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base@17.0.3/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base@17.0.3/java.lang.reflect.Method.invoke(Method.java:568)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at com.intellij.testFramework.junit5.impl.EdtInterceptorExtension$Companion$intercept$$inlined$runInEdtAndGet$1.compute(EdtTestUtil.kt:25)
	at com.intellij.testFramework.EdtTestUtil.lambda$runInEdtAndGet$0(EdtTestUtil.java:21)
	at com.intellij.testFramework.EdtTestUtil.lambda$runInEdtAndWait$1(EdtTestUtil.java:40)
	at com.intellij.openapi.application.TransactionGuardImpl.runWithWritingAllowed(TransactionGuardImpl.java:209)
	at com.intellij.openapi.application.TransactionGuardImpl.access$100(TransactionGuardImpl.java:21)
	at com.intellij.openapi.application.TransactionGuardImpl$1.run(TransactionGuardImpl.java:191)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:813)
	at com.intellij.openapi.application.impl.ApplicationImpl$3.run(ApplicationImpl.java:429)
	at com.intellij.openapi.application.impl.LaterInvocator$1.run(LaterInvocator.java:97)
	at com.intellij.openapi.application.impl.FlushQueue.doRun(FlushQueue.java:74)
	at com.intellij.openapi.application.impl.FlushQueue.runNextEvent(FlushQueue.java:114)
	at com.intellij.openapi.application.impl.FlushQueue.flushNow(FlushQueue.java:36)
	at java.desktop@17.0.3/java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:318)
	at java.desktop@17.0.3/java.awt.EventQueue.dispatchEventImpl(EventQueue.java:771)
	at java.desktop@17.0.3/java.awt.EventQueue$4.run(EventQueue.java:722)
	at java.desktop@17.0.3/java.awt.EventQueue$4.run(EventQueue.java:716)
	at java.base@17.0.3/java.security.AccessController.doPrivileged(AccessController.java:399)
	at java.base@17.0.3/java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:86)
	at java.desktop@17.0.3/java.awt.EventQueue.dispatchEvent(EventQueue.java:741)
	at com.intellij.ide.IdeEventQueue.defaultDispatchEvent(IdeEventQueue.java:909)
	at com.intellij.ide.IdeEventQueue._dispatchEvent(IdeEventQueue.java:756)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$5(IdeEventQueue.java:437)
	at com.intellij.openapi.progress.impl.CoreProgressManager.computePrioritized(CoreProgressManager.java:772)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$6(IdeEventQueue.java:436)
	at com.intellij.openapi.application.TransactionGuardImpl.performActivity(TransactionGuardImpl.java:113)
	at com.intellij.ide.IdeEventQueue.performActivity(IdeEventQueue.java:615)
	at com.intellij.ide.IdeEventQueue.lambda$dispatchEvent$7(IdeEventQueue.java:434)
	at com.intellij.openapi.application.impl.ApplicationImpl.runIntendedWriteActionOnCurrentThread(ApplicationImpl.java:813)
	at com.intellij.ide.IdeEventQueue.dispatchEvent(IdeEventQueue.java:480)
	at java.desktop@17.0.3/java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:203)
	at java.desktop@17.0.3/java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:124)
	at java.desktop@17.0.3/java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:113)
	at java.desktop@17.0.3/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:109)
	at java.desktop@17.0.3/java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
	at java.desktop@17.0.3/java.awt.EventDispatchThread.run(EventDispatchThread.java:90)
Caused by: java.lang.Throwable: Provider 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40@c27f758' is retaining PSI, causing memory leaks and possible invalid element access.
via 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40.arg$3'; Value: 'JsonArray' of class com.intellij.json.psi.impl.JsonArrayImpl
via 'com.intellij.psi.util.CachedValuesManager$1.val$provider'; Value: 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40@c27f758' of class com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40
via '?'; Value: 'com.solanteq.solar.plugin.element.FormElementFactoryKt$$Lambda$1795/0x00000008017dec40@c27f758' of class com.intellij.psi.util.CachedValuesManager$1
0

Hi,

Indeed, it seems that you capture valueArray PSI element besides the element you cache a value for. If you compute a value for a PSI element, you shouldn’t use other PSI elements in the provider code (lambda passed to CachedValuesManager.getCachedValue()).

See the Javadoc of the com.intellij.psi.util.CachedValue class, especially “Context-independence” part.

0

Please sign in to leave a comment.