~bigbes/sr-ht-dolt

ref: 2acea60f6caf786a6ae6605609bb66898369c8a1 sr-ht-dolt/storage/dbcache.go -rw-r--r-- 3.7 KiB
2acea60f — Eugene Blikh bd: update sync.remote 8 days ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package storage

import (
	"context"
	"fmt"
	"sync"

	"github.com/dolthub/dolt/go/libraries/doltcore/remotesrv"
	"github.com/dolthub/dolt/go/store/nbs"

	"sourcecraft.dev/bigbes/sr-ht-dolt/core"
)

// defaultMemTableSize mirrors upstream utils/remotesrv LocalCSCache: the
// in-memory table size handed to nbs.NewLocalStore before chunks spill to disk.
const defaultMemTableSize = 128 * 1024 * 1024

// RepoLookup resolves a repository's owner and database name to its absolute
// on-disk store directory. It returns an error when no repository row exists;
// Cache.Get propagates that error unchanged so the remotesapi interceptor layer
// can map it to a gRPC NotFound (v1 has no push-to-create: databases are made
// explicitly through the web UI).
//
// It is defined here as a function type so storage/ never imports db/; the main
// wiring injects a db-backed lookup.
type RepoLookup func(ctx context.Context, owner, name string) (diskPath string, err error)

// Cache is a remotesrv.DBCache that serves only repositories with an existing
// row, memoizing one nbs.NewLocalStore per absolute disk path. It never creates
// directories: unknown repos are errors, not implicit creations. It is safe for
// concurrent use.
type Cache struct {
	lookup RepoLookup

	mu  sync.Mutex
	dbs map[string]remotesrv.RemoteSrvStore
}

var _ remotesrv.DBCache = (*Cache)(nil)

// NewCache builds a Cache backed by lookup. lookup must be non-nil.
func NewCache(lookup RepoLookup) *Cache {
	if lookup == nil {
		panic("storage: NewCache requires a non-nil RepoLookup")
	}
	return &Cache{
		lookup: lookup,
		dbs:    make(map[string]remotesrv.RemoteSrvStore),
	}
}

// Get resolves the remotesapi repo path to an on-disk store and returns a
// memoized NBS chunk store for it. The path is normalized with
// core.ParseRepoPath (trim slashes, optional "~", reject traversal), then
// resolved to a disk path via the injected RepoLookup. A missing repository row
// surfaces as the lookup's error. Get never creates directories or stores on
// disk; nbs.NewLocalStore opens the existing bare store created by InitStore.
func (c *Cache) Get(ctx context.Context, path, nbfVerStr string) (remotesrv.RemoteSrvStore, error) {
	owner, name, err := core.ParseRepoPath(path)
	if err != nil {
		return nil, fmt.Errorf("storage: parse repo path %q: %w", path, err)
	}

	diskPath, err := c.lookup(ctx, owner, name)
	if err != nil {
		return nil, err
	}

	c.mu.Lock()
	defer c.mu.Unlock()

	if cs, ok := c.dbs[diskPath]; ok {
		return cs, nil
	}

	cs, err := nbs.NewLocalStore(ctx, nbfVerStr, diskPath, defaultMemTableSize, nbs.NewUnlimitedMemQuotaProvider(), false)
	if err != nil {
		return nil, fmt.Errorf("storage: open store %q: %w", diskPath, err)
	}
	c.dbs[diskPath] = cs
	return cs, nil
}

// Evict closes and drops the memoized store for diskPath, if any. It is called
// when a repository is deleted so a subsequent recreation at the same path does
// not reuse a stale handle. Evicting an absent path is a no-op. It returns the
// error from closing the store, if one was open.
func (c *Cache) Evict(diskPath string) error {
	c.mu.Lock()
	defer c.mu.Unlock()

	cs, ok := c.dbs[diskPath]
	if !ok {
		return nil
	}
	delete(c.dbs, diskPath)
	if err := cs.Close(); err != nil {
		return fmt.Errorf("storage: close evicted store %q: %w", diskPath, err)
	}
	return nil
}

// Close closes every memoized store and empties the cache. It is called at
// server shutdown. It closes all stores before returning and reports the first
// error encountered, if any.
func (c *Cache) Close() error {
	c.mu.Lock()
	defer c.mu.Unlock()

	var firstErr error
	for path, cs := range c.dbs {
		if err := cs.Close(); err != nil && firstErr == nil {
			firstErr = fmt.Errorf("storage: close store %q: %w", path, err)
		}
		delete(c.dbs, path)
	}
	return firstErr
}