k8s.io/kube-openapi@v0.0.0-20240228011516-70dd3763d340/pkg/cached/cache.go (about) 1 /* 2 Copyright 2022 The Kubernetes Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 // Package cached provides a cache mechanism based on etags to lazily 18 // build, and/or cache results from expensive operation such that those 19 // operations are not repeated unnecessarily. The operations can be 20 // created as a tree, and replaced dynamically as needed. 21 // 22 // All the operations in this module are thread-safe. 23 // 24 // # Dependencies and types of caches 25 // 26 // This package uses a source/transform/sink model of caches to build 27 // the dependency tree, and can be used as follows: 28 // - [Func]: A source cache that recomputes the content every time. 29 // - [Once]: A source cache that always produces the 30 // same content, it is only called once. 31 // - [Transform]: A cache that transforms data from one format to 32 // another. It's only refreshed when the source changes. 33 // - [Merge]: A cache that aggregates multiple caches in a map into one. 34 // It's only refreshed when the source changes. 35 // - [MergeList]: A cache that aggregates multiple caches in a list into one. 36 // It's only refreshed when the source changes. 37 // - [Atomic]: A cache adapter that atomically replaces the source with a new one. 38 // - [LastSuccess]: A cache adapter that caches the last successful and returns 39 // it if the next call fails. It extends [Atomic]. 40 // 41 // # Etags 42 // 43 // Etags in this library is a cache version identifier. It doesn't 44 // necessarily strictly match to the semantics of http `etags`, but are 45 // somewhat inspired from it and function with the same principles. 46 // Hashing the content is a good way to guarantee that your function is 47 // never going to be called spuriously. In Kubernetes world, this could 48 // be a `resourceVersion`, this can be an actual etag, a hash, a UUID 49 // (if the cache always changes), or even a made-up string when the 50 // content of the cache never changes. 51 package cached 52 53 import ( 54 "fmt" 55 "sync" 56 "sync/atomic" 57 ) 58 59 // Value is wrapping a value behind a getter for lazy evaluation. 60 type Value[T any] interface { 61 Get() (value T, etag string, err error) 62 } 63 64 // Result is wrapping T and error into a struct for cases where a tuple is more 65 // convenient or necessary in Golang. 66 type Result[T any] struct { 67 Value T 68 Etag string 69 Err error 70 } 71 72 func (r Result[T]) Get() (T, string, error) { 73 return r.Value, r.Etag, r.Err 74 } 75 76 // Func wraps a (thread-safe) function as a Value[T]. 77 func Func[T any](fn func() (T, string, error)) Value[T] { 78 return valueFunc[T](fn) 79 } 80 81 type valueFunc[T any] func() (T, string, error) 82 83 func (c valueFunc[T]) Get() (T, string, error) { 84 return c() 85 } 86 87 // Static returns constant values. 88 func Static[T any](value T, etag string) Value[T] { 89 return Result[T]{Value: value, Etag: etag} 90 } 91 92 // Merge merges a of cached values. The merge function only gets called if any of 93 // the dependency has changed. 94 // 95 // If any of the dependency returned an error before, or any of the 96 // dependency returned an error this time, or if the mergeFn failed 97 // before, then the function is run again. 98 // 99 // Note that this assumes there is no "partial" merge, the merge 100 // function will remerge all the dependencies together everytime. Since 101 // the list of dependencies is constant, there is no way to save some 102 // partial merge information either. 103 // 104 // Also note that Golang map iteration is not stable. If the mergeFn 105 // depends on the order iteration to be stable, it will need to 106 // implement its own sorting or iteration order. 107 func Merge[K comparable, T, V any](mergeFn func(results map[K]Result[T]) (V, string, error), caches map[K]Value[T]) Value[V] { 108 list := make([]Value[T], 0, len(caches)) 109 110 // map from index to key 111 indexes := make(map[int]K, len(caches)) 112 i := 0 113 for k := range caches { 114 list = append(list, caches[k]) 115 indexes[i] = k 116 i++ 117 } 118 119 return MergeList(func(results []Result[T]) (V, string, error) { 120 if len(results) != len(indexes) { 121 panic(fmt.Errorf("invalid result length %d, expected %d", len(results), len(indexes))) 122 } 123 m := make(map[K]Result[T], len(results)) 124 for i := range results { 125 m[indexes[i]] = results[i] 126 } 127 return mergeFn(m) 128 }, list) 129 } 130 131 // MergeList merges a list of cached values. The function only gets called if 132 // any of the dependency has changed. 133 // 134 // The benefit of ListMerger over the basic Merger is that caches are 135 // stored in an ordered list so the order of the cache will be 136 // preserved in the order of the results passed to the mergeFn. 137 // 138 // If any of the dependency returned an error before, or any of the 139 // dependency returned an error this time, or if the mergeFn failed 140 // before, then the function is reran. 141 // 142 // Note that this assumes there is no "partial" merge, the merge 143 // function will remerge all the dependencies together everytime. Since 144 // the list of dependencies is constant, there is no way to save some 145 // partial merge information either. 146 func MergeList[T, V any](mergeFn func(results []Result[T]) (V, string, error), delegates []Value[T]) Value[V] { 147 return &listMerger[T, V]{ 148 mergeFn: mergeFn, 149 delegates: delegates, 150 } 151 } 152 153 type listMerger[T, V any] struct { 154 lock sync.Mutex 155 mergeFn func([]Result[T]) (V, string, error) 156 delegates []Value[T] 157 cache []Result[T] 158 result Result[V] 159 } 160 161 func (c *listMerger[T, V]) prepareResultsLocked() []Result[T] { 162 cacheResults := make([]Result[T], len(c.delegates)) 163 ch := make(chan struct { 164 int 165 Result[T] 166 }, len(c.delegates)) 167 for i := range c.delegates { 168 go func(index int) { 169 value, etag, err := c.delegates[index].Get() 170 ch <- struct { 171 int 172 Result[T] 173 }{index, Result[T]{Value: value, Etag: etag, Err: err}} 174 }(i) 175 } 176 for i := 0; i < len(c.delegates); i++ { 177 res := <-ch 178 cacheResults[res.int] = res.Result 179 } 180 return cacheResults 181 } 182 183 func (c *listMerger[T, V]) needsRunningLocked(results []Result[T]) bool { 184 if c.cache == nil { 185 return true 186 } 187 if c.result.Err != nil { 188 return true 189 } 190 if len(results) != len(c.cache) { 191 panic(fmt.Errorf("invalid number of results: %v (expected %v)", len(results), len(c.cache))) 192 } 193 for i, oldResult := range c.cache { 194 newResult := results[i] 195 if newResult.Etag != oldResult.Etag || newResult.Err != nil || oldResult.Err != nil { 196 return true 197 } 198 } 199 return false 200 } 201 202 func (c *listMerger[T, V]) Get() (V, string, error) { 203 c.lock.Lock() 204 defer c.lock.Unlock() 205 cacheResults := c.prepareResultsLocked() 206 if c.needsRunningLocked(cacheResults) { 207 c.cache = cacheResults 208 c.result.Value, c.result.Etag, c.result.Err = c.mergeFn(c.cache) 209 } 210 return c.result.Value, c.result.Etag, c.result.Err 211 } 212 213 // Transform the result of another cached value. The transformFn will only be called 214 // if the source has updated, otherwise, the result will be returned. 215 // 216 // If the dependency returned an error before, or it returns an error 217 // this time, or if the transformerFn failed before, the function is 218 // reran. 219 func Transform[T, V any](transformerFn func(T, string, error) (V, string, error), source Value[T]) Value[V] { 220 return MergeList(func(delegates []Result[T]) (V, string, error) { 221 if len(delegates) != 1 { 222 panic(fmt.Errorf("invalid cache for transformer cache: %v", delegates)) 223 } 224 return transformerFn(delegates[0].Value, delegates[0].Etag, delegates[0].Err) 225 }, []Value[T]{source}) 226 } 227 228 // Once calls Value[T].Get() lazily and only once, even in case of an error result. 229 func Once[T any](d Value[T]) Value[T] { 230 return &once[T]{ 231 data: d, 232 } 233 } 234 235 type once[T any] struct { 236 once sync.Once 237 data Value[T] 238 result Result[T] 239 } 240 241 func (c *once[T]) Get() (T, string, error) { 242 c.once.Do(func() { 243 c.result.Value, c.result.Etag, c.result.Err = c.data.Get() 244 }) 245 return c.result.Value, c.result.Etag, c.result.Err 246 } 247 248 // Replaceable extends the Value[T] interface with the ability to change the 249 // underlying Value[T] after construction. 250 type Replaceable[T any] interface { 251 Value[T] 252 Store(Value[T]) 253 } 254 255 // Atomic wraps a Value[T] as an atomic value that can be replaced. It implements 256 // Replaceable[T]. 257 type Atomic[T any] struct { 258 value atomic.Pointer[Value[T]] 259 } 260 261 var _ Replaceable[[]byte] = &Atomic[[]byte]{} 262 263 func (x *Atomic[T]) Store(val Value[T]) { x.value.Store(&val) } 264 func (x *Atomic[T]) Get() (T, string, error) { return (*x.value.Load()).Get() } 265 266 // LastSuccess calls Value[T].Get(), but hides errors by returning the last 267 // success if there has been any. 268 type LastSuccess[T any] struct { 269 Atomic[T] 270 success atomic.Pointer[Result[T]] 271 } 272 273 var _ Replaceable[[]byte] = &LastSuccess[[]byte]{} 274 275 func (c *LastSuccess[T]) Get() (T, string, error) { 276 success := c.success.Load() 277 value, etag, err := c.Atomic.Get() 278 if err == nil { 279 if success == nil { 280 c.success.CompareAndSwap(nil, &Result[T]{Value: value, Etag: etag, Err: err}) 281 } 282 return value, etag, err 283 } 284 285 if success != nil { 286 return success.Value, success.Etag, success.Err 287 } 288 289 return value, etag, err 290 }