Enhancement Request: Alignment of Kotlin Coroutines 'suspend' support with Reactive 'retrieve' pipeline
Concrete Use Case
In a high-throughput Spring Boot application using Spring WebFlux and Kotlin Coroutines, we expect the entire execution chain to be non-blocking. When using @Cacheable on a suspend function, the framework is expected to leverage the non-blocking capabilities of the underlying cache (e.g., Redis via Lettuce or Caffeine).
However, the current implementation of CacheAspectSupport treats suspend functions differently from Publisher types (Mono/Flux). Even with the kotlinx-coroutines-reactor bridge, a suspend function with sync = false (default) triggers a synchronous Cache.get(key) call. In a reactive environment, this synchronous call can lead to "thread pinning" or starvation of the Event Loop/Worker threads if the cache access is slow or remote.
How I have tried to solve this so far
Enabling sync = true: This is the only way to force the interceptor to use the retrieve method (which returns a CompletableFuture). While this makes the flow non-blocking, it introduces mandatory synchronization (locks) at the cache level, which is often undesirable when we only want an asynchronous lookup without the overhead of a coordinated lock.
Manual Bridge: Implementing a custom Cache wrapper that delegates get to getAsync().join(). While this works, it still consumes a thread waiting for the result, rather than allowing the Coroutine to suspend naturally in a non-blocking pipeline.
Proposed Change
I have identified an inconsistency in CacheAspectSupport.java that prevents suspend functions from benefiting from the reactive pipeline:
-
Sequential Lookup: For suspend functions (around line 541), the logic falls back to findInCaches, which is strictly synchronous.
-
Reactive Advantage: In contrast, the ReactiveCacheAspectSupport logic (around line 1143) uses a retrieve based approach, providing a fully non-blocking CompletionStage chain.
The enhancement would consist of treating suspend function returns as "async-first" citizens, similar to Publisher. Since Spring 6.1+ already recognizes suspend functions, the interceptor should be able to route these calls through the retrieve/CompletableFuture path even when sync = false. This would ensure that the Coroutine suspension is truly non-blocking regarding the cache I/O, matching the performance characteristics of Project Reactor types.
Enhancement Request: Alignment of Kotlin Coroutines 'suspend' support with Reactive 'retrieve' pipeline
Concrete Use Case
In a high-throughput Spring Boot application using Spring WebFlux and Kotlin Coroutines, we expect the entire execution chain to be non-blocking. When using
@Cacheableon a suspend function, the framework is expected to leverage the non-blocking capabilities of the underlying cache (e.g., Redis via Lettuce or Caffeine).However, the current implementation of
CacheAspectSupporttreats suspend functions differently from Publisher types (Mono/Flux). Even with thekotlinx-coroutines-reactorbridge, a suspend function withsync = false(default) triggers a synchronousCache.get(key)call. In a reactive environment, this synchronous call can lead to "thread pinning" or starvation of the Event Loop/Worker threads if the cache access is slow or remote.How I have tried to solve this so far
Enabling
sync = true: This is the only way to force the interceptor to use the retrieve method (which returns aCompletableFuture). While this makes the flow non-blocking, it introduces mandatory synchronization (locks) at the cache level, which is often undesirable when we only want an asynchronous lookup without the overhead of a coordinated lock.Manual Bridge: Implementing a custom Cache wrapper that delegates get to getAsync().join(). While this works, it still consumes a thread waiting for the result, rather than allowing the Coroutine to suspend naturally in a non-blocking pipeline.
Proposed Change
I have identified an inconsistency in
CacheAspectSupport.javathat prevents suspend functions from benefiting from the reactive pipeline:Sequential Lookup: For suspend functions (around line 541), the logic falls back to findInCaches, which is strictly synchronous.
Reactive Advantage: In contrast, the
ReactiveCacheAspectSupportlogic (around line 1143) uses a retrieve based approach, providing a fully non-blocking CompletionStage chain.The enhancement would consist of treating suspend function returns as "async-first" citizens, similar to Publisher. Since Spring 6.1+ already recognizes suspend functions, the interceptor should be able to route these calls through the retrieve/CompletableFuture path even when
sync = false. This would ensure that the Coroutine suspension is truly non-blocking regarding the cache I/O, matching the performance characteristics of Project Reactor types.