github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/search/indexed_tlf_db.go (about) 1 // Copyright 2019 Keybase Inc. All rights reserved. 2 // Use of this source code is governed by a BSD 3 // license that can be found in the LICENSE file. 4 5 package search 6 7 import ( 8 "context" 9 "io" 10 "path/filepath" 11 "sync" 12 13 "github.com/keybase/client/go/kbfs/kbfsmd" 14 "github.com/keybase/client/go/kbfs/ldbutils" 15 "github.com/keybase/client/go/kbfs/libkbfs" 16 "github.com/keybase/client/go/kbfs/tlf" 17 "github.com/keybase/client/go/logger" 18 "github.com/pkg/errors" 19 "github.com/syndtr/goleveldb/leveldb/storage" 20 ) 21 22 const ( 23 initialIndexedTlfsDbVersion uint64 = 1 24 currentIndexedTlfsDbVersion uint64 = initialIndexedTlfsDbVersion 25 indexedTlfsFolderName string = "indexed_tlfs" 26 ) 27 28 // IndexedTlfDb is a database that holds metadata about indexed TLFs. 29 type IndexedTlfDb struct { 30 config libkbfs.Config 31 log logger.Logger 32 33 // Protect the DB from being shutdown while they're being 34 // accessed, and mutable data. 35 lock sync.RWMutex 36 tlfDb *ldbutils.LevelDb // tlfID -> TLF metadata. 37 38 shutdownCh chan struct{} 39 40 closer func() 41 } 42 43 // newIndexedTlfDbFromStorage creates a new *IndexedTlfDb with the 44 // passed-in storage.Storage interfaces as a storage layers for the db. 45 func newIndexedTlfDbFromStorage( 46 config libkbfs.Config, tlfStorage storage.Storage) ( 47 db *IndexedTlfDb, err error) { 48 log := config.MakeLogger("ITD") 49 closers := make([]io.Closer, 0, 1) 50 closer := func() { 51 for _, c := range closers { 52 closeErr := c.Close() 53 if closeErr != nil { 54 log.Warning("Error closing leveldb or storage: %+v", closeErr) 55 } 56 } 57 } 58 defer func() { 59 if err != nil { 60 err = errors.WithStack(err) 61 closer() 62 } 63 }() 64 65 tlfDbOptions := ldbutils.LeveldbOptions(config.Mode()) 66 tlfDb, err := ldbutils.OpenLevelDbWithOptions(tlfStorage, tlfDbOptions) 67 if err != nil { 68 return nil, err 69 } 70 closers = append(closers, tlfDb) 71 72 return &IndexedTlfDb{ 73 config: config, 74 tlfDb: tlfDb, 75 shutdownCh: make(chan struct{}), 76 closer: closer, 77 }, nil 78 } 79 80 // newIndexedTlfDb creates a new *IndexedTlfDb with a 81 // specified directory on the filesystem as storage. 82 func newIndexedTlfDb(config libkbfs.Config, dirPath string) ( 83 db *IndexedTlfDb, err error) { 84 log := config.MakeLogger("ITD") 85 defer func() { 86 if err != nil { 87 log.Error("Error initializing indexed TLF db: %+v", err) 88 } 89 }() 90 dbPath := filepath.Join(dirPath, indexedTlfsFolderName) 91 versionPath, err := ldbutils.GetVersionedPathForDb( 92 log, dbPath, "indexed TLFs", currentIndexedTlfsDbVersion) 93 if err != nil { 94 return nil, err 95 } 96 tlfDbPath := filepath.Join(versionPath, tlfDbFilename) 97 tlfStorage, err := storage.OpenFile(tlfDbPath, false) 98 if err != nil { 99 return nil, err 100 } 101 defer func() { 102 if err != nil { 103 tlfStorage.Close() 104 } 105 }() 106 return newIndexedTlfDbFromStorage(config, tlfStorage) 107 } 108 109 type tlfMD struct { 110 // Exported only for serialization. 111 IndexedRevision kbfsmd.Revision `codec:"i"` 112 StartedRevision kbfsmd.Revision `codec:"s"` 113 } 114 115 // getMetadataLocked retrieves the metadata for a block in the db, or 116 // returns leveldb.ErrNotFound and a zero-valued metadata otherwise. 117 func (db *IndexedTlfDb) getMetadataLocked(tlfID tlf.ID) ( 118 metadata tlfMD, err error) { 119 metadataBytes, err := db.tlfDb.Get(tlfID.Bytes(), nil) 120 if err != nil { 121 return tlfMD{}, err 122 } 123 err = db.config.Codec().Decode(metadataBytes, &metadata) 124 if err != nil { 125 return tlfMD{}, err 126 } 127 return metadata, nil 128 } 129 130 // checkAndLockDb checks whether the db is started. 131 func (db *IndexedTlfDb) checkDbLocked( 132 ctx context.Context, method string) error { 133 // First see if the context has expired since we began. 134 select { 135 case <-ctx.Done(): 136 return ctx.Err() 137 default: 138 } 139 140 // shutdownCh has to be checked under lock, otherwise we can race. 141 select { 142 case <-db.shutdownCh: 143 return errors.WithStack(DbClosedError{method}) 144 default: 145 } 146 if db.tlfDb == nil { 147 return errors.WithStack(DbClosedError{method}) 148 } 149 return nil 150 } 151 152 // Get returns the revisions for the given TLF. 153 func (db *IndexedTlfDb) Get( 154 ctx context.Context, tlfID tlf.ID) ( 155 indexedRev, startedRev kbfsmd.Revision, err error) { 156 db.lock.RLock() 157 defer db.lock.RUnlock() 158 err = db.checkDbLocked(ctx, "ITD(Get)") 159 if err != nil { 160 return kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized, err 161 } 162 163 md, err := db.getMetadataLocked(tlfID) 164 if err != nil { 165 return kbfsmd.RevisionUninitialized, kbfsmd.RevisionUninitialized, err 166 } 167 return md.IndexedRevision, md.StartedRevision, nil 168 } 169 170 // Put saves the revisions for the given TLF. 171 func (db *IndexedTlfDb) Put( 172 ctx context.Context, tlfID tlf.ID, 173 indexedRev, startedRev kbfsmd.Revision) error { 174 db.lock.Lock() 175 defer db.lock.Unlock() 176 err := db.checkDbLocked(ctx, "ITD(Put)") 177 if err != nil { 178 return err 179 } 180 181 md := tlfMD{ 182 IndexedRevision: indexedRev, 183 StartedRevision: startedRev, 184 } 185 encodedMetadata, err := db.config.Codec().Encode(&md) 186 if err != nil { 187 return err 188 } 189 190 return db.tlfDb.Put(tlfID.Bytes(), encodedMetadata, nil) 191 } 192 193 // Delete removes the metadata for the TLF from the DB. 194 func (db *IndexedTlfDb) Delete( 195 ctx context.Context, tlfID tlf.ID) error { 196 db.lock.Lock() 197 defer db.lock.Unlock() 198 err := db.checkDbLocked(ctx, "ITD(Delete)") 199 if err != nil { 200 return err 201 } 202 203 return db.tlfDb.Delete(tlfID.Bytes(), nil) 204 } 205 206 // Shutdown closes this db. 207 func (db *IndexedTlfDb) Shutdown(ctx context.Context) { 208 db.lock.Lock() 209 defer db.lock.Unlock() 210 // shutdownCh has to be checked under lock, otherwise we can race. 211 select { 212 case <-db.shutdownCh: 213 db.log.CWarningf(ctx, "Shutdown called more than once") 214 return 215 default: 216 } 217 close(db.shutdownCh) 218 if db.tlfDb == nil { 219 return 220 } 221 db.closer() 222 db.tlfDb = nil 223 }