Replace the existing value when caching a key that is already present

add_to_cache_uint64() called HASH_ADD without first looking the key up.
uthash keeps both entries in that case, but HASH_FIND only ever reaches the
newer one, so the older entry becomes unreachable and its value stays alive
until free_cache() tears the whole cache down. It also counted twice against
the cache budget.

ec_recover_data_block() reaches this: it caches the recovered BlockHeader for
a block offset that may already be in block_header_cache, because a header
read from the file is cached before the corruption check that sends the read
into erasure recovery.

Look the key up first, and on a hit free the old value and reuse the entry.
This commit is contained in:
2026-09-02 00:03:52 +01:00
parent 598d9224a7
commit 86e6b7ed82

View File

@@ -55,8 +55,9 @@ void *find_in_cache_uint64(struct CacheHeader *cache, const uint64_t key)
/**
* @brief Adds a value to the cache with a uint64_t key, evicting LRU entries if over budget.
*
* Adds a new entry to the cache. If the cache exceeds its memory budget, the least recently
* used entries are evicted until it fits again. The entry just inserted is never evicted, so the
* Adds a new entry to the cache. If an entry with the same key is already present, its value
* is freed and replaced. If the cache exceeds its memory budget, the least recently used
* entries are evicted until it fits again. The entry just inserted is never evicted, so the
* caller may keep using the pointer it handed over for the remainder of the call.
*
* @param cache Pointer to the cache header.
@@ -66,8 +67,25 @@ void *find_in_cache_uint64(struct CacheHeader *cache, const uint64_t key)
*/
void add_to_cache_uint64(struct CacheHeader *cache, const uint64_t key, void *value, const size_t size)
{
struct CacheEntry *entry = malloc(sizeof(struct CacheEntry));
if(!entry) return;
struct CacheEntry *entry = NULL;
// Replace an existing entry for this key. A blind HASH_ADD would leave the old entry in the
// table unreachable by HASH_FIND, and its value alive until the whole cache is freed.
HASH_FIND(hh, cache->cache, &key, sizeof(uint64_t), entry);
if(entry)
{
cache->cur_bytes -= entry->size;
if(cache->free_func && entry->value && entry->value != value) cache->free_func(entry->value);
HASH_DELETE(hh, cache->cache, entry);
}
else
{
entry = malloc(sizeof(struct CacheEntry));
if(!entry) return;
}
entry->key = key;
entry->value = value;