Skip to content

[Bug] DirCache LRU implementation fails to evict items on write and wipes cache during iteration #2095

Description

@raj-prince

Describe the Bug

DirCache uses functools.lru_cache to limit the number of cached paths to max_paths:

if max_paths:
    self._q = lru_cache(max_paths + 1)(lambda key: self._cache.pop(key, None))

However, functools.lru_cache only calls the wrapped function (self._cache.pop) when a key is a cache miss (computing a value for a new key), NOT when an old key is evicted from lru_cache's internal index.

This causes three major issues in DirCache:

  1. Memory Leak (No Eviction on Write): Adding entries beyond max_paths never deletes old entries from self._cache. self._cache grows without bound.
  2. Deferred Deletion on Read: When an old key that fell out of lru_cache is read via __getitem__, _q treats it as a miss and calls self._cache.pop(key), deleting key from _cache at read time and raising a KeyError.
  3. Cache Destruction during Iteration: __iter__ filters entries using k in self (__getitem__), triggering cascading lru_cache misses. As a result, calling list(dircache) deletes every single item in _cache and returns [].

Minimal Reproducible Example

from fsspec.dircache import DirCache

dc = DirCache(max_paths=2)

# 1. Add 4 items (exceeding max_paths=2)
dc["a"] = 1
dc["b"] = 2
dc["c"] = 3
dc["d"] = 4

# Bug 1: _cache size is 4 instead of 2
print("Actual size:", len(dc._cache))  # Output: 4 (Expected: 2)

# Bug 2: Iterating wipes out the cache and returns []
print("Keys yielded by iter:", list(dc))  # Output: []
print("Cache after iter:", dc._cache)     # Output: {}

Expected Behavior

  • Adding keys beyond max_paths should immediately evict the least recently set keys from self._cache. len(dc._cache) should never exceed max_paths.
  • Iterating over dircache (list(dc)) should yield valid, unexpired keys without mutating or clearing the cache.

Environment Information

  • Library: fsspec
  • Python Version: 3.8+

Suggested Fix

Replace functools.lru_cache with collections.OrderedDict (or standard dict popping) in DirCache to handle LRU ordering directly upon __setitem__ and __getitem__.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions