github.com/prysmaticlabs/prysm@v1.4.4/beacon-chain/cache/skip_slot_cache.go (about)

     1  package cache
     2  
     3  import (
     4  	"context"
     5  	"math"
     6  	"sync"
     7  	"time"
     8  
     9  	lru "github.com/hashicorp/golang-lru"
    10  	"github.com/prometheus/client_golang/prometheus"
    11  	"github.com/prometheus/client_golang/prometheus/promauto"
    12  	iface "github.com/prysmaticlabs/prysm/beacon-chain/state/interface"
    13  	"go.opencensus.io/trace"
    14  )
    15  
    16  var (
    17  	// Metrics
    18  	skipSlotCacheHit = promauto.NewCounter(prometheus.CounterOpts{
    19  		Name: "skip_slot_cache_hit",
    20  		Help: "The total number of cache hits on the skip slot cache.",
    21  	})
    22  	skipSlotCacheMiss = promauto.NewCounter(prometheus.CounterOpts{
    23  		Name: "skip_slot_cache_miss",
    24  		Help: "The total number of cache misses on the skip slot cache.",
    25  	})
    26  )
    27  
    28  // SkipSlotCache is used to store the cached results of processing skip slots in state.ProcessSlots.
    29  type SkipSlotCache struct {
    30  	cache      *lru.Cache
    31  	lock       sync.RWMutex
    32  	disabled   bool // Allow for programmatic toggling of the cache, useful during initial sync.
    33  	inProgress map[[32]byte]bool
    34  }
    35  
    36  // NewSkipSlotCache initializes the map and underlying cache.
    37  func NewSkipSlotCache() *SkipSlotCache {
    38  	cache, err := lru.New(8)
    39  	if err != nil {
    40  		panic(err)
    41  	}
    42  	return &SkipSlotCache{
    43  		cache:      cache,
    44  		inProgress: make(map[[32]byte]bool),
    45  	}
    46  }
    47  
    48  // Enable the skip slot cache.
    49  func (c *SkipSlotCache) Enable() {
    50  	c.disabled = false
    51  }
    52  
    53  // Disable the skip slot cache.
    54  func (c *SkipSlotCache) Disable() {
    55  	c.disabled = true
    56  }
    57  
    58  // Get waits for any in progress calculation to complete before returning a
    59  // cached response, if any.
    60  func (c *SkipSlotCache) Get(ctx context.Context, r [32]byte) (iface.BeaconState, error) {
    61  	ctx, span := trace.StartSpan(ctx, "skipSlotCache.Get")
    62  	defer span.End()
    63  	if c.disabled {
    64  		// Return a miss result if cache is not enabled.
    65  		skipSlotCacheMiss.Inc()
    66  		return nil, nil
    67  	}
    68  
    69  	delay := minDelay
    70  
    71  	// Another identical request may be in progress already. Let's wait until
    72  	// any in progress request resolves or our timeout is exceeded.
    73  	inProgress := false
    74  	for {
    75  		if ctx.Err() != nil {
    76  			return nil, ctx.Err()
    77  		}
    78  
    79  		c.lock.RLock()
    80  		if !c.inProgress[r] {
    81  			c.lock.RUnlock()
    82  			break
    83  		}
    84  		inProgress = true
    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  	span.AddAttributes(trace.BoolAttribute("inProgress", inProgress))
    94  
    95  	item, exists := c.cache.Get(r)
    96  
    97  	if exists && item != nil {
    98  		skipSlotCacheHit.Inc()
    99  		span.AddAttributes(trace.BoolAttribute("hit", true))
   100  		return item.(iface.BeaconState).Copy(), nil
   101  	}
   102  	skipSlotCacheMiss.Inc()
   103  	span.AddAttributes(trace.BoolAttribute("hit", false))
   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 *SkipSlotCache) MarkInProgress(r [32]byte) error {
   110  	if c.disabled {
   111  		return nil
   112  	}
   113  
   114  	c.lock.Lock()
   115  	defer c.lock.Unlock()
   116  
   117  	if c.inProgress[r] {
   118  		return ErrAlreadyInProgress
   119  	}
   120  	c.inProgress[r] = true
   121  	return nil
   122  }
   123  
   124  // MarkNotInProgress will release the lock on a given request. This should be
   125  // called after put.
   126  func (c *SkipSlotCache) MarkNotInProgress(r [32]byte) error {
   127  	if c.disabled {
   128  		return nil
   129  	}
   130  
   131  	c.lock.Lock()
   132  	defer c.lock.Unlock()
   133  
   134  	delete(c.inProgress, r)
   135  	return nil
   136  }
   137  
   138  // Put the response in the cache.
   139  func (c *SkipSlotCache) Put(_ context.Context, r [32]byte, state iface.BeaconState) error {
   140  	if c.disabled {
   141  		return nil
   142  	}
   143  
   144  	// Copy state so cached value is not mutated.
   145  	c.cache.Add(r, state.Copy())
   146  
   147  	return nil
   148  }