github.com/keybase/client/go@v0.0.0-20241007131713-f10651d043c8/kbfs/libkbfs/merkle_root.go (about)

     1  // Copyright 2017 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 libkbfs
     6  
     7  import (
     8  	"sync"
     9  	"time"
    10  
    11  	"github.com/keybase/client/go/kbfs/idutil"
    12  	"github.com/keybase/client/go/logger"
    13  	"github.com/keybase/client/go/protocol/keybase1"
    14  	"golang.org/x/net/context"
    15  )
    16  
    17  // ECMRCtxTagKey is the type for unique ECMR background operation IDs.
    18  type ECMRCtxTagKey struct{}
    19  
    20  // ECMRID is used in EventuallyConsistentMerkleRoot for only
    21  // background RPCs.  More specifically, when we need to spawn a
    22  // background goroutine for GetCurrentMerkleRoot, a new context with
    23  // this tag is created and used. This is also used as a prefix for the
    24  // logger module name in EventuallyConsistentMerkleRoot.
    25  const ECMRID = "ECMR"
    26  
    27  type cachedMerkleRoot struct {
    28  	timestamp time.Time
    29  	root      keybase1.MerkleRootV2
    30  	rootTime  time.Time
    31  }
    32  
    33  // EventuallyConsistentMerkleRoot keeps tracks of the current global
    34  // Merkle root, in a way user of which can choose to accept stale data
    35  // to reduce calls to the API server.
    36  type EventuallyConsistentMerkleRoot struct {
    37  	config  Config
    38  	log     logger.Logger
    39  	getter  idutil.MerkleRootGetter
    40  	fetcher *fetchDecider
    41  
    42  	mu     sync.RWMutex
    43  	cached cachedMerkleRoot
    44  }
    45  
    46  // NewEventuallyConsistentMerkleRoot creates a new
    47  // EventuallyConsistentMerkleRoot object.
    48  func NewEventuallyConsistentMerkleRoot(
    49  	config Config, getter idutil.MerkleRootGetter) *EventuallyConsistentMerkleRoot {
    50  	ecmr := &EventuallyConsistentMerkleRoot{
    51  		config: config,
    52  		log:    config.MakeLogger(ECMRID),
    53  		getter: getter,
    54  	}
    55  	ecmr.fetcher = newFetchDecider(
    56  		ecmr.log, config.MakeVLogger(ecmr.log), ecmr.getAndCache,
    57  		ECMRCtxTagKey{}, ECMRID, ecmr.config)
    58  	return ecmr
    59  }
    60  
    61  func (ecmr *EventuallyConsistentMerkleRoot) getAndCache(
    62  	ctx context.Context) (err error) {
    63  	defer func() {
    64  		ecmr.log.CDebugf(ctx, "getAndCache: error=%v", err)
    65  	}()
    66  	bareRoot, rootTime, err := ecmr.getter.GetCurrentMerkleRoot(ctx)
    67  	if err != nil {
    68  		return err
    69  	}
    70  
    71  	ecmr.mu.Lock()
    72  	defer ecmr.mu.Unlock()
    73  	ecmr.cached.root = bareRoot
    74  	ecmr.cached.rootTime = rootTime
    75  	ecmr.cached.timestamp = ecmr.config.Clock().Now()
    76  
    77  	return nil
    78  }
    79  
    80  func (ecmr *EventuallyConsistentMerkleRoot) getCached() cachedMerkleRoot {
    81  	ecmr.mu.RLock()
    82  	defer ecmr.mu.RUnlock()
    83  	return ecmr.cached
    84  }
    85  
    86  // Get returns the current merkle root, and the server timestamp of
    87  // that root. To help avoid having too frequent calls into the API
    88  // server, caller can provide a positive tolerance, to accept stale
    89  // LimitBytes and UsageBytes data. If tolerance is 0 or negative, this
    90  // always makes a blocking RPC to bserver and return latest quota
    91  // usage.
    92  //
    93  // 1) If the age of cached data is more than blockTolerance, a blocking RPC is
    94  // issued and the function only returns after RPC finishes, with the newest
    95  // data from RPC. The RPC causes cached data to be refreshed as well.
    96  // 2) Otherwise, if the age of cached data is more than bgTolerance,
    97  // a background RPC is spawned to refresh cached data, and the stale
    98  // data is returned immediately.
    99  // 3) Otherwise, the cached stale data is returned immediately.
   100  func (ecmr *EventuallyConsistentMerkleRoot) Get(
   101  	ctx context.Context, bgTolerance, blockTolerance time.Duration) (
   102  	timestamp time.Time, root keybase1.MerkleRootV2,
   103  	rootTime time.Time, err error) {
   104  	c := ecmr.getCached()
   105  	err = ecmr.fetcher.Do(ctx, bgTolerance, blockTolerance, c.timestamp)
   106  	if err != nil {
   107  		return time.Time{}, keybase1.MerkleRootV2{}, time.Time{}, err
   108  	}
   109  	c = ecmr.getCached()
   110  	return c.timestamp, c.root, c.rootTime, nil
   111  }