Linux kernel mirror (for testing) git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
kernel os linux

sunrpc: fix race in cache cleanup causing stale nextcheck time

When cache cleanup runs concurrently with cache entry removal, a race
condition can occur that leads to incorrect nextcheck times. This can
delay cache cleanup for the cache_detail by up to 1800 seconds:

1. cache_clean() sets nextcheck to current time plus 1800 seconds
2. While scanning a non-empty bucket, concurrent cache entry removal can
empty that bucket
3. cache_clean() finds no cache entries in the now-empty bucket to update
the nextcheck time
4. This maybe delays the next scan of the cache_detail by up to 1800
seconds even when it should be scanned earlier based on remaining
entries

Fix this by moving the hash_lock acquisition earlier in cache_clean().
This ensures bucket emptiness checks and nextcheck updates happen
atomically, preventing the race between cleanup and entry removal.

Signed-off-by: Long Li <leo.lilong@huawei.com>
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>

authored by

Long Li and committed by
Chuck Lever
2298abcb 5ca00634

+7 -8
+7 -8
net/sunrpc/cache.c
··· 464 464 } 465 465 } 466 466 467 + spin_lock(&current_detail->hash_lock); 468 + 467 469 /* find a non-empty bucket in the table */ 468 - while (current_detail && 469 - current_index < current_detail->hash_size && 470 + while (current_index < current_detail->hash_size && 470 471 hlist_empty(&current_detail->hash_table[current_index])) 471 472 current_index++; 472 473 473 474 /* find a cleanable entry in the bucket and clean it, or set to next bucket */ 474 - 475 - if (current_detail && current_index < current_detail->hash_size) { 475 + if (current_index < current_detail->hash_size) { 476 476 struct cache_head *ch = NULL; 477 477 struct cache_detail *d; 478 478 struct hlist_head *head; 479 479 struct hlist_node *tmp; 480 480 481 - spin_lock(&current_detail->hash_lock); 482 - 483 481 /* Ok, now to clean this strand */ 484 - 485 482 head = &current_detail->hash_table[current_index]; 486 483 hlist_for_each_entry_safe(ch, tmp, head, cache_list) { 487 484 if (current_detail->nextcheck > ch->expiry_time) ··· 499 502 spin_unlock(&cache_list_lock); 500 503 if (ch) 501 504 sunrpc_end_cache_remove_entry(ch, d); 502 - } else 505 + } else { 506 + spin_unlock(&current_detail->hash_lock); 503 507 spin_unlock(&cache_list_lock); 508 + } 504 509 505 510 return rv; 506 511 }