diff --git a/cache.go b/cache.go index ff4dc72..1f5b347 100644 --- a/cache.go +++ b/cache.go @@ -1,6 +1,9 @@ package dataloader -import "context" +import ( + "context" + "sync" +) // The Cache interface. If a custom cache is provided, it must implement this interface. type Cache interface { @@ -10,6 +13,62 @@ type Cache interface { Clear() } +// InMemoryCache is an in memory implementation of Cache interface. +// This implementation is well suited for a "per-request" dataloader +// (i.e. one that only lives for the life of an HTTP request) +// but it is not well suited for long lived cached items. +type InMemoryCache struct { + mu sync.RWMutex + items map[interface{}]Thunk +} + +// NewCache constructs a new InMemoryCache. +func NewCache() *InMemoryCache { + items := make(map[interface{}]Thunk) + return &InMemoryCache{ + items: items, + } +} + +// Set the `value` at `key` in the cache. +func (c *InMemoryCache) Set(_ context.Context, key interface{}, value Thunk) { + c.mu.Lock() + c.items[key] = value + c.mu.Unlock() +} + +// Get the value at `key` if it exists. +// Returns value (or nil) and whether the value was found. +func (c *InMemoryCache) Get(_ context.Context, key interface{}) (Thunk, bool) { + c.mu.RLock() + item, found := c.items[key] + c.mu.RUnlock() + + return item, found +} + +// Delete the value at `key` from the cache. +func (c *InMemoryCache) Delete(ctx context.Context, key interface{}) bool { + _, found := c.Get(ctx, key) + + if found { + c.mu.Lock() + delete(c.items, key) + c.mu.Unlock() + } + + return found +} + +// Clear the entire cache. +func (c *InMemoryCache) Clear() { + c.mu.Lock() + for k := range c.items { + delete(c.items, k) + } + c.mu.Unlock() +} + // NoCache implements Cache interface where all methods are noops. // This is useful for when you don't want to cache items but still // want to use a data loader @@ -19,10 +78,10 @@ type NoCache struct{} func (c *NoCache) Get(context.Context, interface{}) (Thunk, bool) { return nil, false } // Set is a NOOP -func (c *NoCache) Set(context.Context, interface{}, Thunk) { return } +func (c *NoCache) Set(context.Context, interface{}, Thunk) {} // Delete is a NOOP func (c *NoCache) Delete(context.Context, interface{}) bool { return false } // Clear is a NOOP -func (c *NoCache) Clear() { return } +func (c *NoCache) Clear() {} diff --git a/dataloader.go b/dataloader.go index a8031d9..0732425 100644 --- a/dataloader.go +++ b/dataloader.go @@ -280,13 +280,16 @@ func (l *Loader) LoadMany(originalContext context.Context, keys []interface{}) T wg.Add(length) for i := range keys { - go func(ctx context.Context, i int) { + // Request the key serially to ensure the result order is maintained in key order. + thunk := l.Load(ctx, keys[i]) + + go func(ctx context.Context, i int, thunk Thunk) { defer wg.Done() - thunk := l.Load(ctx, keys[i]) + result, err := thunk() data[i] = result errors[i] = err - }(ctx, i) + }(ctx, i, thunk) } go func() { diff --git a/inMemoryCache.go b/inMemoryCache.go deleted file mode 100644 index 62b4248..0000000 --- a/inMemoryCache.go +++ /dev/null @@ -1,65 +0,0 @@ -// +build !go1.9 - -package dataloader - -import ( - "context" - "sync" -) - -// InMemoryCache is an in memory implementation of Cache interface. -// this simple implementation is well suited for -// a "per-request" dataloader (i.e. one that only lives -// for the life of an http request) but it not well suited -// for long lived cached items. -type InMemoryCache struct { - items map[interface{}]Thunk - mu sync.RWMutex -} - -// NewCache constructs a new InMemoryCache -func NewCache() *InMemoryCache { - items := make(map[interface{}]Thunk) - return &InMemoryCache{ - items: items, - } -} - -// Set sets the `value` at `key` in the cache -func (c *InMemoryCache) Set(_ context.Context, key interface{}, value Thunk) { - c.mu.Lock() - c.items[key] = value - c.mu.Unlock() -} - -// Get gets the value at `key` if it exsits, returns value (or nil) and bool -// indicating of value was found -func (c *InMemoryCache) Get(_ context.Context, key interface{}) (Thunk, bool) { - c.mu.RLock() - defer c.mu.RUnlock() - - item, found := c.items[key] - if !found { - return nil, false - } - - return item, true -} - -// Delete deletes item at `key` from cache -func (c *InMemoryCache) Delete(ctx context.Context, key interface{}) bool { - if _, found := c.Get(ctx, key); found { - c.mu.Lock() - defer c.mu.Unlock() - delete(c.items, key) - return true - } - return false -} - -// Clear clears the entire cache -func (c *InMemoryCache) Clear() { - c.mu.Lock() - c.items = map[interface{}]Thunk{} - c.mu.Unlock() -} diff --git a/inMemoryCache_go19.go b/inMemoryCache_go19.go deleted file mode 100644 index af5916a..0000000 --- a/inMemoryCache_go19.go +++ /dev/null @@ -1,57 +0,0 @@ -// +build go1.9 - -package dataloader - -import ( - "context" - "sync" -) - -// InMemoryCache is an in memory implementation of Cache interface. -// this simple implementation is well suited for -// a "per-request" dataloader (i.e. one that only lives -// for the life of an http request) but it not well suited -// for long lived cached items. -type InMemoryCache struct { - items *sync.Map -} - -// NewCache constructs a new InMemoryCache -func NewCache() *InMemoryCache { - return &InMemoryCache{ - items: &sync.Map{}, - } -} - -// Set sets the `value` at `key` in the cache -func (c *InMemoryCache) Set(_ context.Context, key interface{}, value Thunk) { - c.items.Store(key, value) -} - -// Get gets the value at `key` if it exsits, returns value (or nil) and bool -// indicating of value was found -func (c *InMemoryCache) Get(_ context.Context, key interface{}) (Thunk, bool) { - item, found := c.items.Load(key) - if !found { - return nil, false - } - - return item.(Thunk), true -} - -// Delete deletes item at `key` from cache -func (c *InMemoryCache) Delete(_ context.Context, key interface{}) bool { - if _, found := c.items.Load(key); found { - c.items.Delete(key) - return true - } - return false -} - -// Clear clears the entire cache -func (c *InMemoryCache) Clear() { - c.items.Range(func(key, _ interface{}) bool { - c.items.Delete(key) - return true - }) -}