11package auth
22
33import (
4- "container/list "
4+ "maps "
55 "sync"
6+ "sync/atomic"
67 "time"
78
89 "github.com/zalando/skipper/filters"
10+ "github.com/zalando/skipper/metrics"
911)
1012
1113type (
1214 tokeninfoCache struct {
13- client tokeninfoClient
14- size int
15- ttl time. Duration
16- now func () time.Time
17-
18- mu sync. Mutex
19- cache map [string ]* entry
20- // least recently used token at the end
21- history * list. List
15+ client tokeninfoClient
16+ metrics metrics. Metrics
17+ size int
18+ ttl time.Duration
19+ now func () time. Time
20+
21+ cache sync. Map // map[string]*entry
22+ count atomic. Int64 // estimated number of cached entries, see https://github.com/golang/go/issues/20680
23+ quit chan struct {}
2224 }
2325
2426 entry struct {
25- cachedAt time.Time
26- expiresAt time.Time
27- info map [string ]any
28- // reference in the history
29- href * list.Element
27+ expiresAt time.Time
28+ info map [string ]any
29+ infoExpiresAt time.Time
3030 }
3131)
3232
3333var _ tokeninfoClient = & tokeninfoCache {}
3434
3535const expiresInField = "expires_in"
3636
37- func newTokeninfoCache (client tokeninfoClient , size int , ttl time.Duration ) * tokeninfoCache {
38- return & tokeninfoCache {
37+ func newTokeninfoCache (client tokeninfoClient , metrics metrics. Metrics , size int , ttl time.Duration ) * tokeninfoCache {
38+ c := & tokeninfoCache {
3939 client : client ,
40+ metrics : metrics ,
4041 size : size ,
4142 ttl : ttl ,
4243 now : time .Now ,
43- cache : make (map [string ]* entry , size ),
44- history : list .New (),
44+ quit : make (chan struct {}),
4545 }
46+ go c .evictLoop ()
47+ return c
48+ }
49+
50+ func (c * tokeninfoCache ) Close () {
51+ c .client .Close ()
52+ close (c .quit )
4653}
4754
4855func (c * tokeninfoCache ) getTokeninfo (token string , ctx filters.FilterContext ) (map [string ]any , error ) {
@@ -58,35 +65,21 @@ func (c *tokeninfoCache) getTokeninfo(token string, ctx filters.FilterContext) (
5865}
5966
6067func (c * tokeninfoCache ) cached (token string ) map [string ]any {
61- now := c .now ()
62-
63- c .mu .Lock ()
64-
65- if e , ok := c .cache [token ]; ok {
68+ if v , ok := c .cache .Load (token ); ok {
69+ now := c .now ()
70+ e := v .(* entry )
6671 if now .Before (e .expiresAt ) {
67- c .history .MoveToFront (e .href )
68- cachedInfo := e .info
69- c .mu .Unlock ()
70-
7172 // It might be ok to return cached value
7273 // without adjusting "expires_in" to avoid copy
7374 // if caller never modifies the result and
7475 // when "expires_in" did not change (same second)
7576 // or for small TTL values
76- info := shallowCopyOf ( cachedInfo )
77+ info := maps . Clone ( e . info )
7778
78- elapsed := now .Sub (e .cachedAt ).Truncate (time .Second ).Seconds ()
79- info [expiresInField ] = info [expiresInField ].(float64 ) - elapsed
79+ info [expiresInField ] = e .infoExpiresAt .Sub (now ).Truncate (time .Second ).Seconds ()
8080 return info
81- } else {
82- // remove expired
83- delete (c .cache , token )
84- c .history .Remove (e .href )
8581 }
8682 }
87-
88- c .mu .Unlock ()
89-
9083 return nil
9184}
9285
@@ -95,38 +88,62 @@ func (c *tokeninfoCache) tryCache(token string, info map[string]any) {
9588 if expiresIn <= 0 {
9689 return
9790 }
98- if c .ttl > 0 && expiresIn > c .ttl {
99- expiresIn = c .ttl
100- }
10191
10292 now := c .now ()
103- expiresAt := now .Add (expiresIn )
93+ e := & entry {
94+ info : info ,
95+ infoExpiresAt : now .Add (expiresIn ),
96+ }
97+
98+ if c .ttl > 0 && expiresIn > c .ttl {
99+ e .expiresAt = now .Add (c .ttl )
100+ } else {
101+ e .expiresAt = e .infoExpiresAt
102+ }
104103
105- c .mu .Lock ()
106- defer c .mu .Unlock ()
104+ if _ , loaded := c .cache .Swap (token , e ); ! loaded {
105+ c .count .Add (1 )
106+ }
107+ }
107108
108- if e , ok := c .cache [token ]; ok {
109- // update
110- e .cachedAt = now
111- e .expiresAt = expiresAt
112- e .info = info
113- c .history .MoveToFront (e .href )
114- return
109+ func (c * tokeninfoCache ) evictLoop () {
110+ ticker := time .NewTicker (time .Minute )
111+ defer ticker .Stop ()
112+ for {
113+ select {
114+ case <- c .quit :
115+ return
116+ case <- ticker .C :
117+ c .evict ()
118+ }
115119 }
120+ }
116121
117- // create
118- c .cache [token ] = & entry {
119- cachedAt : now ,
120- expiresAt : expiresAt ,
121- info : info ,
122- href : c .history .PushFront (token ),
122+ func (c * tokeninfoCache ) evict () {
123+ now := c .now ()
124+ // Evict expired entries
125+ c .cache .Range (func (key , value any ) bool {
126+ e := value .(* entry )
127+ if now .After (e .expiresAt ) {
128+ if c .cache .CompareAndDelete (key , value ) {
129+ c .count .Add (- 1 )
130+ }
131+ }
132+ return true
133+ })
134+
135+ // Evict random entries until the cache size is within limits
136+ if c .count .Load () > int64 (c .size ) {
137+ c .cache .Range (func (key , value any ) bool {
138+ if c .cache .CompareAndDelete (key , value ) {
139+ c .count .Add (- 1 )
140+ }
141+ return c .count .Load () > int64 (c .size )
142+ })
123143 }
124144
125- // remove least used
126- if len (c .cache ) > c .size {
127- leastUsed := c .history .Back ()
128- delete (c .cache , leastUsed .Value .(string ))
129- c .history .Remove (leastUsed )
145+ if c .metrics != nil {
146+ c .metrics .UpdateGauge ("tokeninfocache.count" , float64 (c .count .Load ()))
130147 }
131148}
132149
@@ -141,11 +158,3 @@ func expiresIn(info map[string]any) time.Duration {
141158 }
142159 return 0
143160}
144-
145- func shallowCopyOf (info map [string ]any ) map [string ]any {
146- m := make (map [string ]any , len (info ))
147- for k , v := range info {
148- m [k ] = v
149- }
150- return m
151- }
0 commit comments