blob: 97a22d59aa95583a51fc18038bd927df3cd1aaf3 [file] [log] [blame]
// Copyright 2016 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// Package cacheContext implements a context.Context wrapper which caches the
// results of Value calls, speeding up subsequent calls for the same key.
//
// Context values are stored as immutable layers in a backwards-referenced list.
// Each lookup traverses the list from tail to head looking for a layer with the
// requested value. As a Context retains more values, this traversal will get
// more expensive. This class can be used for large Contexts to avoid repeatedly
// paying the cost of traversal for the same read-only value.
package cacheContext
import (
"context"
"sync"
)
// cacheContext implements a caching context.Context.
type cacheContext struct {
context.Context
mu sync.RWMutex
cache map[interface{}]interface{}
}
// Wrap wraps the supplied Context in a caching Context. All Value lookups will
// be cached at this level, avoiding the expense of future Context traversals
// for that same key.
func Wrap(c context.Context) context.Context {
if _, ok := c.(*cacheContext); ok {
return c
}
return &cacheContext{Context: c}
}
func (c *cacheContext) Value(key interface{}) interface{} {
// Optimistic: the value is cached.
c.mu.RLock()
if v, ok := c.cache[key]; ok {
c.mu.RUnlock()
return v
}
c.mu.RUnlock()
// Pessimistic: not in cache, load from Context and cache it.
c.mu.Lock()
defer c.mu.Unlock()
if v, ok := c.cache[key]; ok {
return v // someone did it already
}
v := c.Context.Value(key)
if c.cache == nil {
c.cache = make(map[interface{}]interface{}, 1)
}
c.cache[key] = v
return v
}