Skip to content

Fix single-item cache bug#99

Merged
karlseguin merged 3 commits intokarlseguin:masterfrom
pior:memleak
Jan 8, 2026
Merged

Fix single-item cache bug#99
karlseguin merged 3 commits intokarlseguin:masterfrom
pior:memleak

Conversation

@pior
Copy link
Contributor

@pior pior commented Jan 8, 2026

Fixes a bug introduced in v3.0.7 where the first/only item in the cache is incorrectly handled during promotion and deletion.

Bug

The v3.0.7 release changed from external Node pointers to intrinsic linked list pointers (next/prev embedded in Item).
The code uses next == nil && prev == nil to detect if an item is not in the list.

However, when inserting into an empty list, Insert() returns early without setting next or prev, leaving both as nil. This means the first/only item in the cache has both pointers nil while legitimately being in the list.

This causes two bugs:

  1. Promotion (doPromote): A single item is incorrectly treated as "new", causing size to be incremented again
  2. Deletion (doDelete): The cleanup path is skipped entirely, size is not decremented and onDelete callback is never called.

Changes

  • Add an explicit inList boolean field to Item
  • Set inList to true in Insert() and false in Remove().
  • Replace all next/prev nil checks with inList in both Cache and LayeredCache.

Performance

The inList boolean adds a small memory overhead (~1-8 bytes per item including padding).
On amd64 the Item struct already uses 2 cache-lines, the new field does not change that.

Negligible CPU cost reduction: from two pointer comparisons to a single boolean check.

The intrinsic linked list change (61f5066) introduced a bug where items with next==nil && prev==nil are incorrectly detected as "not in list".
This affects the first/only item in the cache:
- Promotion treats it as new, double-counting size
- Deletion skips size decrement and onDelete callback
@pior pior marked this pull request as ready for review January 8, 2026 11:32
The intrinsic linked list change (61f5066) introduced a bug where items with next==nil && prev==nil are incorrectly detected as "not in list".
This affects the first/only item in the cache:
- Promotion treats it as new, double-counting size
- Deletion skips size decrement and onDelete callback
The intrinsic linked list change used next==nil && prev==nil to detect items not in the list.
This fails for the first/only item, which has both pointers nil while legitimately being in the list.

Add an inList boolean to Item, set by Insert/Remove, and use it in doPromote/doDelete instead of checking pointer state.

Fixes size double-counting on promotion and missing cleanup on delete.

The item struct already uses two cache-lines, adding the inList field does not have any significant performance impact.
@karlseguin karlseguin merged commit 6d2af3f into karlseguin:master Jan 8, 2026
@karlseguin
Copy link
Owner

Thank you.

@pior
Copy link
Contributor Author

pior commented Jan 16, 2026

Feedback after 4 days in production, and ~30 deploys: the memory leak is gone.

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.

2 participants