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 }