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  }