spring-projects / spring-projects/spring-data-redis
Reactive connection leaks from pool when acquisition is cancelled before the connection arrives
@mp911de is already working on this.
Since Jun 1, 2026.
- Dominant language
- Java
- Stars
- 1.9k
- Forks
- 1.3k
- PR merge metrics
- No merged PRs in 30d
Description
Bug Report
LettuceReactiveRedisConnection.AsyncConnect leaks a pooled connection when the connection acquisition is cancelled (e.g. request timeout, client disconnect, subscription cancellation) before the connection has arrived from the LettuceConnectionProvider.
When this happens, the late-arriving connection is closed via it.closeAsync() but is never released back to the connection provider, so a pooled provider's internal accounting (objectCount / all queue in Lettuce's BoundedAsyncPool) still counts the connection as active. Over time this exhausts the pool and leads to PoolException / NoSuchElementException: Pool exhausted, even though the physical connections are already closed.
Current Behavior
In the AsyncConnect constructor:
this.connectionPublisher = defer.doOnNext(it -> {
if (isClosing(STATE.get(this))) {
it.closeAsync(); // closes the connection but never releases the pool slot
} else {
connection = it;
}
})
Race scenario:
- Subscriber subscribes to
getConnection();AsyncConnectrequests a connection from the pool.this.connectionis stillnull. - The subscription is cancelled, so
close()runs. Becausethis.connectionis stillnull,connectionProvider.releaseAsync(...)is skipped and the state becomesCLOSED. - The pool finally yields the connection.
doOnNextsees the closing state and callsit.closeAsync(). connectionProvider.releaseAsync(it)is never called, so the pool never reclaims the slot. Leak.
Expected Behavior
A connection that arrives after close() should be released back to the connection provider (connectionProvider.releaseAsync(it)), mirroring what close() itself does for an already-arrived connection. For non-pooled providers this is equivalent to closing (the default releaseAsync delegates to closeAsync()), and for pooled providers it correctly returns the connection to the pool.
Context
Originally reported against Lettuce as redis/lettuce#3609, but the root cause is in Spring Data Redis's AsyncConnect. The fix is a one-line change plus a regression test; PR to follow.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.