Fix race condition for lookupCommand (#14466)

Fixes data race where main thread modifies pauserehash in dictNext while
IO thread reads `useStoredKeyApi()` in `lookupCommand()->dictFind()`
path.
ASAN detected overlapping memory access at same byte offset
causing race condition. When bit fields are adjacent in a struct,
modifying one bit field requires a read-modify-write operation on the
entire memory unit, which can cause race conditions with concurrent
access to other bit fields in the same unit.
This was introduced by https://github.com/redis/redis/pull/13696,
although it was changed by https://github.com/redis/redis/pull/14440,
but this issue still exist.

The fix moves `useStoredKeyApi` to share a memory word with
`pauseAutoResize` instead. Since `pauseAutoResize` is never modified for
`server.commands`, this eliminates the race condition while maintaining
memory efficiency through bit field packing.

Reproduce step:
```
make SANITIZER=thread
./runtest --tsan --config io-threads 4 --accurate --verbose --dump-logs --single unit/obuf-limits --loop --stop
```

failed CI:
https://github.com/redis/redis/actions/runs/18803219305/job/53653572710

---------

Co-authored-by: Moti Cohen <moti.cohen@redis.com>
This commit is contained in:
debing.sun 2025-10-28 21:35:03 +08:00 committed by GitHub
parent 91b5808fd6
commit 6ea4e2c949
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -127,12 +127,14 @@ struct dict {
long rehashidx; /* rehashing not in progress if rehashidx == -1 */
/* Keep small vars at end for optimal (minimal) struct padding */
unsigned pauserehash : 15; /* If >0 rehashing is paused */
/* Note: pauserehash is a full unsigned so iterator increments
* don't perform RMW on the same storage unit as other bitfields. */
unsigned pauserehash; /* If >0 rehashing is paused */
unsigned useStoredKeyApi : 1; /* See comment of storedHashFunction above */
/* Keep small vars at end for optimal (minimal) struct padding */
signed char ht_size_exp[2]; /* exponent of size. (size = 1<<exp) */
int16_t pauseAutoResize; /* If >0 automatic resizing is disallowed (<0 indicates coding error) */
signed pauseAutoResize: 15; /* If >0 automatic resizing is disallowed (<0 indicates coding error) */
unsigned useStoredKeyApi: 1; /* See comment of storedHashFunction above */
void *metadata[];
};