133728e barerepo 1mo
1
// Package cache keys answers by immutable object hash, so it never needs invalidation.
133728e barerepo 1mo
2
package cache
133728e barerepo 1mo
4
import (
133728e barerepo 1mo
5
"crypto/sha256"
133728e barerepo 1mo
6
"encoding/hex"
133728e barerepo 1mo
7
"os"
133728e barerepo 1mo
8
"path/filepath"
133728e barerepo 1mo
9
"sync"
133728e barerepo 1mo
12
// Disk is content-addressed, and deleting the directory is safe because the server rebuilds.
133728e barerepo 1mo
13
type Disk struct {
133728e barerepo 1mo
14
root string
133728e barerepo 1mo
15
mu sync.Mutex
133728e barerepo 1mo
16
hot map[string][]byte
133728e barerepo 1mo
19
// hotLimit bounds the memory half, where a page reads the same few entries in a row.
133728e barerepo 1mo
20
const hotLimit = 512
133728e barerepo 1mo
22
func New(root string) *Disk {
133728e barerepo 1mo
23
return &Disk{root: root, hot: map[string][]byte{}}
133728e barerepo 1mo
26
// Get returns a cached value, or false.
133728e barerepo 1mo
27
func (d *Disk) Get(kind, key string) ([]byte, bool) {
133728e barerepo 1mo
28
if d == nil {
133728e barerepo 1mo
29
return nil, false
133728e barerepo 1mo
31
k := kind + ":" + key
133728e barerepo 1mo
32
d.mu.Lock()
133728e barerepo 1mo
33
if v, ok := d.hot[k]; ok {
133728e barerepo 1mo
34
d.mu.Unlock()
133728e barerepo 1mo
35
return v, true
133728e barerepo 1mo
37
d.mu.Unlock()
133728e barerepo 1mo
39
body, err := os.ReadFile(d.path(kind, key))
133728e barerepo 1mo
40
if err != nil {
133728e barerepo 1mo
41
return nil, false
133728e barerepo 1mo
43
d.remember(k, body)
133728e barerepo 1mo
44
return body, true
133728e barerepo 1mo
47
// Put ignores a write failure, because the next reader recomputes, which is the point.
133728e barerepo 1mo
48
func (d *Disk) Put(kind, key string, body []byte) {
133728e barerepo 1mo
49
if d == nil {
133728e barerepo 1mo
50
return
133728e barerepo 1mo
52
d.remember(kind+":"+key, body)
133728e barerepo 1mo
53
path := d.path(kind, key)
133728e barerepo 1mo
54
if err := os.MkdirAll(filepath.Dir(path), 0o750); err != nil {
133728e barerepo 1mo
55
return
133728e barerepo 1mo
57
tmp := path + ".tmp"
133728e barerepo 1mo
58
if err := os.WriteFile(tmp, body, 0o640); err != nil {
133728e barerepo 1mo
59
return
133728e barerepo 1mo
61
// Rename, so a reader never sees half an answer.
133728e barerepo 1mo
62
_ = os.Rename(tmp, path)
133728e barerepo 1mo
65
func (d *Disk) remember(k string, body []byte) {
133728e barerepo 1mo
66
d.mu.Lock()
133728e barerepo 1mo
67
defer d.mu.Unlock()
133728e barerepo 1mo
68
if len(d.hot) >= hotLimit {
133728e barerepo 1mo
69
d.hot = map[string][]byte{}
133728e barerepo 1mo
71
d.hot[k] = body
133728e barerepo 1mo
74
// path spreads entries over two levels, so no directory holds a million files.
133728e barerepo 1mo
75
func (d *Disk) path(kind, key string) string {
133728e barerepo 1mo
76
sum := sha256.Sum256([]byte(key))
133728e barerepo 1mo
77
name := hex.EncodeToString(sum[:])
133728e barerepo 1mo
78
return filepath.Join(d.root, kind, name[:2], name[2:])