Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TransactionAwareCacheDecorator renders CacheErrorHandler useless #28554

Open
neiser opened this issue Jun 2, 2022 · 6 comments
Open

TransactionAwareCacheDecorator renders CacheErrorHandler useless #28554

neiser opened this issue Jun 2, 2022 · 6 comments
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: feedback-provided Feedback has been provided type: enhancement A general enhancement
Milestone

Comments

@neiser
Copy link
Contributor

neiser commented Jun 2, 2022

Affects: All Versions since 3.2?

When configuring a custom cache error handler via CachingConfigurer::errorHandler and having transaction-aware caching enabled (for example, via RedisCacheManager.RedisCacheManagerBuilder::transactionAware, which decorates with TransactionAwareCacheDecorator), the cache error handler is never called when the put fails in TransactionSynchronization::afterCommit once the transaction was committed. In particular, this prevents users to suppress runtime exceptions from the cache backend by using the LoggingCacheErrorHandler, such as connection problems or command timeouts from Redis.

To illustrate the problem, I've created a simple demo project using Redis as a cache backend (which cannot connect as there's no Redis running on localhost:6379)

The test where I did not enable transaction-awareness does not throw any exception, whereas the test with transaction-awareness does, rather unexpectedly, as I've installed a LoggingCacheErrorHandler.

Note that I've configured a very dummy transaction handling to make the bug appear.

A workaround for the bug would be to not enable transaction-awareness via RedisCacheManager.RedisCacheManagerBuilder::transactionAware, but to instrument the cache manually. I did this with AOP on any cache instance by decorating the CacheManager::getCache with a BeanPostProcessor, but this is quite ugly:

@Aspect
@RequiredArgsConstructor
@EqualsAndHashCode
public class CacheTransactionAwareAspect {
    private final CacheErrorHandler cacheErrorHandler;
    private final Cache cache;

    private static Object proceedAfterCommit(ProceedingJoinPoint pjp, Consumer<RuntimeException> errorHandler) throws Throwable {
        if (TransactionSynchronizationManager.isSynchronizationActive()) {
            TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization() {
                @Override
                public void afterCommit() {
                    try {
                        pjp.proceed();
                    } catch (RuntimeException e) {
                        errorHandler.accept(e);
                    } catch (Throwable e) {
                        throw new RuntimeException(e);
                    }
                }
            });
            return null;
        } else {
            return pjp.proceed();
        }
    }

    @Around("execution(* org.springframework.cache.Cache.put(..))")
    public Object wrapPutMethod(ProceedingJoinPoint pjp) throws Throwable {
        return proceedAfterCommit(pjp, e -> {
            var args = pjp.getArgs();
            cacheErrorHandler.handleCachePutError(e, cache, args[0], args[1]);
        });
    }

    @Around("execution(* org.springframework.cache.Cache.evict(..))")
    public Object wrapEvictMethod(ProceedingJoinPoint pjp) throws Throwable {
        return proceedAfterCommit(pjp, e -> {
            var args = pjp.getArgs();
            cacheErrorHandler.handleCacheEvictError(e, cache, args[0]);
        });
    }

    @Around("execution(* org.springframework.cache.Cache.clear(..))")
    public Object wrapClearMethod(ProceedingJoinPoint pjp) throws Throwable {
        return proceedAfterCommit(pjp, e -> {
            cacheErrorHandler.handleCacheClearError(e, cache);
        });
    }
}

Let me know if you need further information to reproduce the bug.

I've also just tried a fix, but I don't know how to get the error handler (which should be kind of a singleton from CachingConfigurer) into the AbstractTransactionSupportingCacheManager. Any hints would be appreciated and I'd create a PR if this attempt goes into the right direction.

@spring-projects-issues spring-projects-issues added the status: waiting-for-triage An issue we've not yet triaged or decided on label Jun 2, 2022
@neiser neiser changed the title Active TransactionAwareCacheDecorator renders CacheErrorHandler useless TransactionAwareCacheDecorator renders CacheErrorHandler useless Jun 2, 2022
@sbrannen
Copy link
Member

sbrannen commented Jun 2, 2022

When you say CachingCustomizer, do you instead mean org.springframework.cache.annotation.CachingConfigurer?

@sbrannen sbrannen added status: waiting-for-feedback We need additional information before we can continue in: core Issues in core modules (aop, beans, core, context, expression) labels Jun 2, 2022
@rwinch rwinch added in: core Issues in core modules (aop, beans, core, context, expression) and removed in: core Issues in core modules (aop, beans, core, context, expression) labels Jun 2, 2022
@neiser
Copy link
Contributor Author

neiser commented Jun 2, 2022

@sbrannen Ah yes, I meant CachingConfigurer. I've updated the description and the example code.

@spring-projects-issues spring-projects-issues added status: feedback-provided Feedback has been provided and removed status: waiting-for-feedback We need additional information before we can continue labels Jun 2, 2022
neiser added a commit to neiser/spring-framework that referenced this issue Jun 2, 2022
@neiser
Copy link
Contributor Author

neiser commented Jul 6, 2022

@sbrannen Is there any update from your side? Anything I could do to support you with this?

@sbrannen sbrannen added this to the Triage Queue milestone Jul 8, 2022
@rstoyanchev rstoyanchev removed this from the Triage Queue milestone Jan 20, 2023
@mAlaliSy
Copy link

Any update on this?

@neiser
Copy link
Contributor Author

neiser commented Aug 15, 2023

Maybe @rstoyanchev can explain why this has been removed from the Triage Queue without further comment? 🤔

@sbrannen
Copy link
Member

We deleted the triage queue milestone in favor of a different internal process.

Note that this issue is still assigned the “waiting-for-triage” label. So its status has not changed.

@jhoeller jhoeller removed the status: waiting-for-triage An issue we've not yet triaged or decided on label Dec 29, 2023
@jhoeller jhoeller added this to the 6.2.x milestone Dec 29, 2023
@jhoeller jhoeller added the type: enhancement A general enhancement label Apr 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
in: core Issues in core modules (aop, beans, core, context, expression) status: feedback-provided Feedback has been provided type: enhancement A general enhancement
Projects
None yet
Development

No branches or pull requests

7 participants