Query cache
Cache manipulation tool
warning
The function getQueryCache.computeIfAbsent(key, k → computeMyStuff)
is
broken and leads to dead locks. Specifically, there is one mutex on the
compute function and another at the leaf of the map that can wait for each
other.
To avoid duplicating the code and to increase business
code readability, there is a function (with four declinations) on
the PostProcessorUtils
class :
public static <T, K> @Nullable T getInCacheOrCompute(@Nullable Map<K, ? super Optional<T>> cache, @NonNull K key, @NonNull NullableCacheSupplier<T> supplier);
public static <T, K> @NonNull T getInCacheOrComputeNeverNull(@Nullable Map<K, ? super T> cache, @NonNull K key, @NonNull NotNullCacheSupplier<T> supplier);
public static <T> @Nullable T getInCacheOrCompute(@Nullable Map<? super CompositeKey, ? super Optional<T>> cache, @NonNull NullableCacheSupplier<T> supplier, Object... keys);
public static <T> @NonNull T getInCacheOrComputeNeverNull(@Nullable Map<? super CompositeKey, ? super T> cache, @NonNull NotNullCacheSupplier<T> supplier, Object... keys);
The function handles the null case by encapsulating the result into an
Optional
object, except for the “…NeverNull” flavor.
For multiple keys there is a signature with a key list, that uses the CompositeKey object as cache key.
Example :
// Enum is guarantee to be unique by the JSR, so we can use it as key discriminator
enum CacheKey { PNL, VAR, SHIFT }
// [...]
BinaryOperator<Double> formula = PostProcessorUtils.getInCacheOrCompute(
cache,
() -> getVaRExplainFormula(sensitivityKind, sensitivityName, riskClass),
CacheKey.VAR, riskClass, sensitivityName, sensitivityKind);
note
Despite computeIfAbsent
, getInCacheOrCompute
could still call the lambda
several times to retrieve the requested value because there is
no concurrency check at this level (which avoids the dead lock) but only
the first unique computed value is returned.
Cache key
The common way to create a cache key is to generate a string from the cache parameters. But this approach forces the program to spend a lot of time on String factories and the use of string as hash key is also highly inefficient.
So the best way is to use the input parameters as-is, but an Object[] has no hash and equals.
That’s why we use the CompositeKey
key object that just encapsulates an
Object[]
to provide mandatory functions needed for HashMap
.
Example :
public static <T> @Nullable T getInCacheOrCompute(@Nullable Map<? super CompositeKey, ? super Optional<T>> cache, @NonNull NullableCacheSupplier<T> supplier, Object... keys) {
return getInCacheOrCompute(cache, CompositeKey.of(keys), supplier);
}