forked from keybase/client
-
Notifications
You must be signed in to change notification settings - Fork 0
/
disk_block_metadata_store.go
259 lines (221 loc) · 6.73 KB
/
disk_block_metadata_store.go
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
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
// Copyright 2018 Keybase Inc. All rights reserved.
// Use of this source code is governed by a BSD
// license that can be found in the LICENSE file.
package libkbfs
import (
"context"
"sync"
"github.com/keybase/client/go/kbfs/kbfsblock"
"github.com/keybase/client/go/kbfs/kbfscodec"
"github.com/keybase/client/go/logger"
"github.com/pkg/errors"
ldberrors "github.com/syndtr/goleveldb/leveldb/errors"
)
// XattrType represents the xattr type.
type XattrType int
// New types can only be added at end.
const (
_ XattrType = iota
XattrAppleQuarantine
)
const (
initialBlockMetadataStoreVersion uint64 = 1
currentBlockMetadataStoreVersion uint64 = initialBlockMetadataStoreVersion
blockMetadataFolderName string = "kbfs_block_metadata"
blockMetadataDbFilename string = "diskBlockMetadata.leveldb"
)
type diskBlockMetadataStoreConfig interface {
Codec() kbfscodec.Codec
MakeLogger(module string) logger.Logger
StorageRoot() string
Mode() InitMode
}
// diskBlockMetadataStore interacts with BlockMetadata data storage on disk.
type diskBlockMetadataStore struct {
log logger.Logger
config diskBlockMetadataStoreConfig
// Track the hit rate and eviction rate. These are goroutine safe.
hitMeter *CountMeter
missMeter *CountMeter
putMeter *CountMeter
lock sync.RWMutex
db *LevelDb
shutdownCh chan struct{}
}
// newDiskBlockMetadataStore creates a new disk BlockMetadata storage.
func newDiskBlockMetadataStore(
config diskBlockMetadataStoreConfig, mode InitMode) (
BlockMetadataStore, error) {
log := config.MakeLogger("BMS")
db, err := openVersionedLevelDB(
log, config.StorageRoot(), blockMetadataFolderName,
currentBlockMetadataStoreVersion, blockMetadataDbFilename, mode)
if err != nil {
return nil, err
}
return &diskBlockMetadataStore{
log: log,
config: config,
hitMeter: NewCountMeter(),
missMeter: NewCountMeter(),
putMeter: NewCountMeter(),
db: db,
shutdownCh: make(chan struct{}),
}, err
}
// Shutdown shuts done this storae.
func (s *diskBlockMetadataStore) Shutdown() {
s.log.Debug("Shutting down diskBlockMetadataStore")
s.lock.Lock()
defer s.lock.Unlock()
// shutdownCh has to be checked under lock, otherwise we can race.
select {
case <-s.shutdownCh:
s.log.Warning("Shutdown called more than once")
default:
}
close(s.shutdownCh)
if s.db == nil {
return
}
s.db.Close()
s.db = nil
s.hitMeter.Shutdown()
s.missMeter.Shutdown()
s.putMeter.Shutdown()
}
var _ BlockMetadataStore = (*diskBlockMetadataStore)(nil)
// ErrBlockMetadataStoreShutdown is returned when methods are called on
// diskBlockMetadataStore when it's already shutdown.
type ErrBlockMetadataStoreShutdown struct{}
// Error implements the error interface.
func (ErrBlockMetadataStoreShutdown) Error() string {
return "disk block metadata store has shutdown"
}
// GetMetadata implements the BlockMetadataStore interface.
func (s *diskBlockMetadataStore) GetMetadata(ctx context.Context,
blockID kbfsblock.ID) (value BlockMetadataValue, err error) {
s.lock.RLock()
defer s.lock.RUnlock()
select {
case <-s.shutdownCh:
return BlockMetadataValue{}, ErrBlockMetadataStoreShutdown{}
default:
}
encoded, err := s.db.GetWithMeter(blockID.Bytes(), s.hitMeter, s.missMeter)
switch errors.Cause(err) {
case ldberrors.ErrNotFound:
return BlockMetadataValue{}, err
case nil:
if err = s.config.Codec().Decode(encoded, &value); err != nil {
s.log.CWarningf(ctx, "decoding block metadata error: %v", err)
return BlockMetadataValue{}, ldberrors.ErrNotFound
}
return value, nil
default:
s.log.CWarningf(ctx, "GetMetadata error: %v", err)
return BlockMetadataValue{}, ldberrors.ErrNotFound
}
}
// UpdateMetadata implements the BlockMetadataStore interface.
func (s *diskBlockMetadataStore) UpdateMetadata(ctx context.Context,
blockID kbfsblock.ID, updater BlockMetadataUpdater) error {
bid := blockID.Bytes()
s.lock.Lock()
defer s.lock.Unlock()
select {
case <-s.shutdownCh:
return ErrBlockMetadataStoreShutdown{}
default:
}
var value BlockMetadataValue
encoded, err := s.db.Get(bid, nil)
switch errors.Cause(err) {
case ldberrors.ErrNotFound:
case nil:
if err = s.config.Codec().Decode(encoded, &value); err != nil {
s.log.CWarningf(ctx, "decoding block metadata error: %v", err)
}
default:
s.log.CWarningf(ctx, "GetMetadata error: %v", err)
}
if err = updater(&value); err != nil {
return err
}
if encoded, err = s.config.Codec().Encode(value); err != nil {
return err
}
return s.db.PutWithMeter(bid, encoded, s.putMeter)
}
// xattrStore is a wrapper around BlockMetadataStore that handles xattr
// values.
type xattrStore struct {
store BlockMetadataStore
// Track the hit rate and eviction rate. These are goroutine safe.
hitMeter *CountMeter
missMeter *CountMeter
putMeter *CountMeter
}
// NewXattrStoreFromBlockMetadataStore returns a XattrStore which is a wrapper
// around the passed in store.
func NewXattrStoreFromBlockMetadataStore(store BlockMetadataStore) XattrStore {
return xattrStore{
store: store,
hitMeter: NewCountMeter(),
missMeter: NewCountMeter(),
putMeter: NewCountMeter(),
}
}
var _ XattrStore = (*xattrStore)(nil)
// GetXattr implements the XattrStore interface.
func (s xattrStore) GetXattr(ctx context.Context,
blockID kbfsblock.ID, xattrType XattrType) ([]byte, error) {
blockMetadata, err := s.store.GetMetadata(ctx, blockID)
switch errors.Cause(err) {
case ldberrors.ErrNotFound:
s.missMeter.Mark(1)
return nil, err
case nil:
default:
return nil, err
}
v, ok := blockMetadata.Xattr[xattrType]
if !ok {
s.missMeter.Mark(1)
return nil, ldberrors.ErrNotFound
}
s.hitMeter.Mark(1)
return v, nil
}
// SetXattr implements the XattrStore interface.
func (s xattrStore) SetXattr(ctx context.Context,
blockID kbfsblock.ID, xattrType XattrType, xattrValue []byte) (err error) {
if err = s.store.UpdateMetadata(ctx, blockID,
func(v *BlockMetadataValue) error {
if v.Xattr == nil {
v.Xattr = make(map[XattrType][]byte)
}
v.Xattr[xattrType] = xattrValue
return nil
}); err != nil {
return err
}
s.putMeter.Mark(1)
return nil
}
// NoopBlockMetadataStore satisfies the BlockMetadataStore interface but
// does nothing.
type NoopBlockMetadataStore struct{}
var _ BlockMetadataStore = NoopBlockMetadataStore{}
// GetMetadata always returns ldberrors.ErrNotFound.
func (NoopBlockMetadataStore) GetMetadata(ctx context.Context,
blockID kbfsblock.ID) (value BlockMetadataValue, err error) {
return BlockMetadataValue{}, ldberrors.ErrNotFound
}
// UpdateMetadata returns nil error but does nothing.
func (NoopBlockMetadataStore) UpdateMetadata(ctx context.Context,
blockID kbfsblock.ID, updater BlockMetadataUpdater) error {
return nil
}
// Shutdown does nothing.
func (NoopBlockMetadataStore) Shutdown() {}