1package memo
2
3import (
4	"sync"
5	"time"
6)
7
8type (
9	Memo struct {
10		f     Func
11		mu    sync.Mutex //guards cache
12		cache *entry
13
14		cacheDur  time.Duration
15		lastCache time.Time
16	}
17
18	Func func() (interface{}, error)
19)
20
21type (
22	entry struct {
23		res   result
24		ready chan struct{} // close when res is read
25	}
26
27	result struct {
28		value interface{}
29		err   error
30	}
31)
32
33func New(f Func, cd time.Duration) *Memo {
34	return &Memo{
35		f:        f,
36		cacheDur: cd,
37	}
38}
39
40// Invalidate resets the cache to nil if the memo's given cache duration has
41// elapsed, and returns true if the cache was actually invalidated.
42func (memo *Memo) Invalidate() bool {
43	defer memo.mu.Unlock()
44	memo.mu.Lock()
45
46	if memo.cache != nil && time.Now().Sub(memo.lastCache) > memo.cacheDur {
47		memo.cache = nil
48		memo.lastCache = time.Now()
49		return true
50	}
51	return false
52}
53
54func (memo *Memo) Get() (value interface{}, err error) {
55	memo.mu.Lock()
56	if memo.cache == nil {
57		memo.cache = &entry{ready: make(chan struct{})}
58		memo.mu.Unlock()
59
60		memo.cache.res.value, memo.cache.res.err = memo.f()
61
62		close(memo.cache.ready)
63	} else {
64		memo.mu.Unlock()
65
66		<-memo.cache.ready
67	}
68	return memo.cache.res.value, memo.cache.res.err
69}
70
71// Reset forcibly resets the cache to nil without checking if the cache
72// duration has elapsed.
73func (memo *Memo) Reset() {
74	defer memo.mu.Unlock()
75	memo.mu.Lock()
76
77	memo.cache = nil
78	memo.lastCache = time.Now()
79}
80