From 55676a35757bcbf2fa40cc3fd144ba412c65b658 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Tue, 9 Jun 2026 05:15:58 +0000 Subject: internal/cache: add (and move clock to internal/cache/clock) --- internal/cache/clock/shard_write.go | 105 ++++++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 internal/cache/clock/shard_write.go (limited to 'internal/cache/clock/shard_write.go') diff --git a/internal/cache/clock/shard_write.go b/internal/cache/clock/shard_write.go new file mode 100644 index 00000000..40ddabd0 --- /dev/null +++ b/internal/cache/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 +} -- cgit v1.3.1-10-gc9f91