aboutsummaryrefslogtreecommitdiff
path: root/objectstore/packed/idx_lookup_candidates.go
blob: a2de262aebb21cda1919a3ddd2066f6e1ae52f24 (about) (plain) (blame)
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
119
120
121
122
123
124
125
126
package packed

import (
	"fmt"
	"os"
	"slices"
	"strings"
)

// packCandidate describes one discovered pack/index pair.
type packCandidate struct {
	// packName is the .pack basename.
	packName string
	// idxName is the .idx basename.
	idxName string
	// mtime is the pack file modification time for initial ordering.
	mtime int64
}

type candidateSnapshot struct {
	candidates      []packCandidate
	candidateByPack map[string]packCandidate
}

// Refresh rescans objects/pack and atomically installs a fresh candidate list
// for future lookups.
//
// Refresh does not invalidate existing readers. Cached pack/index mappings,
// including ones for previously visible candidates, may be retained until
// Close.
func (store *Store) Refresh() error {
	store.refreshMu.Lock()
	defer store.refreshMu.Unlock()

	candidates, err := store.discoverCandidates()
	if err != nil {
		return err
	}

	candidateByPack := make(map[string]packCandidate, len(candidates))
	for _, candidate := range candidates {
		candidateByPack[candidate.packName] = candidate
	}

	store.reconcileMRU(candidates)

	store.candidates.Store(&candidateSnapshot{
		candidates:      candidates,
		candidateByPack: candidateByPack,
	})

	return nil
}

func (store *Store) ensureCandidates() (*candidateSnapshot, error) {
	snapshot := store.candidates.Load()
	if snapshot != nil {
		return snapshot, nil
	}

	err := store.Refresh()
	if err != nil {
		return nil, err
	}

	return store.candidates.Load(), nil
}

// discoverCandidates scans the objects/pack root and returns sorted pack/index
// pairs.
func (store *Store) discoverCandidates() ([]packCandidate, error) {
	dir, err := store.root.Open(".")
	if err != nil {
		if os.IsNotExist(err) {
			return nil, nil
		}

		return nil, err
	}

	defer func() { _ = dir.Close() }()

	entries, err := dir.ReadDir(-1)
	if err != nil {
		return nil, err
	}

	candidates := make([]packCandidate, 0, len(entries))
	for _, entry := range entries {
		if entry.IsDir() || !strings.HasSuffix(entry.Name(), ".idx") {
			continue
		}

		idxName := entry.Name()
		packName := strings.TrimSuffix(idxName, ".idx") + ".pack"

		packInfo, err := store.root.Stat(packName)
		if err != nil {
			if os.IsNotExist(err) {
				return nil, fmt.Errorf("objectstore/packed: missing pack file for index %q", idxName)
			}

			return nil, err
		}

		candidates = append(candidates, packCandidate{
			packName: packName,
			idxName:  idxName,
			mtime:    packInfo.ModTime().UnixNano(),
		})
	}

	slices.SortFunc(candidates, func(a, b packCandidate) int {
		if a.mtime != b.mtime {
			if a.mtime > b.mtime {
				return -1
			}

			return 1
		}

		return strings.Compare(a.packName, b.packName)
	})

	return candidates, nil
}