reactor / reactor-addons

Additional optional modules for the Reactor project

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

CacheMono - onCacheMissResume is being called even if there is a value in the cache

hmble2 opened this issue · comments

Even if the value is present in the cache, onCacheMissResume is fired every time.
Issue is due to otherSupplier.get() in method argument is not deferred and hence called immediately.

	public static <KEY, VALUE> MonoCacheBuilderCacheMiss<KEY, VALUE> lookup(
			Function<KEY, Mono<Signal<? extends VALUE>>> reader, KEY key) {
		return otherSupplier -> writer -> Mono.defer(() ->
				reader.apply(key)
				  .switchIfEmpty(otherSupplier.get()
				                              .materialize()
				                              .flatMap(signal -> writer.apply(key, signal)
				                                                       .then(Mono.just(signal))
				                              )
				  )
				  .dematerialize());
	}

and at,

public static <KEY, VALUE> MonoCacheBuilderMapMiss<VALUE> lookup(Map<KEY, ? super Signal<? extends VALUE>> cacheMap, KEY key) {
    return otherSupplier -> Mono.defer(() ->
            Mono.justOrEmpty(cacheMap.get(key))
                .switchIfEmpty(otherSupplier.get().materialize()
                                .doOnNext(value -> cacheMap.put(key, value)))
                .dematerialize()
    );
}

This can be fixed by adding Mono.defer to otherSupplier.get in all places having onCacheMissResume callback e.g.

public static <KEY, VALUE> MonoCacheBuilderCacheMiss<KEY, VALUE> lookup(
		Function<KEY, Mono<Signal<? extends VALUE>>> reader, KEY key) {
	return otherSupplier -> writer -> Mono.defer(() ->
			reader.apply(key)
			  .switchIfEmpty(Mono.defer(()->otherSupplier.get()
			                              .materialize()
			                              .flatMap(signal -> writer.apply(key, signal)
			                                                       .then(Mono.just(signal))
			                              ))
			  )
			  .dematerialize());
}

Workaround: Until the issue is fixed, callback can add a defer to resolve the issue:
.onCacheMissResume(() -> Mono.defer(() -> createExpensiveGraph(key)))

I have the same issue. But I don't think it's duo to CacheMono.
You can put value in the cache at the beginning to check if onCacheMissResume is fired.

I adapted the unit test from the official repo to verify this:

    Mono<Integer> source = Mono.fromCallable(()->{
      System.out.println("here");
      return 123;
    }).doOnSubscribe(sub -> sourceSubscribed.incrementAndGet());
    AtomicReference<Context> storeRef = new AtomicReference<>(Context.empty());
    String key = "burger";
    storeRef.updateAndGet(ctx -> ctx.put(key,321)); // put the value in the cache

    Mono<Integer> cachedMono = CacheMono
        .lookup(k -> Mono.justOrEmpty(storeRef.get().<Integer>getOrEmpty(k))
                .map(Signal::next),
            key)
        .onCacheMissResume(source)
        .andWriteWith((k, sig) -> Mono.fromRunnable(() ->
            storeRef.updateAndGet(ctx -> ctx.put(k, sig.get())))
        );

    cachedMono.subscribe(System.out::println);
    System.out.println(sourceSubscribed.get());

you can see that there is no here output in the console.

I think the reason is there is no lock/synchronization when the cache is missed.
For example:
There is no value in the cache at the beginning.
Request 1,2,3 comes to get the value from cache at the same time,
Request 1 gets null and fires the onCacheMissResume
When Request 2 comes, the write operation fired by Request 1 haven't finished, so it will fire the write again due to the null.
When Request 3 comes, it will get the proper value if the write operation have finished.

But i didn't know if there is a solution.

I was also thinking this as an issue till I see this discussion https://stackoverflow.com/questions/63812343/spring-webflux-with-in-memory-cache

The confusion is caused due to mixing of imperative and reactive style. However, should the Mono that gets invoked (i.e. fetchValueFromSource) when onCacheMissResume is fired needed to be wired at assembly time? Adding a Mono.defer could eliminate the confusion and thus won't execute any imperative code in method Mono fetchValueFromSource(). Please see here https://stackoverflow.com/questions/54974326/how-to-make-reactor-cache-cachemono-not-to-call-calculation-twice @simonbasle @alex-pumpkin

is it the same as / fixed by #227 by any chance ? (that PR has just been released in 3.3.4.RELEASE and 3.4.0-RC1)

@simonbasle yeah, exact same issue. Thanks, this should be okay to close.

Duplicate of #227