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.

Code

392 commit(s) on main

Merge change request 01M70WES5ZZDJ6RP226WB11BBZ: Let people rename and remove their own passkeys keenan · 23m ago 4b8c9e1d
history blame raw 3.2 KiB
1package pages
2
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.
6
7import (
8 "container/list"
9 "strings"
10 "sync"
11)
12
13type cacheEntry struct {
14 key string
15 commit string
16 sha string
17 ctype string
18 body []byte
19 modUnix int64
20}
21
22type 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
37func 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.
51func (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
58func (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
69func (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
80func (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.
110func (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
132func (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
138func min(a, b int64) int64 {
139 if a < b {
140 return a
141 }
142 return b
143}
144