From c6dde8ced069932a9a0952d9871f819f56c86dcf Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Tue, 23 Jun 2015 22:49:24 +0300 Subject: [PATCH 1/2] CachedValuesManager used to purge cache under low memory for PY-16282 --- .../psi/types/TypeEvalContextCache.java | 73 +++++++++++-------- 1 file changed, 44 insertions(+), 29 deletions(-) diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java index d02d2160f752..b205503379fd 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java @@ -15,43 +15,43 @@ */ package com.jetbrains.python.psi.types; +import com.intellij.openapi.Disposable; import com.intellij.openapi.project.Project; +import com.intellij.openapi.util.Disposer; +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.psi.util.PsiModificationTracker.SERVICE; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import java.util.HashMap; import java.util.Map; /** - * Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change. + * Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change or low memory conditions. * Class is thread safe. - * See {@link #getContext(com.intellij.openapi.project.Project, TypeEvalContext)} + * See {@link #getContext(Project, TypeEvalContext)} * * @author Ilya.Kazakevich */ -class TypeEvalContextCache { - /** - * Cache itself. - */ - @NotNull - private final Map myCache = new HashMap(); - /** - * Current PSI modification count - */ - private long myModificationCount = -1; +final class TypeEvalContextCache implements CachedValueProvider>, Disposable { /** * Lock to sync */ @NotNull private final Object myLock = new Object(); + /** + * {@link CachedValue} to store/create map [constraints, context] + */ + private volatile CachedValue> myCachedMapStorage; /** * Returns context from cache (if exist) or returns the one you provided (and puts it into cache). * To use this method, do the following: *
    - *
  1. Instantiate {@link com.jetbrains.python.psi.types.TypeEvalContext} you want to use
  2. + *
  3. Instantiate {@link TypeEvalContext} you want to use
  4. *
  5. Pass its instance here as argument
  6. *
  7. Use result
  8. *
@@ -62,25 +62,40 @@ class TypeEvalContextCache { */ @NotNull TypeEvalContext getContext(@NotNull final Project project, @NotNull final TypeEvalContext standard) { - final PsiModificationTracker tracker = SERVICE.getInstance(project); - synchronized (myLock) { - final long currentCount = tracker.getModificationCount(); - if (currentCount == myModificationCount) { - // Cache is valid, use it - final TypeEvalContext valueFromCache = myCache.get(standard.getConstraints()); - if (valueFromCache != null) { - // We have element in cache, return it - return valueFromCache; + + // Double check here to prevent useless sync + if (myCachedMapStorage == null) { + final CachedValuesManager manager = CachedValuesManager.getManager(project); + synchronized (myLock) { // Create storage if not exists. Should be created at first launch only + if (myCachedMapStorage == null) { + myCachedMapStorage = manager.createCachedValue(this, false); + Disposer.register(project, this); // To nullify property on project close } } - else { - // Cache is invalid, flush it and store current count - myCache.clear(); - myModificationCount = currentCount; + } + // Map is not thread safe nor "getValue" is. + synchronized (myLock) { + final Map map = myCachedMapStorage.getValue(); + final TypeEvalContext context = map.get(standard.getConstraints()); + if (context != null) { // Context already in cache, return it + return context; } - // We do not have value in cache (or cache is invalid), put it - myCache.put(standard.getConstraints(), standard); + map.put(standard.getConstraints(), standard); // Put this context to cache return standard; } } + + @Nullable + @Override + public Result> compute() { + // This method is called if cache is empty. Create new map for it. + final HashMap map = new HashMap(); + return new Result>(map, PsiModificationTracker.MODIFICATION_COUNT); + } + + @Override + public void dispose() { + // On project close + myCachedMapStorage = null; + } } From 02459ac610e9ee602e2319f72820e3834e60bce1 Mon Sep 17 00:00:00 2001 From: "Ilya.Kazakevich" Date: Wed, 24 Jun 2015 22:37:55 +0300 Subject: [PATCH 2/2] TypeEvalContextCache improved for PY-16282 according to IDEA-CR-3330. It now uses ProjectService, so each project has its own cache, and cache is destroyed when project is closed. --- .../python/psi/types/TypeEvalContext.java | 32 ++++--- .../psi/types/TypeEvalContextCache.java | 70 +-------------- .../psi/types/TypeEvalContextCacheImpl.java | 90 +++++++++++++++++++ python/src/META-INF/python-core.xml | 4 + 4 files changed, 118 insertions(+), 78 deletions(-) create mode 100644 python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java index 71655096756b..1cfc3aced28e 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContext.java @@ -15,6 +15,7 @@ */ package com.jetbrains.python.psi.types; +import com.intellij.openapi.components.ServiceManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.text.StringUtil; import com.intellij.psi.PsiElement; @@ -31,12 +32,6 @@ import java.util.*; */ public class TypeEvalContext { - /** - * Contexts are cached here to prevent useless recreations (and cache loss) - */ - @NotNull - private static final TypeEvalContextCache CACHE = new TypeEvalContextCache(); - public static class Key { private static final Key INSTANCE = new Key(); @@ -94,7 +89,7 @@ public class TypeEvalContext { * the analyzed code was called or may be called. Since this is basically guesswork, the results should be used only for code completion. */ public static TypeEvalContext codeCompletion(@NotNull final Project project, @Nullable final PsiFile origin) { - return CACHE.getContext(project, new TypeEvalContext(true, true, true, origin)); + return getContextFromCache(project, new TypeEvalContext(true, true, true, origin)); } /** @@ -105,7 +100,7 @@ public class TypeEvalContext { * For code completion see {@link TypeEvalContext#codeCompletion(Project, PsiFile)}. */ public static TypeEvalContext userInitiated(@NotNull final Project project, @Nullable final PsiFile origin) { - return CACHE.getContext(project, new TypeEvalContext(true, true, false, origin)); + return getContextFromCache(project, new TypeEvalContext(true, true, false, origin)); } /** @@ -115,7 +110,7 @@ public class TypeEvalContext { * Inspections should not create a new type evaluation context. They should re-use the context of the inspection session. */ public static TypeEvalContext codeAnalysis(@NotNull final Project project, @Nullable final PsiFile origin) { - return CACHE.getContext(project, new TypeEvalContext(false, false, false, origin)); + return getContextFromCache(project, new TypeEvalContext(false, false, false, origin)); } /** @@ -128,7 +123,7 @@ public class TypeEvalContext { public static TypeEvalContext codeInsightFallback(@Nullable final Project project) { final TypeEvalContext anchor = new TypeEvalContext(false, false, false, null); if (project != null) { - return CACHE.getContext(project, anchor); + return getContextFromCache(project, anchor); } return anchor; } @@ -139,7 +134,20 @@ public class TypeEvalContext { * Should be used only when normal code insight context is not enough for getting good results. */ public static TypeEvalContext deepCodeInsight(@NotNull final Project project) { - return CACHE.getContext(project, new TypeEvalContext(false, true, false, null)); + return getContextFromCache(project, new TypeEvalContext(false, true, false, null)); + } + + /** + * Moves context through cache returning one from cache (if exists). + * + * @param project current project + * @param context context to fetch from cache + * @return context to use + * @see TypeEvalContextCache#getContext(TypeEvalContext) + */ + @NotNull + private static TypeEvalContext getContextFromCache(@NotNull final Project project, @NotNull final TypeEvalContext context) { + return ServiceManager.getService(project, TypeEvalContextCache.class).getContext(context); } public TypeEvalContext withTracing() { @@ -191,7 +199,7 @@ public class TypeEvalContext { } } final PyType type = element.getType(this, Key.INSTANCE); - assertValid(type, element); + assertValid(type, element); synchronized (myEvaluated) { myEvaluated.put(element, type); } diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java index b205503379fd..4a8ea5acf38e 100644 --- a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCache.java @@ -1,5 +1,5 @@ /* - * Copyright 2000-2014 JetBrains s.r.o. + * Copyright 2000-2015 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,38 +15,13 @@ */ package com.jetbrains.python.psi.types; -import com.intellij.openapi.Disposable; -import com.intellij.openapi.project.Project; -import com.intellij.openapi.util.Disposer; -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 org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.HashMap; -import java.util.Map; /** - * Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change or low memory conditions. - * Class is thread safe. - * See {@link #getContext(Project, TypeEvalContext)} - * + * Project service to cache {@link TypeEvalContext} * @author Ilya.Kazakevich */ -final class TypeEvalContextCache implements CachedValueProvider>, Disposable { - /** - * Lock to sync - */ - @NotNull - private final Object myLock = new Object(); - - /** - * {@link CachedValue} to store/create map [constraints, context] - */ - private volatile CachedValue> myCachedMapStorage; - +public interface TypeEvalContextCache { /** * Returns context from cache (if exist) or returns the one you provided (and puts it into cache). * To use this method, do the following: @@ -56,46 +31,9 @@ final class TypeEvalContextCache implements CachedValueProviderUse result * * - * @param project project is required for caching engine * @param standard context you want to use. Just instantiate it and pass here. * @return context from cache (the one equals by constraints to yours or the one you provided) */ @NotNull - TypeEvalContext getContext(@NotNull final Project project, @NotNull final TypeEvalContext standard) { - - // Double check here to prevent useless sync - if (myCachedMapStorage == null) { - final CachedValuesManager manager = CachedValuesManager.getManager(project); - synchronized (myLock) { // Create storage if not exists. Should be created at first launch only - if (myCachedMapStorage == null) { - myCachedMapStorage = manager.createCachedValue(this, false); - Disposer.register(project, this); // To nullify property on project close - } - } - } - // Map is not thread safe nor "getValue" is. - synchronized (myLock) { - final Map map = myCachedMapStorage.getValue(); - final TypeEvalContext context = map.get(standard.getConstraints()); - if (context != null) { // Context already in cache, return it - return context; - } - map.put(standard.getConstraints(), standard); // Put this context to cache - return standard; - } - } - - @Nullable - @Override - public Result> compute() { - // This method is called if cache is empty. Create new map for it. - final HashMap map = new HashMap(); - return new Result>(map, PsiModificationTracker.MODIFICATION_COUNT); - } - - @Override - public void dispose() { - // On project close - myCachedMapStorage = null; - } + TypeEvalContext getContext(@NotNull TypeEvalContext standard); } diff --git a/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java new file mode 100644 index 000000000000..2c7ba1658220 --- /dev/null +++ b/python/psi-api/src/com/jetbrains/python/psi/types/TypeEvalContextCacheImpl.java @@ -0,0 +1,90 @@ +/* + * Copyright 2000-2014 JetBrains s.r.o. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +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.psi.util.PsiModificationTracker; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.HashMap; +import java.util.Map; + +/** + * Caches context by their constraints (to prevent context cache loss). Flushes cache every PSI change or low memory conditions. + * Class is thread safe. + * See {@link #getContext(TypeEvalContext)} + * + * @author Ilya.Kazakevich + */ +final class TypeEvalContextCacheImpl implements TypeEvalContextCache { + + /** + * Producer to create map to store cache + */ + @NotNull + private static final MapCreator MAP_CREATOR = new MapCreator(); + + /** + * Lock to sync + */ + @NotNull + private final Object myLock = new Object(); + + /** + * {@link CachedValue} to store/create map [constraints, context] + */ + private final CachedValue> myCachedMapStorage; + + + TypeEvalContextCacheImpl(@NotNull final Project project) { + final CachedValuesManager manager = CachedValuesManager.getManager(project); + myCachedMapStorage = manager.createCachedValue(MAP_CREATOR, false); + } + + + @NotNull + @Override + public TypeEvalContext getContext(@NotNull final TypeEvalContext standard) { + + // Map is not thread safe, and "getValue" is not atomic. I do not want several maps to be created. + synchronized (myLock) { + final Map map = myCachedMapStorage.getValue(); + final TypeEvalContext context = map.get(standard.getConstraints()); + if (context != null) { + return context; + } + map.put(standard.getConstraints(), standard); + return standard; + } + } + + /** + * Provider that creates map to store cache. Map depends on PSI modification + */ + private static final class MapCreator implements CachedValueProvider> { + @Nullable + @Override + public Result> compute() { + // This method is called if cache is empty. Create new map for it. + final HashMap map = new HashMap(); + return new Result>(map, PsiModificationTracker.MODIFICATION_COUNT); + } + } +} diff --git a/python/src/META-INF/python-core.xml b/python/src/META-INF/python-core.xml index 8ac1ecdb1ab6..476948f3a9bc 100644 --- a/python/src/META-INF/python-core.xml +++ b/python/src/META-INF/python-core.xml @@ -86,6 +86,10 @@ + +