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 }