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  }