go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/caching/cacheContext/context.go (about) 1 // Copyright 2016 The LUCI Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Package cacheContext implements a context.Context wrapper which caches the 16 // results of Value calls, speeding up subsequent calls for the same key. 17 // 18 // Context values are stored as immutable layers in a backwards-referenced list. 19 // Each lookup traverses the list from tail to head looking for a layer with the 20 // requested value. As a Context retains more values, this traversal will get 21 // more expensive. This class can be used for large Contexts to avoid repeatedly 22 // paying the cost of traversal for the same read-only value. 23 package cacheContext 24 25 import ( 26 "context" 27 "sync" 28 ) 29 30 // cacheContext implements a caching context.Context. 31 type cacheContext struct { 32 context.Context 33 34 mu sync.RWMutex 35 cache map[any]any 36 } 37 38 // Wrap wraps the supplied Context in a caching Context. All Value lookups will 39 // be cached at this level, avoiding the expense of future Context traversals 40 // for that same key. 41 func Wrap(ctx context.Context) context.Context { 42 if _, ok := ctx.(*cacheContext); ok { 43 return ctx 44 } 45 return &cacheContext{Context: ctx} 46 } 47 48 func (c *cacheContext) Value(key any) any { 49 // Optimistic: the value is cached. 50 c.mu.RLock() 51 if v, ok := c.cache[key]; ok { 52 c.mu.RUnlock() 53 return v 54 } 55 c.mu.RUnlock() 56 57 // Pessimistic: not in cache, load from Context and cache it. 58 c.mu.Lock() 59 defer c.mu.Unlock() 60 if v, ok := c.cache[key]; ok { 61 return v // someone did it already 62 } 63 v := c.Context.Value(key) 64 if c.cache == nil { 65 c.cache = make(map[any]any, 1) 66 } 67 c.cache[key] = v 68 return v 69 }