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 }