Keep track of pending Redis get calls in client side caching to avoid race with invalidations. #3481#3497
Conversation
|
Hi, I’m Jit, a friendly security platform designed to help developers build secure applications from day zero with an MVS (Minimal viable security) mindset. In case there are security findings, they will be communicated to you as a comment inside the PR. Hope you’ll enjoy using Jit. Questions? Comments? Want to learn more? Get in touch with us. |
|
So far didn't add any tests. I felt I was only changing internal parts and there is already tests against the end behavior. Testing the race itself, i.e. somehow mocking the order of threads, is tricky. But let me know if you prefer more tests. |
|
FYI I have been applying the same fix in my production code, it has been running there for weeks successfully. Would be nice to clean up the code by getting this fix into lettuce (currently have the implementation of CacheAccessor in our code base). And of course, to fix the issue for others. |
… race with invalidations. redis#3481
4e32b1b to
8ecc6aa
Compare
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 2 potential issues.
Reviewed by Cursor Bugbot for commit 8ecc6aa. Configure here.
| V value = cacheAccessor.get(key); | ||
|
|
||
| if (value == null) { | ||
| cacheAccessor.setPending(key); |
There was a problem hiding this comment.
Sentinel leaks into map when Redis returns null
Medium Severity
When redisCache.get(key) returns null in the get(K key) method, setPending has already placed the REDIS_IN_PROGRESS sentinel into the user's Map<K, V>, but no subsequent put or evict call is made to clean it up. This leaves a type-unsafe Object in the user-owned typed map indefinitely. The sentinel accumulates for every queried-but-nonexistent key, causing a slow memory leak and map pollution (e.g., map.size() reports phantom entries, iterating values yields ClassCastException). The same issue applies in the get(K key, Callable<V> valueLoader) exception paths.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 8ecc6aa. Configure here.
| @Override | ||
| public void put(K key, V value) { | ||
| map.replace(key, (V) REDIS_IN_PROGRESS, value); | ||
| } |
There was a problem hiding this comment.
put silently drops updates violating public API contract
Medium Severity
The put method now uses map.replace(key, (V) REDIS_IN_PROGRESS, value), which only stores the value when the current entry is the sentinel. The old MapCacheAccessor used unconditional map.put(key, value). This silently breaks the CacheAccessor public interface contract, which documents that put will replace any previously contained mapping. Any external caller using CacheAccessor.forMap() and invoking put directly (e.g., to pre-populate or manually update the cache) will have their writes silently discarded.
Reviewed by Cursor Bugbot for commit 8ecc6aa. Configure here.
|
Rebased to latest main to clean up diff. @tishun , pinging to see if there is any interest in this one? Happy to add tests / edit based on review, anything else, but of course will only do so if there is interest. I personally think this is a crucial bug that can't be ignored. You mentioned in 3481 that there are plans to migrate the code in question anyways. But since it's been some time I'm just looking for an update. |


Closes #3481
Make sure that:
mvn formatter:formattarget. Don’t submit any formatting related changes.Note
Medium Risk
Changes client-side caching semantics during cache misses by introducing a pending marker and conditional writes; concurrency/race behavior is subtle and could impact cache correctness under load.
Overview
Prevents a race where a cache miss triggers a Redis read that later overwrites a concurrent invalidation by introducing a pending state for in-flight Redis
getcalls.CacheAccessorgains a newsetPendinghook (no-op by default),ClientSideCachingmarks keys pending before Redis lookups, and the defaultMapaccessor is replaced withPendingAwareCacheAccessorwhich uses a sentinel andmap.replace(...)to only write results if the key is still pending.Reviewed by Cursor Bugbot for commit 8ecc6aa. Bugbot is set up for automated code reviews on this repo. Configure here.