Sign in

Search issue titles and bodies, change requests and comments. Private repositories you cannot read are not included.

vole
Clone

Read only, no credentials:

https://rodent.software/git/vole.git

Write over SSH with a registered key:

git clone ssh://git@0.0.0.0:2222/vole.git

A free and open-source forge that lives in your Git repo.

Blame · server/pages/cache.go

Line attribution on main · 143 line(s)

d3296cda
Keenan Nemetz
17d ago
1 package pages
2
520470e1
Keenan Nemetz
16d ago
3 // The blob cache is required, not a nicety: cold git reads are ~1.2 ms p50,
4 // and the same reads served from memory are 0.075 ms — the difference between a
5 // docs site that feels like a file server and one that feels like a database.
d3296cda
Keenan Nemetz
17d ago
6
7 import (
8 "container/list"
9 "strings"
10 "sync"
11 )
12
13 type cacheEntry struct {
14 key string
15 commit string
16 sha string
17 ctype string
18 body []byte
19 modUnix int64
20 }
21
22 type lruCache struct {
23 mu sync.Mutex
24 max int64
25 used int64
26 ll *list.List // front = most recently used
27 items map[string]*list.Element
28 // maxEntry keeps one large file from evicting the whole site. Bodies bigger
29 // than this are served from git every time.
30 maxEntry int64
31 // lookups caches path resolution: (commit, path) → tree entry. Without it a
32 // warm request still walks the commit and its trees on every hit, which is
33 // most of the cold cost the cache exists to remove.
34 lookups map[string]treeEntry
35 }
36
37 func newLRU(max int64) *lruCache {
38 if max <= 0 {
39 max = 32 << 20
40 }
41 return &lruCache{
42 max: max,
43 maxEntry: min(max/8, 1<<20),
44 ll: list.New(),
45 items: map[string]*list.Element{},
46 lookups: map[string]treeEntry{},
47 }
48 }
49
50 // lookupGet returns a cached resolution.
51 func (c *lruCache) lookup(key string) (treeEntry, bool) {
52 c.mu.Lock()
53 defer c.mu.Unlock()
54 e, ok := c.lookups[key]
55 return e, ok
56 }
57
58 func (c *lruCache) setLookup(key string, e treeEntry) {
59 c.mu.Lock()
60 defer c.mu.Unlock()
61 // Path resolutions are tiny; a plain cap keeps the map from growing with
62 // history. Clearing is cheap and only costs a walk on the next request.
63 if len(c.lookups) > 8192 {
64 c.lookups = map[string]treeEntry{}
65 }
66 c.lookups[key] = e
67 }
68
69 func (c *lruCache) get(key string) (*cacheEntry, bool) {
70 c.mu.Lock()
71 defer c.mu.Unlock()
72 el, ok := c.items[key]
73 if !ok {
74 return nil, false
75 }
76 c.ll.MoveToFront(el)
77 return el.Value.(*cacheEntry), true
78 }
79
80 func (c *lruCache) put(e *cacheEntry) {
81 size := int64(len(e.body))
82 if size > c.maxEntry || size > c.max {
83 return // not cacheable; served straight from git
84 }
85 c.mu.Lock()
86 defer c.mu.Unlock()
87 if old, ok := c.items[e.key]; ok {
88 c.used -= int64(len(old.Value.(*cacheEntry).body))
89 c.ll.Remove(old)
90 delete(c.items, e.key)
91 }
92 el := c.ll.PushFront(e)
93 c.items[e.key] = el
94 c.used += size
95 for c.used > c.max {
96 back := c.ll.Back()
97 if back == nil {
98 break
99 }
100 victim := back.Value.(*cacheEntry)
101 c.ll.Remove(back)
102 delete(c.items, victim.key)
103 c.used -= int64(len(victim.body))
104 }
105 }
106
107 // dropCommit forgets every path of one commit. A push moves the ref, so the old
108 // commit's pages are unreachable and their memory may as well be freed now
109 // rather than when the LRU happens to reach them.
110 func (c *lruCache) dropCommit(commit string) {
111 if commit == "" {
112 return
113 }
114 c.mu.Lock()
115 defer c.mu.Unlock()
116 for key, el := range c.items {
117 e := el.Value.(*cacheEntry)
118 if e.commit != commit {
119 continue
120 }
121 c.ll.Remove(el)
122 delete(c.items, key)
123 c.used -= int64(len(e.body))
124 }
125 for key := range c.lookups {
126 if strings.HasPrefix(key, commit+"\x00") {
127 delete(c.lookups, key)
128 }
129 }
130 }
131
132 func (c *lruCache) stats() (entries int, bytes int64) {
133 c.mu.Lock()
134 defer c.mu.Unlock()
135 return len(c.items), c.used
136 }
137
138 func min(a, b int64) int64 {
139 if a < b {
140 return a
141 }
142 return b
143 }