From b619e21989bfe469f759127fc144079c100628ae Mon Sep 17 00:00:00 2001 From: Mikhail Golubev Date: Fri, 4 Apr 2025 17:28:58 +0300 Subject: [PATCH] [python] Remove TypeEvalContextBasedCache GitOrigin-RevId: 74d87561c3cc857ede78e60f19a7edbad35f29ee --- .../psi/types/TypeEvalContextBasedCache.java | 71 ------------------- .../psi/types/TypeEvalContextCacheImpl.java | 40 +++++++---- 2 files changed, 27 insertions(+), 84 deletions(-) delete mode 100644 python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextBasedCache.java diff --git a/python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextBasedCache.java b/python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextBasedCache.java deleted file mode 100644 index d5a7accdde0d..000000000000 --- a/python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextBasedCache.java +++ /dev/null @@ -1,71 +0,0 @@ -// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. -package com.jetbrains.python.psi.types; - -import com.intellij.psi.util.CachedValue; -import com.intellij.psi.util.CachedValueProvider; -import com.intellij.psi.util.CachedValuesManager; -import com.intellij.psi.util.PsiModificationTracker; -import com.intellij.util.Function; -import com.intellij.util.containers.ContainerUtil; -import org.jetbrains.annotations.NotNull; - -import java.util.concurrent.ConcurrentMap; - -/** - * Engine to cache something in map, where {@link TypeEvalContext} is used as key. - * This cache is weak-based (no memory leaks), thread safe and purges on any PSI change. - * - * @author Ilya.Kazakevich - */ -public final class TypeEvalContextBasedCache { - private final @NotNull CachedValue> myCachedMapStorage; - - private final @NotNull Function myProvider; - - /** - * @param manager Cache manager to be used to store cache - * @param valueProvider engine to create value based on context. - */ - public TypeEvalContextBasedCache(final @NotNull CachedValuesManager manager, - final @NotNull Function valueProvider) { - myCachedMapStorage = manager.createCachedValue(new MapCreator<>(), false); - myProvider = valueProvider; - } - - /** - * Returns value (executes provider to obtain new if no any and stores it in cache). - * It is better to run this method under read action to make sure PSI not modified in the middle of its execution - * - * @param context to be used as key - * @return value - */ - public @NotNull T getValue(final @NotNull TypeEvalContext context) { - - // map is thread safe but not atomic nor getValue() is, so in worst case several threads may produce same result - // myProvider.fun should never be launched under lock to prevent deadlocks like PY-24300 and PY-24625 - // both explicit locking and computeIfAbsent leads to deadlock - final ConcurrentMap map = myCachedMapStorage.getValue(); - final TypeEvalConstraints key = context.getConstraints(); - final T value = map.get(key); - if (value != null) { - return value; - } - final T newValue = myProvider.fun(context); - T oldValue = - map.putIfAbsent(key, newValue);// ConcurrentMap guarantees happens-before so from this moment get() should work in other threads - return oldValue == null ? newValue : oldValue; - } - - /** - * Provider that creates map to store cache. Map depends on PSI modification - */ - private static final class MapCreator implements CachedValueProvider> { - @Override - public @NotNull Result> compute() { - // This method is called if cache is empty. Create new map for it. - // Concurrent map allows several threads to call get and put, so it is thread safe but not atomic - final ConcurrentMap map = ContainerUtil.createConcurrentSoftValueMap(); - return new Result<>(map, PsiModificationTracker.MODIFICATION_COUNT); - } - } -} diff --git a/python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java b/python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java index 493d7a783688..fbe76e87b6e5 100644 --- a/python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java +++ b/python/python-psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java @@ -2,10 +2,15 @@ package com.jetbrains.python.psi.types; import com.intellij.openapi.project.Project; +import com.intellij.psi.util.CachedValue; +import com.intellij.psi.util.CachedValueProvider; import com.intellij.psi.util.CachedValuesManager; -import com.intellij.util.Function; +import com.intellij.psi.util.PsiModificationTracker; +import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; +import java.util.concurrent.ConcurrentMap; + /** * Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change or low memory conditions. * Class is thread safe. @@ -14,23 +19,32 @@ import org.jetbrains.annotations.NotNull; * @author Ilya.Kazakevich */ final class TypeEvalContextCacheImpl implements TypeEvalContextCache { - private static final @NotNull Function VALUE_PROVIDER = new MyValueProvider(); - private final @NotNull TypeEvalContextBasedCache myCache; + private final @NotNull CachedValue> myCachedMapStorage; TypeEvalContextCacheImpl(@NotNull Project project) { - myCache = new TypeEvalContextBasedCache<>(CachedValuesManager.getManager(project), VALUE_PROVIDER); + myCachedMapStorage = CachedValuesManager.getManager(project).createCachedValue(new CachedValueProvider<>() { + @Override + public @NotNull CachedValueProvider.Result> compute() { + // This method is called if cache is empty. Create new map for it. + // Concurrent map allows several threads to call get and put, so it is thread safe but not atomic + final ConcurrentMap map = ContainerUtil.createConcurrentSoftValueMap(); + return new CachedValueProvider.Result<>(map, PsiModificationTracker.MODIFICATION_COUNT); + } + }); } @Override - public @NotNull TypeEvalContext getContext(final @NotNull TypeEvalContext standard) { - return myCache.getValue(standard); - } - - private static class MyValueProvider implements Function { - @Override - public TypeEvalContext fun(final TypeEvalContext param) { - // key and value are both context here. If no context stored, then key is stored. Old one is returned otherwise to cache. - return param; + public @NotNull TypeEvalContext getContext(@NotNull TypeEvalContext standard) { + // map is thread safe but not atomic nor getValue() is, so in worst case several threads may produce same result + // both explicit locking and computeIfAbsent leads to deadlock + final ConcurrentMap map = myCachedMapStorage.getValue(); + final TypeEvalConstraints key = standard.getConstraints(); + final TypeEvalContext cachedContext = map.get(key); + if (cachedContext != null) { + return cachedContext; } + TypeEvalContext oldValue = + map.putIfAbsent(key, standard);// ConcurrentMap guarantees happens-before so from this moment get() should work in other threads + return oldValue == null ? standard : oldValue; } }