| // 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. |
| // |
| // The memoized result is returned again the next time the function is invoked. |
| // To prevent excessive memory use, the return values are only remembered |
| // for as long as they still have a user. |
| // |
| // To use this package, build a store and use it to acquire handles with the |
| // Bind method. |
| // |
| package memoize |
| |
| import ( |
| "context" |
| "runtime" |
| "sync" |
| "unsafe" |
| |
| "golang.org/x/tools/internal/xcontext" |
| ) |
| |
| // Store binds keys to functions, returning handles that can be used to access |
| // the functions results. |
| type Store struct { |
| mu sync.Mutex |
| // entries is the set of values stored. |
| entries map[interface{}]uintptr |
| } |
| |
| // Function is the type for functions that can be memoized. |
| // The result must be a pointer. |
| type Function func(ctx context.Context) interface{} |
| |
| // 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. |
| type Handle struct { |
| mu sync.Mutex |
| store *Store |
| key interface{} |
| // the function that will be used to populate the value |
| function Function |
| // the lazily poplulated value |
| value interface{} |
| // wait is used to block until the value is ready |
| // will only be non nil if the generator is already running |
| wait chan interface{} |
| // the cancel function for the context being used by the generator |
| // it can be used to abort the generator if the handle is garbage |
| // collected. |
| cancel context.CancelFunc |
| } |
| |
| // Has returns true if they key is currently valid for this store. |
| func (s *Store) Has(key interface{}) bool { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| _, found := s.entries[key] |
| return found |
| } |
| |
| // 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 for as long as the handle is, once it has been |
| // generated. |
| // Bind does not cause the value to be generated. |
| func (s *Store) Bind(key interface{}, function Function) *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") |
| } |
| // check if we already have the key |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| h := s.get(key) |
| if h != nil { |
| // we have a handle already, just return it |
| return h |
| } |
| // we have not seen this key before, add a new entry |
| if s.entries == nil { |
| s.entries = make(map[interface{}]uintptr) |
| } |
| h = &Handle{ |
| store: s, |
| key: key, |
| function: function, |
| } |
| // now add the weak reference to the handle into the map |
| s.entries[key] = uintptr(unsafe.Pointer(h)) |
| // add the deletion the entry when the handle is garbage collected |
| runtime.SetFinalizer(h, release) |
| return h |
| } |
| |
| // Find returns the handle associated with a key, if it is bound. |
| // |
| // It cannot cause a new handle to be generated, and thus may return nil. |
| func (s *Store) Find(key interface{}) *Handle { |
| s.mu.Lock() |
| defer s.mu.Unlock() |
| return s.get(key) |
| } |
| |
| // Cached returns the value associated with a key. |
| // |
| // It cannot cause the value to be generated. |
| // It will return the cached value, if present. |
| func (s *Store) Cached(key interface{}) interface{} { |
| h := s.Find(key) |
| if h == nil { |
| return nil |
| } |
| return h.Cached() |
| } |
| |
| //go:nocheckptr |
| // nocheckptr because: https://github.com/golang/go/issues/35125#issuecomment-545671062 |
| func (s *Store) get(key interface{}) *Handle { |
| // this must be called with the store mutex already held |
| e, found := s.entries[key] |
| if !found { |
| return nil |
| } |
| return (*Handle)(unsafe.Pointer(e)) |
| } |
| |
| // 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() interface{} { |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| return h.value |
| } |
| |
| // Get returns the value associated with a handle. |
| // |
| // If the value is not yet ready, the underlying function will be invoked. |
| // This activates the handle, and it will remember the value for as long as it exists. |
| func (h *Handle) Get(ctx context.Context) interface{} { |
| h.mu.Lock() |
| defer h.mu.Unlock() |
| if h.function == nil { |
| return h.value |
| } |
| // value not ready yet |
| select { |
| case h.value = <-h.run(ctx): |
| // successfully got the value |
| h.function = nil |
| h.cancel = nil |
| return h.value |
| case <-ctx.Done(): |
| // cancelled outer context, leave the generator running |
| // for someone else to pick up later |
| return nil |
| } |
| } |
| |
| // run starts the generator if necessary and returns the value channel. |
| func (h *Handle) run(ctx context.Context) chan interface{} { |
| if h.wait != nil { |
| // generator already running |
| return h.wait |
| } |
| // we use a length one "postbox" so the go routine can quit even if |
| // nobody wants the result yet |
| h.wait = make(chan interface{}, 1) |
| ctx, cancel := context.WithCancel(xcontext.Detach(ctx)) |
| h.cancel = cancel |
| go func() { |
| // in here the handle lock is not held |
| // we post the value back to the first caller that waits for it |
| h.wait <- h.function(ctx) |
| close(h.wait) |
| }() |
| return h.wait |
| } |
| |
| func release(p interface{}) { |
| h := p.(*Handle) |
| h.store.mu.Lock() |
| defer h.store.mu.Unlock() |
| // there is a small gap between the garbage collector deciding that the handle |
| // is liable for collection and the finalizer being called |
| // if the handle is recovered during that time, you will end up with a valid |
| // handle that no longer has an entry in the map, and that no longer has a |
| // finalizer associated with it, but that is okay. |
| delete(h.store.entries, h.key) |
| } |