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 computeIfAbsentgetInCacheOrCompute 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);
}