github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/cache/attestation_data.go (about) 1 package cache 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math" 8 "sync" 9 "time" 10 11 "github.com/prometheus/client_golang/prometheus" 12 "github.com/prometheus/client_golang/prometheus/promauto" 13 ethpb "github.com/prysmaticlabs/prysm/proto/eth/v1alpha1" 14 "github.com/prysmaticlabs/prysm/shared/copyutil" 15 "k8s.io/client-go/tools/cache" 16 ) 17 18 var ( 19 // Delay parameters 20 minDelay = float64(10) // 10 nanoseconds 21 maxDelay = float64(100000000) // 0.1 second 22 delayFactor = 1.1 23 24 // Metrics 25 attestationCacheMiss = promauto.NewCounter(prometheus.CounterOpts{ 26 Name: "attestation_cache_miss", 27 Help: "The number of attestation data requests that aren't present in the cache.", 28 }) 29 attestationCacheHit = promauto.NewCounter(prometheus.CounterOpts{ 30 Name: "attestation_cache_hit", 31 Help: "The number of attestation data requests that are present in the cache.", 32 }) 33 attestationCacheSize = promauto.NewGauge(prometheus.GaugeOpts{ 34 Name: "attestation_cache_size", 35 Help: "The number of attestation data in the attestations cache", 36 }) 37 ) 38 39 // ErrAlreadyInProgress appears when attempting to mark a cache as in progress while it is 40 // already in progress. The client should handle this error and wait for the in progress 41 // data to resolve via Get. 42 var ErrAlreadyInProgress = errors.New("already in progress") 43 44 // AttestationCache is used to store the cached results of an AttestationData request. 45 type AttestationCache struct { 46 cache *cache.FIFO 47 lock sync.RWMutex 48 inProgress map[string]bool 49 } 50 51 // NewAttestationCache initializes the map and underlying cache. 52 func NewAttestationCache() *AttestationCache { 53 return &AttestationCache{ 54 cache: cache.NewFIFO(wrapperToKey), 55 inProgress: make(map[string]bool), 56 } 57 } 58 59 // Get waits for any in progress calculation to complete before returning a 60 // cached response, if any. 61 func (c *AttestationCache) Get(ctx context.Context, req *ethpb.AttestationDataRequest) (*ethpb.AttestationData, error) { 62 if req == nil { 63 return nil, errors.New("nil attestation data request") 64 } 65 66 s, e := reqToKey(req) 67 if e != nil { 68 return nil, e 69 } 70 71 delay := minDelay 72 73 // Another identical request may be in progress already. Let's wait until 74 // any in progress request resolves or our timeout is exceeded. 75 for { 76 if ctx.Err() != nil { 77 return nil, ctx.Err() 78 } 79 80 c.lock.RLock() 81 if !c.inProgress[s] { 82 c.lock.RUnlock() 83 break 84 } 85 c.lock.RUnlock() 86 87 // This increasing backoff is to decrease the CPU cycles while waiting 88 // for the in progress boolean to flip to false. 89 time.Sleep(time.Duration(delay) * time.Nanosecond) 90 delay *= delayFactor 91 delay = math.Min(delay, maxDelay) 92 } 93 94 item, exists, err := c.cache.GetByKey(s) 95 if err != nil { 96 return nil, err 97 } 98 99 if exists && item != nil && item.(*attestationReqResWrapper).res != nil { 100 attestationCacheHit.Inc() 101 return copyutil.CopyAttestationData(item.(*attestationReqResWrapper).res), nil 102 } 103 attestationCacheMiss.Inc() 104 return nil, nil 105 } 106 107 // MarkInProgress a request so that any other similar requests will block on 108 // Get until MarkNotInProgress is called. 109 func (c *AttestationCache) MarkInProgress(req *ethpb.AttestationDataRequest) error { 110 c.lock.Lock() 111 defer c.lock.Unlock() 112 s, e := reqToKey(req) 113 if e != nil { 114 return e 115 } 116 if c.inProgress[s] { 117 return ErrAlreadyInProgress 118 } 119 c.inProgress[s] = true 120 return nil 121 } 122 123 // MarkNotInProgress will release the lock on a given request. This should be 124 // called after put. 125 func (c *AttestationCache) MarkNotInProgress(req *ethpb.AttestationDataRequest) error { 126 c.lock.Lock() 127 defer c.lock.Unlock() 128 s, e := reqToKey(req) 129 if e != nil { 130 return e 131 } 132 delete(c.inProgress, s) 133 return nil 134 } 135 136 // Put the response in the cache. 137 func (c *AttestationCache) Put(_ context.Context, req *ethpb.AttestationDataRequest, res *ethpb.AttestationData) error { 138 data := &attestationReqResWrapper{ 139 req, 140 res, 141 } 142 if err := c.cache.AddIfNotPresent(data); err != nil { 143 return err 144 } 145 trim(c.cache, maxCacheSize) 146 147 attestationCacheSize.Set(float64(len(c.cache.List()))) 148 return nil 149 } 150 151 func wrapperToKey(i interface{}) (string, error) { 152 w, ok := i.(*attestationReqResWrapper) 153 if !ok { 154 return "", errors.New("key is not of type *attestationReqResWrapper") 155 } 156 if w == nil { 157 return "", errors.New("nil wrapper") 158 } 159 if w.req == nil { 160 return "", errors.New("nil wrapper.request") 161 } 162 return reqToKey(w.req) 163 } 164 165 func reqToKey(req *ethpb.AttestationDataRequest) (string, error) { 166 return fmt.Sprintf("%d", req.Slot), nil 167 } 168 169 type attestationReqResWrapper struct { 170 req *ethpb.AttestationDataRequest 171 res *ethpb.AttestationData 172 }