diff --git a/common/lru/basiclru.go b/common/lru/basiclru.go index 7386c77840..fc178e804d 100644 --- a/common/lru/basiclru.go +++ b/common/lru/basiclru.go @@ -22,9 +22,10 @@ package lru // This type is not safe for concurrent use. // The zero value is not valid, instances must be created using NewCache. type BasicLRU[K comparable, V any] struct { - list *list[K] - items map[K]cacheItem[K, V] - cap int + list *list[K] + items map[K]cacheItem[K, V] + cap int + onEvicted func(key K, value V) } type cacheItem[K any, V any] struct { @@ -61,6 +62,10 @@ func (c *BasicLRU[K, V]) Add(key K, value V) (evicted bool) { elem = c.list.removeLast() delete(c.items, elem.v) evicted = true + if c.onEvicted != nil { + v := c.items[elem.v] + c.onEvicted(elem.v, v.value) + } } else { elem = new(listElem[K]) } @@ -142,6 +147,11 @@ func (c *BasicLRU[K, V]) RemoveOldest() (key K, value V, ok bool) { return key, item.value, true } +// OnEvicted sets a callback function to be called when an item is evicted from the cache. +func (c *BasicLRU[K, V]) OnEvicted(fn func(k K, v V)) { + c.onEvicted = fn +} + // Keys returns all keys in the cache. func (c *BasicLRU[K, V]) Keys() []K { keys := make([]K, 0, len(c.items)) diff --git a/common/lru/lru.go b/common/lru/lru.go index 45965adb0d..8a3da5de79 100644 --- a/common/lru/lru.go +++ b/common/lru/lru.go @@ -93,3 +93,11 @@ func (c *Cache[K, V]) Keys() []K { return c.cache.Keys() } + +// OnEvicted sets a callback function to be called when an item is evicted from the cache. +func (c *Cache[K, V]) OnEvicted(fn func(key K, value V)) { + c.mu.Lock() + defer c.mu.Unlock() + + c.cache.OnEvicted(fn) +} diff --git a/core/rawdb/eradb/eradb.go b/core/rawdb/eradb/eradb.go index a36117c799..28d245451b 100644 --- a/core/rawdb/eradb/eradb.go +++ b/core/rawdb/eradb/eradb.go @@ -61,19 +61,32 @@ func New(datadir string) (*EraDatabase, error) { return nil, err } db := &EraDatabase{datadir: datadir, cache: lru.NewCache[uint64, *era.Era](openFileLimit)} + db.cache.OnEvicted(func(key uint64, value *era.Era) { + // Close the era1 file when it is evicted from the cache + // to avoid leaks. + if value != nil { + if err := value.Close(); err != nil { + log.Warn("Error closing era1 file", "epoch", key, "err", err) + } + } + }) log.Info("Opened erastore", "datadir", datadir) return db, nil } // Close closes all open era1 files in the cache. -func (db *EraDatabase) Close() { +func (db *EraDatabase) Close() error { // Close all open era1 files in the cache. keys := db.cache.Keys() + errs := make([]error, len(keys)) for _, key := range keys { if e, ok := db.cache.Get(key); ok { - e.Close() + if err := e.Close(); err != nil { + errs = append(errs, err) + } } } + return errors.Join(errs...) } // GetRawBody returns the raw body for a given block number. diff --git a/core/rawdb/freezer.go b/core/rawdb/freezer.go index ca865b9222..d545005295 100644 --- a/core/rawdb/freezer.go +++ b/core/rawdb/freezer.go @@ -176,7 +176,9 @@ func (f *Freezer) Close() error { errs = append(errs, err) } if f.eradb != nil { - f.eradb.Close() + if err := f.eradb.Close(); err != nil { + errs = append(errs, err) + } } }) if errs != nil {