mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
cache intermediate computation results thread-locally even if recursion prevention has occurred before (IDEA-70668)
This commit is contained in:
@@ -0,0 +1,5 @@
|
||||
import static Foo.*;
|
||||
|
||||
class Foo implements <ref>Bar1, Bar2, Bar3, Bar4, Bar5, Bar6, Bar7, Bar8, Bar9, Bar10, Bar11, Bar12, Bar13, Bar14 {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
import static Foo1.*;
|
||||
import static Foo0.*;
|
||||
import static Foo3.*;
|
||||
import static Foo.*;
|
||||
|
||||
class Foo extends <ref>Bar {}
|
||||
@@ -11,6 +11,7 @@ import com.intellij.openapi.roots.ModuleRootManager;
|
||||
import com.intellij.openapi.vfs.LocalFileSystem;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.testFramework.PlatformTestUtil;
|
||||
import com.intellij.testFramework.ResolveTestCase;
|
||||
|
||||
public class ResolveClassTest extends ResolveTestCase {
|
||||
@@ -154,6 +155,31 @@ public class ResolveClassTest extends ResolveTestCase {
|
||||
assertNull(target);
|
||||
}
|
||||
|
||||
public void testStaticImportInTheSameClass() throws Exception {
|
||||
PsiReference ref = configure();
|
||||
long start = System.currentTimeMillis();
|
||||
assertNull(ref.resolve());
|
||||
PlatformTestUtil.assertTiming("exponent?", 20000, System.currentTimeMillis() - start);
|
||||
}
|
||||
|
||||
public void testStaticImportNetwork() throws Exception {
|
||||
PsiReference ref = configure();
|
||||
int count = 20;
|
||||
|
||||
String imports = "";
|
||||
for (int i = 0; i < count; i++) {
|
||||
imports += "import static Foo" + i + ".*;\n";
|
||||
}
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
createFile(myModule, "Foo" + i + ".java", imports + "class Foo" + i + " extends Bar1, Bar2, Bar3 {}");
|
||||
}
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
assertNull(ref.resolve());
|
||||
PlatformTestUtil.assertTiming("exponent?", 20000, System.currentTimeMillis() - start);
|
||||
}
|
||||
|
||||
@SuppressWarnings({"ConstantConditions"})
|
||||
private void configureDependency() {
|
||||
ApplicationManager.getApplication().runWriteAction(new Runnable() {
|
||||
|
||||
@@ -120,7 +120,7 @@ public class ResolveCache {
|
||||
};
|
||||
|
||||
RecursionGuard.StackStamp stamp = myGuard.markStack();
|
||||
result = needToPreventRecursion ? myGuard.doPreventingRecursion(ref, computable) : computable.compute();
|
||||
result = needToPreventRecursion ? myGuard.doPreventingRecursion(ref, true, computable) : computable.compute();
|
||||
if (stamp.mayCacheNow()) {
|
||||
cache(ref, result, maps, physical, incompleteCode, clearCountOnStart);
|
||||
}
|
||||
|
||||
@@ -24,15 +24,28 @@ import java.util.List;
|
||||
*
|
||||
* @author peter
|
||||
*/
|
||||
public interface RecursionGuard {
|
||||
public abstract class RecursionGuard {
|
||||
|
||||
/**
|
||||
* See {@link #doPreventingRecursion(Object, boolean, Computable)} with memoization disabled
|
||||
*/
|
||||
@SuppressWarnings("JavaDoc")
|
||||
@Deprecated
|
||||
@Nullable
|
||||
public <T> T doPreventingRecursion(Object key, Computable<T> computation) {
|
||||
return doPreventingRecursion(key, false, computation);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param key an id of the computation. Is stored internally to ensure that a recursive calls with the same key won't lead to endless recursion.
|
||||
* @param memoize whether the result of the computation may me cached thread-locally until the last currently active doPreventingRecursion call
|
||||
* completes. May be used to speedup things when recursion re-entrance happens: otherwise nothing would be cached at all and
|
||||
* in some cases exponential performance may be observed.
|
||||
* @param computation a piece of code to compute.
|
||||
* @return the result of the computation or null if we're entering a computation with this key on this thread recursively,
|
||||
*/
|
||||
@Nullable
|
||||
<T> T doPreventingRecursion(Object key, Computable<T> computation);
|
||||
public abstract <T> T doPreventingRecursion(Object key, boolean memoize, Computable<T> computation);
|
||||
|
||||
/**
|
||||
* Used in pair with {@link com.intellij.openapi.util.RecursionGuard.StackStamp#mayCacheNow()} to ensure that cached are only the reliable values,
|
||||
@@ -51,12 +64,12 @@ public interface RecursionGuard {
|
||||
|
||||
* @return an object representing the current stack state, managed by {@link RecursionManager}
|
||||
*/
|
||||
StackStamp markStack();
|
||||
public abstract StackStamp markStack();
|
||||
|
||||
/**
|
||||
* @return the current thread-local stack of keys passed to {@link #doPreventingRecursion(Object, Computable)}
|
||||
*/
|
||||
List<Object> currentStack();
|
||||
public abstract List<Object> currentStack();
|
||||
|
||||
/**
|
||||
* Makes {@link com.intellij.openapi.util.RecursionGuard.StackStamp#mayCacheNow()} return false for all stamps created since a computation with
|
||||
@@ -65,11 +78,13 @@ public interface RecursionGuard {
|
||||
* Used to prevent caching of results that are non-reliable NOT due to recursion prevention: for example, too deep recursion
|
||||
* ({@link #currentStack()} may help in determining the recursion depth)
|
||||
*
|
||||
* Also disables thread-local memoization (see the second parameter of {@link #doPreventingRecursion(Object, boolean, Computable)}.
|
||||
*
|
||||
* @param since the id of a computation whose result is safe to cache whilst for more nested ones it's not.
|
||||
*/
|
||||
void prohibitResultCaching(Object since);
|
||||
public abstract void prohibitResultCaching(Object since);
|
||||
|
||||
interface StackStamp {
|
||||
public interface StackStamp {
|
||||
|
||||
/**
|
||||
* @return whether a computation that started at the moment of this {@link StackStamp} instance creation does not depend on any re-entrant recursive
|
||||
|
||||
@@ -15,9 +15,14 @@
|
||||
*/
|
||||
package com.intellij.openapi.util;
|
||||
|
||||
import com.intellij.reference.SoftReference;
|
||||
import com.intellij.util.containers.SoftHashMap;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* There are moments when a computation A requires the result of computation B, which in turn requires C, which (unexpectedly) requires A.
|
||||
@@ -28,7 +33,7 @@ import java.util.*;
|
||||
* incorrect to cache the results of B and C, because they all are based on the default incomplete result of the A calculation. If the actual
|
||||
* computation sequence were C->A->B->C, the result of the outer C most probably wouldn't be the same as in A->B->C->A, where it depends on
|
||||
* the null A result directly. The natural wish is that the program with cache enabled has the same results as the one without cache. In the above
|
||||
* situation the result of C would depend on the order of invocations of C and A, which can be hardly predictable in multithreaded environments.
|
||||
* situation the result of C would depend on the order of invocations of C and A, which can be hardly predictable in multi-threaded environments.
|
||||
*
|
||||
* Therefore if you use any kind of cache, it probably would make your program safer to cache only when it's safe to do this. See
|
||||
* {@link com.intellij.openapi.util.RecursionGuard#markStack()} and {@link com.intellij.openapi.util.RecursionGuard.StackStamp#mayCacheNow()}
|
||||
@@ -40,18 +45,31 @@ import java.util.*;
|
||||
*/
|
||||
@SuppressWarnings({"UtilityClassWithoutPrivateConstructor"})
|
||||
public class RecursionManager {
|
||||
private static final Object NULL = new Object();
|
||||
private static final ThreadLocal<Integer> ourStamp = new ThreadLocal<Integer>() {
|
||||
@Override
|
||||
protected Integer initialValue() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
private static final ThreadLocal<Integer> ourMemoizationStamp = new ThreadLocal<Integer>() {
|
||||
@Override
|
||||
protected Integer initialValue() {
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
private static final ThreadLocal<LinkedHashMap<MyKey, Integer>> ourProgress = new ThreadLocal<LinkedHashMap<MyKey, Integer>>() {
|
||||
@Override
|
||||
protected LinkedHashMap<MyKey, Integer> initialValue() {
|
||||
return new LinkedHashMap<MyKey, Integer>();
|
||||
}
|
||||
};
|
||||
private static final ThreadLocal<Map<MyKey, SoftReference>> ourIntermediateCache = new ThreadLocal<Map<MyKey, SoftReference>>() {
|
||||
@Override
|
||||
protected Map<MyKey, SoftReference> initialValue() {
|
||||
return new SoftHashMap<MyKey, SoftReference>();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param id just some string to separate different recursion prevention policies from each other
|
||||
@@ -60,23 +78,44 @@ public class RecursionManager {
|
||||
public static RecursionGuard createGuard(@NonNls final String id) {
|
||||
return new RecursionGuard() {
|
||||
@Override
|
||||
public <T> T doPreventingRecursion(Object key, Computable<T> computation) {
|
||||
public <T> T doPreventingRecursion(Object key, boolean memoize, Computable<T> computation) {
|
||||
MyKey realKey = new MyKey(id, key);
|
||||
LinkedHashMap<MyKey, Integer> progressMap = ourProgress.get();
|
||||
if (progressMap.containsKey(realKey)) {
|
||||
//todo cache only here
|
||||
prohibitResultCaching(key);
|
||||
_prohibitResultCaching(key);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
if (memoize) {
|
||||
SoftReference reference = ourIntermediateCache.get().get(realKey);
|
||||
if (reference != null) {
|
||||
Object o = reference.get();
|
||||
if (o != null) {
|
||||
//noinspection unchecked
|
||||
return o == NULL ? null : (T)o;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
progressMap.put(realKey, ourStamp.get());
|
||||
int startStamp = ourMemoizationStamp.get();
|
||||
|
||||
try {
|
||||
return computation.compute();
|
||||
T result = computation.compute();
|
||||
|
||||
if (memoize && ourMemoizationStamp.get() == startStamp) {
|
||||
ourIntermediateCache.get().put(realKey, new SoftReference<Object>(result == null ? NULL : result));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
finally {
|
||||
ourStamp.set(progressMap.remove(realKey));
|
||||
Integer value = progressMap.remove(realKey);
|
||||
ourStamp.set(value);
|
||||
if (value == 0) {
|
||||
ourIntermediateCache.get().clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -105,6 +144,10 @@ public class RecursionManager {
|
||||
|
||||
@Override
|
||||
public void prohibitResultCaching(Object since) {
|
||||
ourMemoizationStamp.set(_prohibitResultCaching(since));
|
||||
}
|
||||
|
||||
private int _prohibitResultCaching(Object since) {
|
||||
int stamp = ourStamp.get() + 1;
|
||||
ourStamp.set(stamp);
|
||||
|
||||
@@ -117,6 +160,7 @@ public class RecursionManager {
|
||||
inLoop = true;
|
||||
}
|
||||
}
|
||||
return stamp;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user