| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| // Package memoize supports memoizing the return values of functions with |
| // idempotent results that are expensive to compute. |
| // |
| // To use this package, build a store and use it to acquire handles with the |
| // Bind method. |
| package memoize |
| |
| import ( |
| "context" |
| "flag" |
| "fmt" |
| "reflect" |
| "sync" |
| "sync/atomic" |
| |
| "golang.org/x/tools/internal/xcontext" |
| ) |
| |
| var ( |
| panicOnDestroyed = flag.Bool("memoize_panic_on_destroyed", false, |
| "Panic when a destroyed generation is read rather than returning an error. "+ |
| "Panicking may make it easier to debug lifetime errors, especially when "+ |
| "used with GOTRACEBACK=crash to see all running goroutines.") |
| ) |
| |
| // Store binds keys to functions, returning handles that can be used to access |
| // the functions results. |
| type Store struct { |
| mu sync.Mutex |
| // handles is the set of values stored. |
| handles map[interface{}]*Handle |
| |
| // generations is the set of generations live in this store. |
| generations map[*Generation]struct{} |
| } |
| |
| // Generation creates a new Generation associated with s. Destroy must be |
| // called on the returned Generation once it is no longer in use. name is |
| // for debugging purposes only. |
| func (s *Store) Generation(name string) *Generation { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| if s.handles == nil { |
| s.handles = map[interface{}]*Handle{} |
| s.generations = map[*Generation]struct{}{} |
| } |
| g := &Generation{store: s, name: name} |
| s.generations[g] = struct{}{} |
| return g |
| } |
| |
| // A Generation is a logical point in time of the cache life-cycle. Cache |
| // entries associated with a Generation will not be removed until the |
| // Generation is destroyed. |
| type Generation struct { |
| // destroyed is 1 after the generation is destroyed. Atomic. |
| destroyed uint32 |
| store *Store |
| name string |
| // destroyedBy describes the caller that togged destroyed from 0 to 1. |
| destroyedBy string |
| // wg tracks the reference count of this generation. |
| wg sync.WaitGroup |
| } |
| |
| // Destroy waits for all operations referencing g to complete, then removes |
| // all references to g from cache entries. Cache entries that no longer |
| // reference any non-destroyed generation are removed. Destroy must be called |
| // exactly once for each generation, and destroyedBy describes the caller. |
| func (g *Generation) Destroy(destroyedBy string) { |
| g.wg.Wait() |
| |
| prevDestroyedBy := g.destroyedBy |
| g.destroyedBy = destroyedBy |
| if ok := atomic.CompareAndSwapUint32(&g.destroyed, 0, 1); !ok { |
| panic("Destroy on generation " + g.name + " already destroyed by " + prevDestroyedBy) |
| } |
| |
| g.store.mu.Lock() |
| defer g.store.mu.Unlock() |
| for k, e := range g.store.handles { |
| e.mu.Lock() |
| if _, ok := e.generations[g]; ok { |
| delete(e.generations, g) // delete even if it's dead, in case of dangling references to the entry. |
| if len(e.generations) == 0 { |
| delete(g.store.handles, k) |
| e.state = stateDestroyed |
| if e.cleanup != nil && e.value != nil { |
| e.cleanup(e.value) |
| } |
| } |
| } |
| e.mu.Unlock() |
| } |
| delete(g.store.generations, g) |
| } |
| |
| // Acquire creates a new reference to g, and returns a func to release that |
| // reference. |
| func (g *Generation) Acquire() func() { |
| destroyed := atomic.LoadUint32(&g.destroyed) |
| if destroyed != 0 { |
| panic("acquire on generation " + g.name + " destroyed by " + g.destroyedBy) |
| } |
| g.wg.Add(1) |
| return g.wg.Done |
| } |
| |
| // Arg is a marker interface that can be embedded to indicate a type is |
| // intended for use as a Function argument. |
| type Arg interface{ memoizeArg() } |
| |
| // Function is the type for functions that can be memoized. |
| // The result must be a pointer. |
| type Function func(ctx context.Context, arg Arg) interface{} |
| |
| type state int |
| |
| const ( |
| stateIdle = iota |
| stateRunning |
| stateCompleted |
| stateDestroyed |
| ) |
| |
| // Handle is returned from a store when a key is bound to a function. |
| // It is then used to access the results of that function. |
| // |
| // A Handle starts out in idle state, waiting for something to demand its |
| // evaluation. It then transitions into running state. While it's running, |
| // waiters tracks the number of Get calls waiting for a result, and the done |
| // channel is used to notify waiters of the next state transition. Once the |
| // evaluation finishes, value is set, state changes to completed, and done |
| // is closed, unblocking waiters. Alternatively, as Get calls are cancelled, |
| // they decrement waiters. If it drops to zero, the inner context is cancelled, |
| // computation is abandoned, and state resets to idle to start the process over |
| // again. |
| type Handle struct { |
| key interface{} |
| mu sync.Mutex |
| |
| // generations is the set of generations in which this handle is valid. |
| generations map[*Generation]struct{} |
| |
| state state |
| // done is set in running state, and closed when exiting it. |
| done chan struct{} |
| // cancel is set in running state. It cancels computation. |
| cancel context.CancelFunc |
| // waiters is the number of Gets outstanding. |
| waiters uint |
| // the function that will be used to populate the value |
| function Function |
| // value is set in completed state. |
| value interface{} |
| // cleanup, if non-nil, is used to perform any necessary clean-up on values |
| // produced by function. |
| cleanup func(interface{}) |
| } |
| |
| // Bind returns a handle for the given key and function. |
| // |
| // Each call to bind will return the same handle if it is already bound. Bind |
| // will always return a valid handle, creating one if needed. Each key can |
| // only have one handle at any given time. The value will be held at least |
| // until the associated generation is destroyed. Bind does not cause the value |
| // to be generated. |
| // |
| // If cleanup is non-nil, it will be called on any non-nil values produced by |
| // function when they are no longer referenced. |
| func (g *Generation) Bind(key interface{}, function Function, cleanup func(interface{})) *Handle { |
| // panic early if the function is nil |
| // it would panic later anyway, but in a way that was much harder to debug |
| if function == nil { |
| panic("the function passed to bind must not be nil") |
| } |
| if atomic.LoadUint32(&g.destroyed) != 0 { |
| panic("operation on generation " + g.name + " destroyed by " + g.destroyedBy) |
| } |
| g.store.mu.Lock() |
| defer g.store.mu.Unlock() |
| h, ok := g.store.handles[key] |
| if !ok { |
| h := &Handle{ |
| key: key, |
| function: function, |
| generations: map[*Generation]struct{}{g: {}}, |
| cleanup: cleanup, |
| } |
| g.store.handles[key] = h |
| return h |
| } |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| if _, ok := h.generations[g]; !ok { |
| h.generations[g] = struct{}{} |
| } |
| return h |
| } |
| |
| // Stats returns the number of each type of value in the store. |
| func (s *Store) Stats() map[reflect.Type]int { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| |
| result := map[reflect.Type]int{} |
| for k := range s.handles { |
| result[reflect.TypeOf(k)]++ |
| } |
| return result |
| } |
| |
| // DebugOnlyIterate iterates through all live cache entries and calls f on them. |
| // It should only be used for debugging purposes. |
| func (s *Store) DebugOnlyIterate(f func(k, v interface{})) { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| |
| for k, e := range s.handles { |
| var v interface{} |
| e.mu.Lock() |
| if e.state == stateCompleted { |
| v = e.value |
| } |
| e.mu.Unlock() |
| if v == nil { |
| continue |
| } |
| f(k, v) |
| } |
| } |
| |
| func (g *Generation) Inherit(hs ...*Handle) { |
| for _, h := range hs { |
| if atomic.LoadUint32(&g.destroyed) != 0 { |
| panic("inherit on generation " + g.name + " destroyed by " + g.destroyedBy) |
| } |
| |
| h.mu.Lock() |
| if h.state == stateDestroyed { |
| panic(fmt.Sprintf("inheriting destroyed handle %#v (type %T) into generation %v", h.key, h.key, g.name)) |
| } |
| h.generations[g] = struct{}{} |
| h.mu.Unlock() |
| } |
| } |
| |
| // Cached returns the value associated with a handle. |
| // |
| // It will never cause the value to be generated. |
| // It will return the cached value, if present. |
| func (h *Handle) Cached(g *Generation) interface{} { |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| if _, ok := h.generations[g]; !ok { |
| return nil |
| } |
| if h.state == stateCompleted { |
| return h.value |
| } |
| return nil |
| } |
| |
| // Get returns the value associated with a handle. |
| // |
| // If the value is not yet ready, the underlying function will be invoked. |
| // If ctx is cancelled, Get returns nil. |
| func (h *Handle) Get(ctx context.Context, g *Generation, arg Arg) (interface{}, error) { |
| release := g.Acquire() |
| defer release() |
| |
| if ctx.Err() != nil { |
| return nil, ctx.Err() |
| } |
| h.mu.Lock() |
| if _, ok := h.generations[g]; !ok { |
| h.mu.Unlock() |
| |
| err := fmt.Errorf("reading key %#v: generation %v is not known", h.key, g.name) |
| if *panicOnDestroyed && ctx.Err() != nil { |
| panic(err) |
| } |
| return nil, err |
| } |
| switch h.state { |
| case stateIdle: |
| return h.run(ctx, g, arg) |
| case stateRunning: |
| return h.wait(ctx) |
| case stateCompleted: |
| defer h.mu.Unlock() |
| return h.value, nil |
| case stateDestroyed: |
| h.mu.Unlock() |
| err := fmt.Errorf("Get on destroyed entry %#v (type %T) in generation %v", h.key, h.key, g.name) |
| if *panicOnDestroyed { |
| panic(err) |
| } |
| return nil, err |
| default: |
| panic("unknown state") |
| } |
| } |
| |
| // run starts h.function and returns the result. h.mu must be locked. |
| func (h *Handle) run(ctx context.Context, g *Generation, arg Arg) (interface{}, error) { |
| childCtx, cancel := context.WithCancel(xcontext.Detach(ctx)) |
| h.cancel = cancel |
| h.state = stateRunning |
| h.done = make(chan struct{}) |
| function := h.function // Read under the lock |
| |
| // Make sure that the generation isn't destroyed while we're running in it. |
| release := g.Acquire() |
| go func() { |
| defer release() |
| // Just in case the function does something expensive without checking |
| // the context, double-check we're still alive. |
| if childCtx.Err() != nil { |
| return |
| } |
| v := function(childCtx, arg) |
| if childCtx.Err() != nil { |
| // It's possible that v was computed despite the context cancellation. In |
| // this case we should ensure that it is cleaned up. |
| if h.cleanup != nil && v != nil { |
| h.cleanup(v) |
| } |
| return |
| } |
| |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| // It's theoretically possible that the handle has been cancelled out |
| // of the run that started us, and then started running again since we |
| // checked childCtx above. Even so, that should be harmless, since each |
| // run should produce the same results. |
| if h.state != stateRunning { |
| // v will never be used, so ensure that it is cleaned up. |
| if h.cleanup != nil && v != nil { |
| h.cleanup(v) |
| } |
| return |
| } |
| // At this point v will be cleaned up whenever h is destroyed. |
| h.value = v |
| h.function = nil |
| h.state = stateCompleted |
| close(h.done) |
| }() |
| |
| return h.wait(ctx) |
| } |
| |
| // wait waits for the value to be computed, or ctx to be cancelled. h.mu must be locked. |
| func (h *Handle) wait(ctx context.Context) (interface{}, error) { |
| h.waiters++ |
| done := h.done |
| h.mu.Unlock() |
| |
| select { |
| case <-done: |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| if h.state == stateCompleted { |
| return h.value, nil |
| } |
| return nil, nil |
| case <-ctx.Done(): |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| h.waiters-- |
| if h.waiters == 0 && h.state == stateRunning { |
| h.cancel() |
| close(h.done) |
| h.state = stateIdle |
| h.done = nil |
| h.cancel = nil |
| } |
| return nil, ctx.Err() |
| } |
| } |