Raw
  1package backoff
  2
  3import (
  4	"container/list"
  5	"errors"
  6	"sync"
  7	"time"
  8)
  9
 10const (
 11	Threshold = 5
 12	Base      = time.Second
 13	Cap       = 15 * time.Minute
 14	Entries   = 10000
 15)
 16
 17var errNoEntries = errors.New("backoff: a table needs at least one entry")
 18
 19type entry struct {
 20	key      string
 21	failures int
 22	until    time.Time
 23	in       *list.List
 24}
 25
 26type Backoff struct {
 27	mu       sync.Mutex
 28	now      func() time.Time
 29	max      int
 30	entries  map[string]*list.Element
 31	counting *list.List
 32	reached  *list.List
 33	evicted  uint64
 34}
 35
 36func New(now func() time.Time, max int) *Backoff {
 37	if max < 1 {
 38		panic(errNoEntries)
 39	}
 40	return &Backoff{now: now, max: max, entries: map[string]*list.Element{}, counting: list.New(), reached: list.New()}
 41}
 42
 43func (b *Backoff) Locked(key string) bool {
 44	b.mu.Lock()
 45	defer b.mu.Unlock()
 46	el, ok := b.entries[key]
 47	if !ok {
 48		return false
 49	}
 50	e := el.Value.(*entry)
 51	e.in.MoveToFront(el)
 52	return b.now().Before(e.until)
 53}
 54
 55func (b *Backoff) Fail(key string) {
 56	b.mu.Lock()
 57	defer b.mu.Unlock()
 58	el, ok := b.entries[key]
 59	if !ok {
 60		for len(b.entries) >= b.max {
 61			b.evict()
 62		}
 63		el = b.counting.PushFront(&entry{key: key, in: b.counting})
 64		b.entries[key] = el
 65	}
 66	e := el.Value.(*entry)
 67	e.in.MoveToFront(el)
 68	e.failures++
 69	if e.failures < Threshold {
 70		return
 71	}
 72	if e.in == b.counting {
 73		b.counting.Remove(el)
 74		e.in = b.reached
 75		b.entries[key] = b.reached.PushFront(e)
 76	}
 77	window := Cap
 78	if shift := e.failures - Threshold; shift < 20 {
 79		if w := Base << shift; w < Cap {
 80			window = w
 81		}
 82	}
 83	e.until = b.now().Add(window)
 84}
 85
 86func (b *Backoff) evict() {
 87	victim := b.counting.Back()
 88	if victim == nil {
 89		victim = b.reached.Back()
 90	}
 91	e := victim.Value.(*entry)
 92	e.in.Remove(victim)
 93	delete(b.entries, e.key)
 94	b.evicted++
 95}
 96
 97func (b *Backoff) Reset(key string) {
 98	b.mu.Lock()
 99	defer b.mu.Unlock()
100	if el, ok := b.entries[key]; ok {
101		el.Value.(*entry).in.Remove(el)
102		delete(b.entries, key)
103	}
104}
105
106func (b *Backoff) Len() int {
107	b.mu.Lock()
108	defer b.mu.Unlock()
109	return len(b.entries)
110}