Skip to content

fix: LRU edge cases in TTL handling and factory validation - #488

Merged
avoidwork merged 4 commits into
masterfrom
fix/lru-edge-cases-ttl-validation
Sep 15, 2026
Merged

avoidwork merged 4 commits into
masterfrom
fix/lru-edge-cases-ttl-validation

Conversation

@avoidwork

Copy link
Copy Markdown
Owner

Description

Fixes 14 edge cases in TTL handling and factory validation for the LRU cache. Validates constructor inputs, reclaims expired keys on set(), skips expired items in read methods, makes forEach() mutation-safe, fixes get() stats, validates array inputs, corrects expiry=0 semantics, and silences setWithEvicted() eviction notification.

Type of Change

  • Bugfix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Documentation update
  • Refactor (no functional changes)
  • Performance improvement
  • CI / build / tooling

Testing

Added 19 new unit tests covering the edge cases: factory validation with non-integer/negative/boolean inputs, expired-key set() with both resetTTL values, read-method TTL enforcement, forEach() mutation safety, get() stats on expired, getMany()/has*() array validation, expiry=0 semantics, and setWithEvicted() silent eviction. Updated 3 existing tests for the corrected expiry=0 behavior.

Coverage

  • 100% line coverage maintained

Checklist

  • npm run lint passes
  • Tests pass with 100% line coverage
  • No forbidden patterns used
  • Conventional Commit style applied

- Validate max/ttl/resetTTL in constructor (reject non-integer, negative, non-boolean)
- Reclaim expired keys on set()/setWithEvicted() instead of leaving dead slots
- Skip expired items in values()/entries()/forEach()/toJSON()
- Make forEach() mutation-safe (capture next pointer before callback)
- Stop incrementing deletes when get() removes an expired item on a miss
- Validate array input in getMany()/hasAll()/hasAny()/values()/entries()
- Treat expiry=0 with ttl>0 as expired in sizeByTTL/keysByTTL/valuesByTTL
- Use silent eviction in setWithEvicted() (no onEvict double-fire)

Fixes #487
@avoidwork

Copy link
Copy Markdown
Owner Author

Performance Audit — No Regression

Ran the benchmark:modern suite as an A/B against the pre-PR build. Verdict: net-neutral to slightly positive. Safe to merge.

Results (pre-PR vs. PR)

Improvements (the bulk):

Benchmark Latency Δ Throughput Δ
get-hit-sequential-100 -17.0% +20.5%
set-random-empty-cache-1000 -11.8% +1.4%
get-hit-random-1000 -8.1% +9.4%
set-random-full-cache-100 -8.3% +5.3%
get-miss-5000 -8.9% +1.0%
has-operation -6.8% +1.7%

Slight regressions (all within noise):

Benchmark Latency Δ Throughput Δ
get-hit-random-5000 +4.2% -3.1%
get-hit-sequential-5000 +4.6% -3.3%
object-values +9.8% -1.9%
set-sequential-empty-cache-5000 +4.6% +1.4%

Why the regressions aren't real

The 5000-size cases report ±19% variance in the benchmark's own output. A +4% shift on a ±19% measurement is pure noise. The object-values +9.8% is a single-sample blip on a 65ns operation — also noise.

Code-level reasoning

  • #removeItem / #evictItem extract duplicated logic — no new work, just consolidation. delete + #unlink + pointer-null is the same work as before.
  • #isExpired now does one fewer comparison (item.expiry === 0 removed) — strictly cheaper on the hot get/set/has path.
  • The added #isExpired branches in values/entries/forEach/toJSON add a per-item check, but those aren't benchmarked hot paths, and the branch is cheap (one comparison).

Bottom line: The get/set/has hot paths are actually faster, and the only "regressions" are inside the noise floor. Safe to merge.

@avoidwork
avoidwork merged commit 2478464 into master Sep 15, 2026
4 checks passed
@avoidwork
avoidwork deleted the fix/lru-edge-cases-ttl-validation branch September 15, 2026 10:57
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant