blob: 114939b6671da2ccfde1bba00fa4477340ce5be6 [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Jan Schär75ea9f42024-07-29 17:01:41 +02004// Package cache implements a cache. The cache hold 256 shards, each shard
5// holds a cache: a map with a mutex. There is no fancy expunge algorithm, it
6// just randomly evicts elements when it gets full.
7package cache
8
9// Taken and modified from CoreDNS, under Apache 2.0.
10
11import (
12 "sync"
13
14 "golang.org/x/sys/cpu"
15)
16
17const shardSize = 256
18
19// Cache is cache.
20type Cache[T any] struct {
21 shards [shardSize]shard[T]
22}
23
24// shard is a cache with random eviction.
25type shard[T any] struct {
26 items map[uint64]T
27 size int
28
29 sync.RWMutex
30
31 _ cpu.CacheLinePad
32}
33
34// New returns a new cache.
35func New[T any](size int) *Cache[T] {
36 ssize := size / shardSize
37 if ssize < 4 {
38 ssize = 4
39 }
40
41 c := &Cache[T]{}
42
43 // Initialize all the shards
44 for i := 0; i < shardSize; i++ {
45 c.shards[i] = shard[T]{items: make(map[uint64]T), size: ssize}
46 }
47 return c
48}
49
50// Get returns the element under key, and whether the element exists.
51func (c *Cache[T]) Get(key uint64) (el T, exists bool) {
52 shard := key % shardSize
53 return c.shards[shard].Get(key)
54}
55
56// Put adds a new element to the cache. If the element already exists,
57// it is overwritten.
58func (c *Cache[T]) Put(key uint64, el T) {
59 shard := key % shardSize
60 c.shards[shard].Put(key, el)
61}
62
63// GetOrPut returns the element under key if it exists,
64// or else stores newEl there. This operation is atomic.
65func (c *Cache[T]) GetOrPut(key uint64, newEl T) (el T, exists bool) {
66 shard := key % shardSize
67 return c.shards[shard].GetOrPut(key, newEl)
68}
69
70// Remove removes the element indexed with key.
71func (c *Cache[T]) Remove(key uint64) {
72 shard := key % shardSize
73 c.shards[shard].Remove(key)
74}
75
76func (s *shard[T]) Get(key uint64) (el T, exists bool) {
77 s.RLock()
78 el, exists = s.items[key]
79 s.RUnlock()
80 return
81}
82
83func (s *shard[T]) Put(key uint64, el T) {
84 s.Lock()
85 if len(s.items) >= s.size {
86 if _, ok := s.items[key]; !ok {
87 for k := range s.items {
88 delete(s.items, k)
89 break
90 }
91 }
92 }
93 s.items[key] = el
94 s.Unlock()
95}
96
97func (s *shard[T]) GetOrPut(key uint64, newEl T) (el T, exists bool) {
98 s.Lock()
99 el, exists = s.items[key]
100 if !exists {
101 if len(s.items) >= s.size {
102 for k := range s.items {
103 delete(s.items, k)
104 break
105 }
106 }
107 s.items[key] = newEl
108 el = newEl
109 }
110 s.Unlock()
111 return
112}
113
114func (s *shard[T]) Remove(key uint64) {
115 s.Lock()
116 delete(s.items, key)
117 s.Unlock()
118}