github.com/outbrain/consul@v1.4.5/agent/cache/entry.go (about)

     1  package cache
     2  
     3  import (
     4  	"container/heap"
     5  	"time"
     6  )
     7  
     8  // cacheEntry stores a single cache entry.
     9  //
    10  // Note that this isn't a very optimized structure currently. There are
    11  // a lot of improvements that can be made here in the long term.
    12  type cacheEntry struct {
    13  	// Fields pertaining to the actual value
    14  	Value interface{}
    15  	// State can be used to store info needed by the cache type but that should
    16  	// not be part of the result the client gets. For example the Connect Leaf
    17  	// type needs to store additional data about when it last attempted a renewal
    18  	// that is not part of the actual IssuedCert struct it returns. It's opaque to
    19  	// the Cache but allows types to store additional data that is coupled to the
    20  	// cache entry's lifetime and will be aged out by TTL etc.
    21  	State interface{}
    22  	Error error
    23  	Index uint64
    24  
    25  	// Metadata that is used for internal accounting
    26  	Valid    bool          // True if the Value is set
    27  	Fetching bool          // True if a fetch is already active
    28  	Waiter   chan struct{} // Closed when this entry is invalidated
    29  
    30  	// Expiry contains information about the expiration of this
    31  	// entry. This is a pointer as its shared as a value in the
    32  	// expiryHeap as well.
    33  	Expiry *cacheEntryExpiry
    34  
    35  	// FetchedAt stores the time the cache entry was retrieved for determining
    36  	// it's age later.
    37  	FetchedAt time.Time
    38  
    39  	// RefreshLostContact stores the time background refresh failed. It gets reset
    40  	// to zero after a background fetch has returned successfully, or after a
    41  	// background request has be blocking for at least 5 seconds, which ever
    42  	// happens first.
    43  	RefreshLostContact time.Time
    44  }
    45  
    46  // cacheEntryExpiry contains the expiration information for a cache
    47  // entry. Any modifications to this struct should be done only while
    48  // the Cache entriesLock is held.
    49  type cacheEntryExpiry struct {
    50  	Key       string        // Key in the cache map
    51  	Expires   time.Time     // Time when entry expires (monotonic clock)
    52  	TTL       time.Duration // TTL for this entry to extend when resetting
    53  	HeapIndex int           // Index in the heap
    54  }
    55  
    56  // Reset resets the expiration to be the ttl duration from now.
    57  func (e *cacheEntryExpiry) Reset() {
    58  	e.Expires = time.Now().Add(e.TTL)
    59  }
    60  
    61  // expiryHeap is a heap implementation that stores information about
    62  // when entires expire. Implements container/heap.Interface.
    63  //
    64  // All operations on the heap and read/write of the heap contents require
    65  // the proper entriesLock to be held on Cache.
    66  type expiryHeap struct {
    67  	Entries []*cacheEntryExpiry
    68  
    69  	// NotifyCh is sent a value whenever the 0 index value of the heap
    70  	// changes. This can be used to detect when the earliest value
    71  	// changes.
    72  	//
    73  	// There is a single edge case where the heap will not automatically
    74  	// send a notification: if heap.Fix is called manually and the index
    75  	// changed is 0 and the change doesn't result in any moves (stays at index
    76  	// 0), then we won't detect the change. To work around this, please
    77  	// always call the expiryHeap.Fix method instead.
    78  	NotifyCh chan struct{}
    79  }
    80  
    81  // Identical to heap.Fix for this heap instance but will properly handle
    82  // the edge case where idx == 0 and no heap modification is necessary,
    83  // and still notify the NotifyCh.
    84  //
    85  // This is important for cache expiry since the expiry time may have been
    86  // extended and if we don't send a message to the NotifyCh then we'll never
    87  // reset the timer and the entry will be evicted early.
    88  func (h *expiryHeap) Fix(entry *cacheEntryExpiry) {
    89  	idx := entry.HeapIndex
    90  	heap.Fix(h, idx)
    91  
    92  	// This is the edge case we handle: if the prev (idx) and current (HeapIndex)
    93  	// is zero, it means the head-of-line didn't change while the value
    94  	// changed. Notify to reset our expiry worker.
    95  	if idx == 0 && entry.HeapIndex == 0 {
    96  		h.notify()
    97  	}
    98  }
    99  
   100  func (h *expiryHeap) Len() int { return len(h.Entries) }
   101  
   102  func (h *expiryHeap) Swap(i, j int) {
   103  	h.Entries[i], h.Entries[j] = h.Entries[j], h.Entries[i]
   104  	h.Entries[i].HeapIndex = i
   105  	h.Entries[j].HeapIndex = j
   106  
   107  	// If we're moving the 0 index, update the channel since we need
   108  	// to re-update the timer we're waiting on for the soonest expiring
   109  	// value.
   110  	if i == 0 || j == 0 {
   111  		h.notify()
   112  	}
   113  }
   114  
   115  func (h *expiryHeap) Less(i, j int) bool {
   116  	// The usage of Before here is important (despite being obvious):
   117  	// this function uses the monotonic time that should be available
   118  	// on the time.Time value so the heap is immune to wall clock changes.
   119  	return h.Entries[i].Expires.Before(h.Entries[j].Expires)
   120  }
   121  
   122  // heap.Interface, this isn't expected to be called directly.
   123  func (h *expiryHeap) Push(x interface{}) {
   124  	entry := x.(*cacheEntryExpiry)
   125  
   126  	// Set initial heap index, if we're going to the end then Swap
   127  	// won't be called so we need to initialize
   128  	entry.HeapIndex = len(h.Entries)
   129  
   130  	// For the first entry, we need to trigger a channel send because
   131  	// Swap won't be called; nothing to swap! We can call it right away
   132  	// because all heap operations are within a lock.
   133  	if len(h.Entries) == 0 {
   134  		h.notify()
   135  	}
   136  
   137  	h.Entries = append(h.Entries, entry)
   138  }
   139  
   140  // heap.Interface, this isn't expected to be called directly.
   141  func (h *expiryHeap) Pop() interface{} {
   142  	old := h.Entries
   143  	n := len(old)
   144  	x := old[n-1]
   145  	h.Entries = old[0 : n-1]
   146  	return x
   147  }
   148  
   149  func (h *expiryHeap) notify() {
   150  	select {
   151  	case h.NotifyCh <- struct{}{}:
   152  		// Good
   153  
   154  	default:
   155  		// If the send would've blocked, we just ignore it. The reason this
   156  		// is safe is because NotifyCh should always be a buffered channel.
   157  		// If this blocks, it means that there is a pending message anyways
   158  		// so the receiver will restart regardless.
   159  	}
   160  }