2020-10-20 17:19:21 +00:00
|
|
|
package cache
|
|
|
|
|
|
|
|
import (
|
|
|
|
"container/heap"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
|
|
|
// cacheEntryExpiry contains the expiration time for a cache entry.
|
|
|
|
type cacheEntryExpiry struct {
|
|
|
|
Key string // Key in the cache map
|
|
|
|
Expires time.Time // Time when entry expires (monotonic clock)
|
|
|
|
HeapIndex int // Index in the heap
|
|
|
|
}
|
|
|
|
|
|
|
|
// expiryHeap is a container/heap.Interface implementation that expires entries
|
|
|
|
// in the cache when their expiration time is reached.
|
|
|
|
//
|
|
|
|
// All operations on the heap and read/write of the heap contents require
|
|
|
|
// the proper entriesLock to be held on Cache.
|
|
|
|
type expiryHeap struct {
|
|
|
|
Entries []*cacheEntryExpiry
|
|
|
|
|
|
|
|
// NotifyCh is sent a value whenever the 0 index value of the heap
|
|
|
|
// changes. This can be used to detect when the earliest value
|
|
|
|
// changes.
|
|
|
|
NotifyCh chan struct{}
|
|
|
|
}
|
|
|
|
|
2020-10-20 17:59:53 +00:00
|
|
|
// Initialize the heap. The buffer of 1 is really important because
|
|
|
|
// its possible for the expiry loop to trigger the heap to update
|
|
|
|
// itself and it'd block forever otherwise.
|
|
|
|
func newExpiryHeap() *expiryHeap {
|
|
|
|
h := &expiryHeap{NotifyCh: make(chan struct{}, 1)}
|
|
|
|
heap.Init(h)
|
|
|
|
return h
|
|
|
|
}
|
|
|
|
|
2020-10-20 18:57:22 +00:00
|
|
|
// Add an entry to the heap.
|
|
|
|
//
|
2020-10-20 17:59:53 +00:00
|
|
|
// Must be synchronized by the caller.
|
2020-10-20 17:19:21 +00:00
|
|
|
func (h *expiryHeap) Add(key string, expiry time.Duration) *cacheEntryExpiry {
|
2020-10-20 18:57:22 +00:00
|
|
|
entry := &cacheEntryExpiry{
|
|
|
|
Key: key,
|
|
|
|
Expires: time.Now().Add(expiry),
|
|
|
|
// Set the initial heap index to the last index. If the entry is swapped it
|
|
|
|
// will have the correct index set, and if it remains at the end the last
|
|
|
|
// index will be correct.
|
|
|
|
HeapIndex: len(h.Entries),
|
|
|
|
}
|
2020-10-20 17:19:21 +00:00
|
|
|
heap.Push(h, entry)
|
2020-10-20 18:57:22 +00:00
|
|
|
if entry.HeapIndex == 0 {
|
|
|
|
h.notify()
|
|
|
|
}
|
2020-10-20 17:19:21 +00:00
|
|
|
return entry
|
|
|
|
}
|
|
|
|
|
|
|
|
// Update the entry that is currently at idx with the new expiry time. The heap
|
|
|
|
// will be rebalanced after the entry is updated.
|
|
|
|
//
|
|
|
|
// Must be synchronized by the caller.
|
|
|
|
func (h *expiryHeap) Update(idx int, expiry time.Duration) {
|
|
|
|
entry := h.Entries[idx]
|
|
|
|
entry.Expires = time.Now().Add(expiry)
|
|
|
|
heap.Fix(h, idx)
|
|
|
|
|
|
|
|
// If the previous index and current index are both zero then Fix did not
|
|
|
|
// swap the entry, and notify must be called here.
|
2020-10-20 18:57:22 +00:00
|
|
|
if idx == 0 || entry.HeapIndex == 0 {
|
2020-10-20 17:19:21 +00:00
|
|
|
h.notify()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-10-20 17:59:53 +00:00
|
|
|
// Must be synchronized by the caller.
|
|
|
|
func (h *expiryHeap) Remove(idx int) {
|
|
|
|
entry := h.Entries[idx]
|
|
|
|
heap.Remove(h, idx)
|
|
|
|
|
|
|
|
// A goroutine which is fetching a new value will have a reference to this
|
|
|
|
// entry. When it re-acquires the lock it needs to be informed that
|
|
|
|
// the entry was expired while it was fetching. Setting HeapIndex to -1
|
|
|
|
// indicates that the entry is no longer in the heap, and must be re-added.
|
|
|
|
entry.HeapIndex = -1
|
2020-10-20 18:57:22 +00:00
|
|
|
|
|
|
|
if idx == 0 {
|
|
|
|
h.notify()
|
|
|
|
}
|
2020-10-20 17:59:53 +00:00
|
|
|
}
|
|
|
|
|
2020-10-20 17:19:21 +00:00
|
|
|
func (h *expiryHeap) Len() int { return len(h.Entries) }
|
|
|
|
|
|
|
|
func (h *expiryHeap) Swap(i, j int) {
|
|
|
|
h.Entries[i], h.Entries[j] = h.Entries[j], h.Entries[i]
|
|
|
|
h.Entries[i].HeapIndex = i
|
|
|
|
h.Entries[j].HeapIndex = j
|
|
|
|
}
|
|
|
|
|
|
|
|
func (h *expiryHeap) Less(i, j int) bool {
|
|
|
|
// The usage of Before here is important (despite being obvious):
|
|
|
|
// this function uses the monotonic time that should be available
|
|
|
|
// on the time.Time value so the heap is immune to wall clock changes.
|
|
|
|
return h.Entries[i].Expires.Before(h.Entries[j].Expires)
|
|
|
|
}
|
|
|
|
|
|
|
|
// heap.Interface, this isn't expected to be called directly.
|
|
|
|
func (h *expiryHeap) Push(x interface{}) {
|
2020-10-20 18:57:22 +00:00
|
|
|
h.Entries = append(h.Entries, x.(*cacheEntryExpiry))
|
2020-10-20 17:19:21 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// heap.Interface, this isn't expected to be called directly.
|
|
|
|
func (h *expiryHeap) Pop() interface{} {
|
|
|
|
n := len(h.Entries)
|
|
|
|
entries := h.Entries
|
|
|
|
last := entries[n-1]
|
|
|
|
h.Entries = entries[0 : n-1]
|
|
|
|
return last
|
|
|
|
}
|
|
|
|
|
2020-10-20 18:57:22 +00:00
|
|
|
// notify the timer that the head value has changed, so the expiry time has
|
|
|
|
// also likely changed.
|
2020-10-20 17:19:21 +00:00
|
|
|
func (h *expiryHeap) notify() {
|
2020-10-20 18:57:22 +00:00
|
|
|
// Send to channel without blocking. Skips sending if there is already
|
|
|
|
// an item in the buffered channel.
|
2020-10-20 17:19:21 +00:00
|
|
|
select {
|
|
|
|
case h.NotifyCh <- struct{}{}:
|
|
|
|
default:
|
|
|
|
}
|
|
|
|
}
|
2020-10-20 17:59:53 +00:00
|
|
|
|
|
|
|
// Must be synchronized by the caller.
|
|
|
|
func (h *expiryHeap) Next() timer {
|
|
|
|
if len(h.Entries) == 0 {
|
|
|
|
return timer{}
|
|
|
|
}
|
|
|
|
entry := h.Entries[0]
|
|
|
|
return timer{
|
|
|
|
timer: time.NewTimer(time.Until(entry.Expires)),
|
|
|
|
Entry: entry,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
type timer struct {
|
|
|
|
timer *time.Timer
|
|
|
|
Entry *cacheEntryExpiry
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *timer) Wait() <-chan time.Time {
|
|
|
|
if t.timer == nil {
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return t.timer.C
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t *timer) Stop() {
|
|
|
|
if t.timer != nil {
|
|
|
|
t.timer.Stop()
|
|
|
|
}
|
|
|
|
}
|