diff options
| author | 2026-06-09 03:05:45 +0000 | |
|---|---|---|
| committer | 2026-06-09 05:07:02 +0000 | |
| commit | 3dd2d95b8347b2b0572a5ad90cbb7c1c84e9a07a (patch) | |
| tree | 144a3e94bd4a03097bfb29741be1028d1740422b /internal/clock/shard_write.go | |
| parent | internal/mru: Fewer files (diff) | |
| signature | No signature | |
internal/lru: Add sharded CLOCK
Diffstat (limited to 'internal/clock/shard_write.go')
| -rw-r--r-- | internal/clock/shard_write.go | 105 |
1 files changed, 105 insertions, 0 deletions
diff --git a/internal/clock/shard_write.go b/internal/clock/shard_write.go new file mode 100644 index 00000000..40ddabd0 --- /dev/null +++ b/internal/clock/shard_write.go @@ -0,0 +1,105 @@ +package clock + +// add inserts or replaces key, then evicts down to budget. +// +// It reports whether the entry was admitted; +// an entry heavier than the shard budget is rejected +// and leaves the shard unchanged. +func (shard *shard[K, V]) add(key K, value V, weight uint64) bool { + if weight > shard.maxWeight { + return false + } + + shard.mu.Lock() + defer shard.mu.Unlock() + + if old, ok := shard.items.Load(key); ok { + shard.unlink(old) + shard.items.Delete(key) + shard.weight -= old.weight + shard.count-- + } + + e := &entry[K, V]{ //nolint:exhaustruct + key: key, + value: value, + weight: weight, + } + e.referenced.Store(true) + + shard.linkBeforeHand(e) + shard.items.Store(key, e) + shard.weight += weight + shard.count++ + + shard.evict() + + return true +} + +// evict advances the clock hand until the shard is within budget. +// +// A referenced entry is spared once, its bit cleared; +// after a full rotation of spared entries the next is evicted regardless, +// so a flood of concurrent reads cannot stall progress. +func (shard *shard[K, V]) evict() { + skips := 0 + + for shard.weight > shard.maxWeight && shard.hand != nil { + victim := shard.hand + + if victim.referenced.Load() && skips < shard.count { + victim.referenced.Store(false) + shard.hand = victim.next + skips++ + + continue + } + + shard.unlink(victim) + shard.items.Delete(victim.key) + shard.weight -= victim.weight + shard.count-- + skips = 0 + } +} + +// linkBeforeHand inserts e just behind the hand, +// so a full rotation passes before the sweep examines it. +func (shard *shard[K, V]) linkBeforeHand(e *entry[K, V]) { + if shard.hand == nil { + e.prev = e + e.next = e + shard.hand = e + + return + } + + tail := shard.hand.prev + tail.next = e + e.prev = tail + e.next = shard.hand + shard.hand.prev = e +} + +// unlink removes e from the ring, +// moving the hand off e when it points at it. +func (shard *shard[K, V]) unlink(e *entry[K, V]) { + if e.next == e { + shard.hand = nil + e.prev = nil + e.next = nil + + return + } + + e.prev.next = e.next + e.next.prev = e.prev + + if shard.hand == e { + shard.hand = e.next + } + + e.prev = nil + e.next = nil +} |
