...
1 package wfe2
2
3 import (
4 "context"
5 "fmt"
6 "sync"
7 "time"
8
9 "github.com/golang/groupcache/lru"
10 "github.com/jmhodges/clock"
11 corepb "github.com/letsencrypt/boulder/core/proto"
12 sapb "github.com/letsencrypt/boulder/sa/proto"
13 "github.com/prometheus/client_golang/prometheus"
14 "google.golang.org/grpc"
15 "google.golang.org/protobuf/proto"
16 )
17
18
19
20 type AccountGetter interface {
21 GetRegistration(ctx context.Context, regID *sapb.RegistrationID, opts ...grpc.CallOption) (*corepb.Registration, error)
22 }
23
24
25
26
27
28 type accountCache struct {
29
30
31 sync.Mutex
32 under AccountGetter
33 ttl time.Duration
34 cache *lru.Cache
35 clk clock.Clock
36 requests *prometheus.CounterVec
37 }
38
39 func NewAccountCache(
40 under AccountGetter,
41 maxEntries int,
42 ttl time.Duration,
43 clk clock.Clock,
44 stats prometheus.Registerer,
45 ) *accountCache {
46 requestsCount := prometheus.NewCounterVec(prometheus.CounterOpts{
47 Name: "cache_requests",
48 }, []string{"status"})
49 stats.MustRegister(requestsCount)
50 return &accountCache{
51 under: under,
52 ttl: ttl,
53 cache: lru.New(maxEntries),
54 clk: clk,
55 requests: requestsCount,
56 }
57 }
58
59 type accountEntry struct {
60 account *corepb.Registration
61 expires time.Time
62 }
63
64 func (ac *accountCache) GetRegistration(ctx context.Context, regID *sapb.RegistrationID, opts ...grpc.CallOption) (*corepb.Registration, error) {
65 ac.Lock()
66 val, ok := ac.cache.Get(regID.Id)
67 ac.Unlock()
68 if !ok {
69 ac.requests.WithLabelValues("miss").Inc()
70 return ac.queryAndStore(ctx, regID)
71 }
72 entry, ok := val.(accountEntry)
73 if !ok {
74 ac.requests.WithLabelValues("wrongtype").Inc()
75 return nil, fmt.Errorf("shouldn't happen: wrong type %T for cache entry", entry)
76 }
77 if entry.expires.Before(ac.clk.Now()) {
78
79
80
81
82
83 ac.Lock()
84 ac.cache.Remove(regID.Id)
85 ac.Unlock()
86 ac.requests.WithLabelValues("expired").Inc()
87 return ac.queryAndStore(ctx, regID)
88 }
89 if entry.account.Id != regID.Id {
90 ac.requests.WithLabelValues("wrong id from cache").Inc()
91 return nil, fmt.Errorf("shouldn't happen: wrong account ID. expected %d, got %d", regID.Id, entry.account.Id)
92 }
93 copied := new(corepb.Registration)
94 proto.Merge(copied, entry.account)
95 ac.requests.WithLabelValues("hit").Inc()
96 return copied, nil
97 }
98
99 func (ac *accountCache) queryAndStore(ctx context.Context, regID *sapb.RegistrationID) (*corepb.Registration, error) {
100 account, err := ac.under.GetRegistration(ctx, regID)
101 if err != nil {
102 return nil, err
103 }
104 if account.Id != regID.Id {
105 ac.requests.WithLabelValues("wrong id from SA").Inc()
106 return nil, fmt.Errorf("shouldn't happen: wrong account ID from backend. expected %d, got %d", regID.Id, account.Id)
107 }
108
109 copied := new(corepb.Registration)
110 proto.Merge(copied, account)
111 ac.Lock()
112 ac.cache.Add(regID.Id, accountEntry{
113 account: copied,
114 expires: ac.clk.Now().Add(ac.ttl),
115 })
116 ac.Unlock()
117 return account, nil
118 }
119
View as plain text