Skip to content

fix(memory): stop listing and counting expired entries - #200

Merged
allen0099 merged 1 commit into
masterfrom
fix/memory-skip-expired-entries
Sep 26, 2026
Merged

allen0099 merged 1 commit into
masterfrom
fix/memory-skip-expired-entries

Conversation

@allen0099

Copy link
Copy Markdown
Owner

Closes #178.

Problem

An entry whose TTL had passed stayed in MemoryBackend.cache until the next sweep or get(). During that time, it was still visible:

  • get_all_keys() and get_cache_data() listed it.
  • clear_pattern(), clear_path() and delete_many() counted it as removed.

Redis never returns an expired key, so the same live data produced different numbers per backend. For example, CacheManager.clear_prefix() with one live key and one expired key returned 2 on memory and 1 on Redis.

Change

  • _evict(), which clear_pattern, clear_path and delete_many share, still removes every matching entry but counts only the unexpired ones.
  • get_all_keys() and get_cache_data() skip expired entries.

Effect on the monitoring routes

/cached-hits and /cached-records still compute is_expired and the expired_* counts. With the built-in backends, those now stay at zero apart from an entry that expires between the listing and the route reading the clock, as they already did on Redis. The route logic is kept for that case and for third-party backends that do list expired entries.

TestExpiredEntryMonitoring injected an expired entry into the memory backend's dict to exercise that logic. It now stubs get_cache_data() to return one, so it tests the route without relying on the old backend behaviour.

Tests

  • test_memory_backend_enumeration_skips_expired_entries.
  • test_memory_backend_does_not_count_expired_entries_as_cleared, parametrized over clear_pattern, clear_path and delete_many.
  • test_clear_prefix_does_not_count_expired_keys, run on both memory and Redis to pin the parity.
  • The new tests expire entries by rewriting expiry on memory and with PEXPIRE 1 on Redis, not by sleeping for a TTL.
  • Mutation check: with the backend change reverted, exactly the five new memory cases fail. The Redis case passes either way, as expected.
  • Full suite against live Redis and Memcached (CACHEX_REQUIRE_LIVE_SERVERS=1): 857 passed, 100% coverage.

Entries that had expired but not yet been swept were returned by
get_all_keys() and get_cache_data(), and counted as removed by
clear_pattern(), clear_path() and delete_many(). Redis never returns an
expired key, so CacheManager.clear_prefix() and the monitoring routes
reported different numbers depending on the backend.

Closes #178
@allen0099 allen0099 added this to the 0.3.8 milestone Sep 26, 2026
@allen0099 allen0099 added bug Something isn't working backends Cache backends and their atomic primitives labels Sep 26, 2026
@allen0099
allen0099 merged commit e7b6a92 into master Sep 26, 2026
11 checks passed
@allen0099
allen0099 deleted the fix/memory-skip-expired-entries branch September 26, 2026 12:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

backends Cache backends and their atomic primitives bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

MemoryBackend: leave expired entries out of enumeration and clear counts

1 participant