From 86e6b7ed82d810699aef419ab076a62ea73aefb5 Mon Sep 17 00:00:00 2001 From: Natalia Portillo Date: Wed, 2 Sep 2026 00:03:52 +0100 Subject: [PATCH] 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. --- src/lru.c | 26 ++++++++++++++++++++++---- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/src/lru.c b/src/lru.c index 4ab638a..9273d3f 100644 --- a/src/lru.c +++ b/src/lru.c @@ -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;