Skip to content

Commit dd559eb

Browse files
[#3618] Fix client-side cache invalidation for mixed str and bytes Redis keys (#3766)
* Support bytes key for client-side caching * Prepare both versions for lookup * linter fix * cache_key check for only one matched against multiple candidates fix * revert back correct changes
1 parent cd42f61 commit dd559eb

File tree

2 files changed

+48
-4
lines changed

2 files changed

+48
-4
lines changed

redis/cache.py

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -212,15 +212,25 @@ def delete_by_cache_keys(self, cache_keys: List[CacheKey]) -> List[bool]:
212212

213213
return response
214214

215-
def delete_by_redis_keys(self, redis_keys: List[bytes]) -> List[bool]:
215+
def delete_by_redis_keys(
216+
self, redis_keys: Union[List[bytes], List[str]]
217+
) -> List[bool]:
216218
response = []
217219
keys_to_delete = []
218220

219221
for redis_key in redis_keys:
220-
if isinstance(redis_key, bytes):
221-
redis_key = redis_key.decode()
222+
# Prepare both versions for lookup
223+
candidates = [redis_key]
224+
if isinstance(redis_key, str):
225+
candidates.append(redis_key.encode("utf-8"))
226+
elif isinstance(redis_key, bytes):
227+
try:
228+
candidates.append(redis_key.decode("utf-8"))
229+
except UnicodeDecodeError:
230+
pass # Non-UTF-8 bytes, skip str version
231+
222232
for cache_key in self._cache:
223-
if redis_key in cache_key.redis_keys:
233+
if any(candidate in cache_key.redis_keys for candidate in candidates):
224234
keys_to_delete.append(cache_key)
225235
response.append(True)
226236

tests/test_cache.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1322,6 +1322,40 @@ def test_delete_by_redis_keys_removes_associated_entries(self, mock_connection):
13221322
assert len(cache.collection) == 1
13231323
assert cache.get(cache_key4).cache_value == b"bar3"
13241324

1325+
def test_delete_by_redis_keys_with_non_utf8_bytes_key(self, mock_connection):
1326+
"""cache fails to invalidate entries when redis_keys contain non-UTF-8 bytes."""
1327+
cache = DefaultCache(CacheConfig(max_size=5))
1328+
1329+
# Valid UTF-8 key works
1330+
utf8_key = b"foo"
1331+
utf8_cache_key = CacheKey(command="GET", redis_keys=(utf8_key,))
1332+
assert cache.set(
1333+
CacheEntry(
1334+
cache_key=utf8_cache_key,
1335+
cache_value=b"bar",
1336+
status=CacheEntryStatus.VALID,
1337+
connection_ref=mock_connection,
1338+
)
1339+
)
1340+
1341+
# Non-UTF-8 bytes key
1342+
bad_key = b"f\xffoo"
1343+
bad_cache_key = CacheKey(command="GET", redis_keys=(bad_key,))
1344+
assert cache.set(
1345+
CacheEntry(
1346+
cache_key=bad_cache_key,
1347+
cache_value=b"bar2",
1348+
status=CacheEntryStatus.VALID,
1349+
connection_ref=mock_connection,
1350+
)
1351+
)
1352+
1353+
# Delete both keys: utf8 should succeed, non-utf8 exposes bug
1354+
results = cache.delete_by_redis_keys([utf8_key, bad_key])
1355+
1356+
assert results[0] is True
1357+
assert results[1] is True, "Cache did not remove entry for non-UTF8 bytes key"
1358+
13251359
def test_flush(self, mock_connection):
13261360
cache = DefaultCache(CacheConfig(max_size=5))
13271361

0 commit comments

Comments
 (0)