Hi, I have a piece of code in a reactive project that looks something like that (similar code for demo purposes):
@Cacheable(value = RedisCacheNames.CONSTANT, cacheManager = "redisCacheManager")
public Mono<String> get(Integer id) {
return Mono.just("result")
.doOnSuccess(s -> throwException())
}
private void throwException() {
throw new RuntimeException();
}
I have a specific exception, but it basically extends from RuntimeException
.
I've been debugging a little, and it seems that CacheAspectSupport
had a piece of code added in commit 8974da2a that changed ReactiveCachingHandler
from this:
return adapter.fromPublisher(Mono.fromFuture(cachedFuture)
.switchIfEmpty(Mono.defer(() -> (Mono) evaluate(null, invoker, method, contexts)))
.flatMap(v -> evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts)));
to this:
return adapter.fromPublisher(Mono.fromFuture(cachedFuture)
.switchIfEmpty(Mono.defer(() -> (Mono) evaluate(null, invoker, method, contexts)))
.flatMap(v -> evaluate(Mono.justOrEmpty(unwrapCacheValue(v)), invoker, method, contexts))
.onErrorResume(RuntimeException.class, ex -> {
try {
getErrorHandler().handleCacheGetError((RuntimeException) ex, cache, key);
return evaluate(null, invoker, method, contexts);
}
catch (RuntimeException exception) {
return Mono.error(exception);
}
}));
The thing is, first evaluate call before the method execution sets the contexts.processed to true, and after my method throws the runtime exception, is caught by this onErrorResume
, which calls evaluate with cacheHit
set to null
, and
if (contexts.processed) {
return cacheHit;
}
returns null
.
Is this an actual issue or I am missing something?
As of now I've done a couple of extra tests, and it seems that I cannot throw a RuntimeException
. I have two CacheManagers configured in my Spring Boot project, one for Caffeine and the other one for Redis. This only happens when using the RedisCacheManager
as seen above.
I think this might be because CaffeineCache
implementation of Cache
returns null
when the element is not present whereas RedisCache
implementation returns a cachedFuture.
Thanks in advance for any help provided!
Comment From: simonbasle
If your exception is a subtype of RuntimeException
, and you have an error handler in place, perhaps you can tune the error handler to rethrow that particular type of exception ?
Comment From: drg2000
Hi @simonbasle ,
I think I don't understand your proposal. In the project we have a controller advice that catches our business runtime exceptions. The thing is, that if I throw SpecificException
my advice expects that specific class type, but since @Cacheable
throws a null pointer it is handled by our generic handler that catches Exception.class
. For the client, the message should look like: "This is our business exception" and not like "The nextFactory returned a null Publisher".
What kind of error handling are you proposing in this case?
Thanks for your time
Comment From: simonbasle
@drg2000 I see, the NPE's message is important context. I'll investigate further, but if you can provide a unit test or simple reproducer it would help tremendously.
Comment From: drg2000
Hi @simonbasle and sorry for the late reply, been kind of busy lately.
I've created a small spring project which replicates the issue. You just can launch the Application test or run the app and NullPointerException
should be displayed in the console. Any more information I can provide please let me know, the repo url is here: https://github.com/drg2000/bug-demo.
Thanks for your time
Comment From: simonbasle
Thanks for the reproducing project, it helped me verify that my tentative fix was working for your case 👍
Comment From: drg2000
Hi @simonbasle, thanks for your help!
It would be helpful If you could post a comment here once the fix is done, just out of curiosity to see which was the solution.
Thanks for your time.