public class RecursionManager
extends java.lang.Object
A()
calls method B()
, which in turn calls C()
,
which (unexpectedly) calls A()
again (it's just an example; the loop could be shorter or longer).
This would normally result in endless recursion and stack overflow. One should avoid situations like these at all cost,
but if that's impossible (e.g. due to different plugins unaware of each other yet calling each other),
RecursionManager
is to the rescue.
It helps to track all the computations in the thread stack and return some default value when
asked to compute A()
for the second time. doPreventingRecursion(java.lang.Object, boolean, com.intellij.openapi.util.Computable<T>)
does precisely this, returning null
when
endless recursion would otherwise happen.
Additionally, imagine all these methods A()
, B()
and C()
cache their results.
Note that if not A()
is called first, but B()
or C()
, the endless recursion would stay just the same,
but it would be prevented in different places (B()
or C()
, respectively). That'd mean there's 3 situations possible:
C()
calls A()
and gets null
as the result (if A()
is first in the stack)C()
calls A()
which calls B()
and gets null
as the result (if B()
is first in the stack)C()
calls A()
which calls B()
which calls C()
and gets null
as the result (if C()
is first in the stack)C()
would be different in those 3 cases, and it'd be unwise to cache just any of them randomly,
whatever is calculated first. In a multi-threaded environment, that'd lead to unpredictability.
Of the 3 possible scenarios above, caching for C()
makes sense only for the last one, because that's the result we'd get if there were no caching at all.
Therefore, if you use any kind of caching in an endless-recursion-prone environment, please ensure you don't cache incomplete results
that happen when you're inside the evil recursion loop.
RecursionManager
assists in distinguishing this situation and allowing caching outside that loop, but disallowing it inside.
To prevent caching incorrect values, please create a private static final
field of createGuard(java.lang.String)
call, and then use
markStack()
and RecursionGuard.StackStamp.mayCacheNow()
on it.
Note that the above only helps with idempotent recursion loops, that is, the ones that stabilize after one iteration, so that
A()->B()->C()->null
returns the same value as A()->B()->C()->A()->B()->C()->null
etc. If your functions lack that quality
(e.g. if they add items to some list), you won't get stable caching results ever, and your code will produce unpredictable results
with hard-to-catch bugs. Therefore, please strive for idempotence.Constructor and Description |
---|
RecursionManager() |
Modifier and Type | Method and Description |
---|---|
static void |
assertOnMissedCache(Disposable parentDisposable)
Enable the mode when a
CachingPreventedException is thrown whenever
RecursionGuard.StackStamp.mayCacheNow() returns false,
either due to recursion prevention or explicit RecursionGuard.prohibitResultCaching(Key) call. |
static void |
assertOnRecursionPrevention(Disposable parentDisposable) |
static <Key> RecursionGuard<Key> |
createGuard(java.lang.String id) |
static void |
disableAssertOnRecursionPrevention(Disposable parentDisposable) |
static void |
disableMissedCacheAssertions(Disposable parentDisposable)
Disables the effect of
assertOnMissedCache(com.intellij.openapi.Disposable) . |
static <T> T |
doPreventingRecursion(java.lang.Object key,
boolean memoize,
Computable<T> computation)
Run the given computation, unless it's already running in this thread.
|
static RecursionGuard.StackStamp |
markStack()
Used in pair with
RecursionGuard.StackStamp.mayCacheNow() to ensure that cached are only the reliable values,
not depending on anything incomplete due to recursive prevention policies. |
public static <T> T doPreventingRecursion(java.lang.Object key, boolean memoize, Computable<T> computation)
RecursionGuard.doPreventingRecursion(Object, boolean, Computable)
,
without a need to bother to create RecursionGuard
.public static <Key> RecursionGuard<Key> createGuard(java.lang.String id)
id
- just some string to separate different recursion prevention policies from each otherpublic static RecursionGuard.StackStamp markStack()
RecursionGuard.StackStamp.mayCacheNow()
to ensure that cached are only the reliable values,
not depending on anything incomplete due to recursive prevention policies.
A typical usage is this:
RecursionGuard.StackStamp stamp = RecursionManager.createGuard("id").markStack();
Result result = doComputation();
if (stamp.mayCacheNow()) {
cache(result);
}
return result;
RecursionManager
public static void assertOnRecursionPrevention(Disposable parentDisposable)
public static void disableAssertOnRecursionPrevention(Disposable parentDisposable)
public static void disableMissedCacheAssertions(Disposable parentDisposable)
assertOnMissedCache(com.intellij.openapi.Disposable)
. Should be used as rarely as possible, ideally only in tests that check
that stack isn't overflown on invalid code.public static void assertOnMissedCache(Disposable parentDisposable)
CachingPreventedException
is thrown whenever
RecursionGuard.StackStamp.mayCacheNow()
returns false,
either due to recursion prevention or explicit RecursionGuard.prohibitResultCaching(Key)
call.
Restore previous mode when parentDisposable is disposed.