github.com/mjibson/goon@v1.1.0/goon.go (about)

     1  /*
     2   * Copyright (c) 2012 The Goon Authors
     3   *
     4   * Permission to use, copy, modify, and distribute this software for any
     5   * purpose with or without fee is hereby granted, provided that the above
     6   * copyright notice and this permission notice appear in all copies.
     7   *
     8   * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
     9   * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
    10   * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
    11   * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
    12   * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
    13   * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
    14   * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
    15   */
    16  
    17  package goon
    18  
    19  import (
    20  	"context"
    21  	"encoding/ascii85"
    22  	"fmt"
    23  	"net/http"
    24  	"path/filepath"
    25  	"reflect"
    26  	"runtime"
    27  	"sync"
    28  	"time"
    29  
    30  	"golang.org/x/crypto/blake2b"
    31  	"google.golang.org/appengine"
    32  	"google.golang.org/appengine/datastore"
    33  	"google.golang.org/appengine/log"
    34  	"google.golang.org/appengine/memcache"
    35  )
    36  
    37  var (
    38  	// LogErrors issues appengine.Context.Errorf on any error.
    39  	LogErrors = true
    40  	// LogTimeoutErrors issues appengine.Context.Warningf on memcache timeout errors.
    41  	LogTimeoutErrors = false
    42  
    43  	// MemcachePutTimeoutThreshold is the number of bytes after which the large
    44  	// timeout setting is added to the small timeout setting. This repeats.
    45  	// Which means that with a 20 KiB threshold and a 100 KiB memcache payload,
    46  	// the final timeout is MemcachePutTimeoutSmall + 5*MemcachePutTimeoutLarge
    47  	MemcachePutTimeoutThreshold = 20 * 1024 // 20 KiB
    48  	// MemcachePutTimeoutSmall is the minimum time to wait during memcache
    49  	// Put operations before aborting them and using the datastore.
    50  	MemcachePutTimeoutSmall = 5 * time.Millisecond
    51  	// MemcachePutTimeoutLarge is the amount of extra time to wait for larger
    52  	// memcache Put requests. See also MemcachePutTimeoutThreshold.
    53  	MemcachePutTimeoutLarge = 1 * time.Millisecond
    54  	// MemcacheGetTimeout is the amount of time to wait for all memcache Get
    55  	// requests, per key fetched. Because we can't really know how big entities
    56  	// we are requesting, this setting should be for the maximum size entity.
    57  	// The final timeout is limited to the number of maximum sized entities
    58  	// an RPC result can contain, so the timeout won't grow insanely large
    59  	// if you're fetching a ton of small entities.
    60  	MemcacheGetTimeout = 31250 * time.Microsecond // 31.25 milliseconds
    61  
    62  	// IgnoreFieldMismatch decides whether *datastore.ErrFieldMismatch errors
    63  	// should be silently ignored. This allows you to easily remove fields from structs.
    64  	IgnoreFieldMismatch = true
    65  )
    66  
    67  var (
    68  	// Determines if memcache.PutMulti errors are returned by goon.
    69  	// Currently only meant for use during goon development testing.
    70  	propagateMemcachePutError = false
    71  )
    72  
    73  // Goon holds the app engine context and the request memory cache.
    74  type Goon struct {
    75  	Context       context.Context
    76  	cache         *cache
    77  	inTransaction bool
    78  	txnCacheLock  sync.Mutex // protects toDelete / toDeleteMC
    79  	toDelete      map[string]struct{}
    80  	toDeleteMC    map[string]struct{}
    81  	// KindNameResolver is used to determine what Kind to give an Entity.
    82  	// Defaults to DefaultKindName
    83  	KindNameResolver KindNameResolver
    84  }
    85  
    86  // MemcacheKey returns the string form of the provided datastore key.
    87  var MemcacheKey = func(k *datastore.Key) string {
    88  	return k.Encode()
    89  }
    90  
    91  // Versioning, so that incompatible changes to the cache system won't cause problems
    92  var cacheKeyPrefix = fmt.Sprintf("g%X:", serializationFormatVersion)
    93  
    94  // cacheKey returns the fully legal string key used for cache systems
    95  func cacheKey(k *datastore.Key) string {
    96  	// By default we just use the prefix + MemcacheKey result
    97  	key := cacheKeyPrefix + MemcacheKey(k)
    98  	// However if the resulting key length exceeds the maximum allowed ..
    99  	if len(key) > memcacheMaxKeySize {
   100  		// .. then we need to shorten it while still staying unique.
   101  		// We pass the key through the BLAKE2b hash function,
   102  		// which is cryptographically secure but also very fast.
   103  		// We request an output of 24 bytes (192-bit) for speed reasons.
   104  		h, err := blake2b.New(24, nil)
   105  		if err != nil {
   106  			panic(fmt.Sprintf("Unexpected error initializing blake2b: %v", err))
   107  		}
   108  		h.Write([]byte(key))
   109  		hash := h.Sum(make([]byte, 0, 24))
   110  		// After hashing, we encode the results with ascii85.
   111  		// Ascii85 works in 4 byte chunks, generating 5 letters for each.
   112  		// Which means we turn our 24 byte hash into a 30 letter string.
   113  		//
   114  		// We want letters instead of using the hash directly for debug reasons.
   115  		// As it will be easier for people to observe & manage keys manually.
   116  		//
   117  		// We aim the length of 30, because 32 is the maximum length
   118  		// where the Go std map does key lookups directly without hashing.
   119  		encoded := make([]byte, 30, 30)
   120  		ascii85.Encode(encoded, hash)
   121  		key = string(encoded)
   122  	}
   123  	return key
   124  }
   125  
   126  // Returns the duration that should be used for a memcache.GetMulti timeout.
   127  func memcacheGetTimeout(keyCount int) time.Duration {
   128  	// Takes the number of keys given to memcache.GetMulti,
   129  	// clamps that to the maximum number of max sized items,
   130  	// and multiplies it with the per-max-sized-item timeout.
   131  	if keyCount > memcacheMaxRPCSize/memcacheMaxItemSize {
   132  		keyCount = memcacheMaxRPCSize / memcacheMaxItemSize
   133  	}
   134  	return time.Duration(keyCount) * MemcacheGetTimeout
   135  }
   136  
   137  // Returns the duration that should be used for a memcache.PutMulti timeout.
   138  func memcachePutTimeout(payloadSize int) time.Duration {
   139  	// Takes the payload size given to memcache.PutMulti,
   140  	// and returns a dynamic duration based on that size.
   141  	return MemcachePutTimeoutSmall + time.Duration(payloadSize/MemcachePutTimeoutThreshold)*MemcachePutTimeoutLarge
   142  }
   143  
   144  // Memcache limits
   145  // These are based on the constants in SDK/google/appengine/api/memcache/memcache_stub.py
   146  // Also documented in https://cloud.google.com/appengine/docs/standard/go/memcache/
   147  const (
   148  	// The Google provided overhead is 73 bytes, which seems wrong in practice.
   149  	// Testing has shown that Put/PutMulti will succeed with even a lower overhead.
   150  	// However by 'succeed' I mean not erroring. The value never gets stored.
   151  	// The actual overhead seems to be 76 at which point the items will
   152  	// start being stored in memcache and show up in the statistics.
   153  	memcacheOverhead     = 76
   154  	memcacheMaxKeySize   = 250
   155  	memcacheMaxItemSize  = 1 << 20 // 1 MiB
   156  	memcacheMaxValueSize = memcacheMaxItemSize - memcacheMaxKeySize - memcacheOverhead
   157  	memcacheMaxRPCSize   = 32 << 20 // 32 MiB
   158  )
   159  
   160  // Datastore limits
   161  const (
   162  	datastoreGetMultiMaxItems    = 1000
   163  	datastorePutMultiMaxItems    = 500
   164  	datastoreDeleteMultiMaxItems = 500
   165  
   166  	// The maximum GetMulti result RPC size was determined experimentally on 2019-05-20
   167  	datastoreGetMultiMaxRPCSize = 50 << 20 // 50 MiB
   168  )
   169  
   170  // NewGoon creates a new Goon object from the given request.
   171  func NewGoon(r *http.Request) *Goon {
   172  	return FromContext(appengine.NewContext(r))
   173  }
   174  
   175  // FromContext creates a new Goon object from the given appengine Context.
   176  // Useful with profiling packages like appstats.
   177  func FromContext(c context.Context) *Goon {
   178  	return &Goon{
   179  		Context:          c,
   180  		cache:            newCache(defaultCacheLimit),
   181  		KindNameResolver: DefaultKindName,
   182  	}
   183  }
   184  
   185  func (g *Goon) error(err error) {
   186  	if !LogErrors {
   187  		return
   188  	}
   189  	_, filename, line, ok := runtime.Caller(1)
   190  	if ok {
   191  		log.Errorf(g.Context, "goon - %s:%d - %v", filepath.Base(filename), line, err)
   192  	} else {
   193  		log.Errorf(g.Context, "goon - %v", err)
   194  	}
   195  }
   196  
   197  func (g *Goon) timeoutError(err error) {
   198  	if LogTimeoutErrors {
   199  		log.Warningf(g.Context, "goon memcache timeout: %v", err)
   200  	}
   201  }
   202  
   203  func (g *Goon) memcacheDeleteError(err error) {
   204  	if err == nil || !LogErrors {
   205  		return
   206  	}
   207  	if me, ok := err.(appengine.MultiError); ok {
   208  		for i := range me {
   209  			if me[i] != nil && me[i] != memcache.ErrCacheMiss {
   210  				err = me[i]
   211  				goto reportError
   212  			}
   213  		}
   214  		return
   215  	}
   216  reportError:
   217  	_, filename, line, ok := runtime.Caller(1)
   218  	if ok {
   219  		log.Errorf(g.Context, "goon - %s:%d - memcache.DeleteMulti failed: %v - the goon cache may be out of sync now!", filepath.Base(filename), line, err)
   220  	} else {
   221  		log.Errorf(g.Context, "goon - memcache.DeleteMulti failed: %v - the goon cache may be out of sync now!", err)
   222  	}
   223  }
   224  
   225  func (g *Goon) extractKeys(src interface{}, putRequest bool) ([]*datastore.Key, error) {
   226  	v := reflect.Indirect(reflect.ValueOf(src))
   227  	if v.Kind() != reflect.Slice {
   228  		return nil, fmt.Errorf("goon: value must be a slice or pointer-to-slice")
   229  	}
   230  	l := v.Len()
   231  
   232  	keys := make([]*datastore.Key, l)
   233  	for i := 0; i < l; i++ {
   234  		vi := v.Index(i)
   235  		key, hasStringId, err := g.getStructKey(vi.Interface())
   236  		if err != nil {
   237  			return nil, err
   238  		}
   239  		if !putRequest && key.Incomplete() {
   240  			return nil, fmt.Errorf("goon: cannot find a key for struct - %v", vi.Interface())
   241  		} else if putRequest && key.Incomplete() && hasStringId {
   242  			return nil, fmt.Errorf("goon: empty string id on put")
   243  		}
   244  		keys[i] = key
   245  	}
   246  	return keys, nil
   247  }
   248  
   249  // Key is the same as KeyError, except nil is returned on error or if the key
   250  // is incomplete.
   251  func (g *Goon) Key(src interface{}) *datastore.Key {
   252  	if k, err := g.KeyError(src); err == nil {
   253  		return k
   254  	}
   255  	return nil
   256  }
   257  
   258  // Kind returns src's datastore Kind or "" on error.
   259  func (g *Goon) Kind(src interface{}) string {
   260  	if k, err := g.KeyError(src); err == nil {
   261  		return k.Kind()
   262  	}
   263  	return ""
   264  }
   265  
   266  // KeyError returns the key of src based on its properties.
   267  func (g *Goon) KeyError(src interface{}) (*datastore.Key, error) {
   268  	key, _, err := g.getStructKey(src)
   269  	return key, err
   270  }
   271  
   272  // RunInTransaction runs f in a transaction. It calls f with a transaction
   273  // context tg that f should use for all App Engine operations. Neither cache nor
   274  // memcache are used or set during a transaction.
   275  //
   276  // Otherwise similar to appengine/datastore.RunInTransaction:
   277  // https://developers.google.com/appengine/docs/go/datastore/reference#RunInTransaction
   278  func (g *Goon) RunInTransaction(f func(tg *Goon) error, opts *datastore.TransactionOptions) error {
   279  	var ng *Goon
   280  	err := datastore.RunInTransaction(g.Context, func(tc context.Context) error {
   281  		ng = &Goon{
   282  			Context:          tc,
   283  			inTransaction:    true,
   284  			toDelete:         make(map[string]struct{}),
   285  			toDeleteMC:       make(map[string]struct{}),
   286  			KindNameResolver: g.KindNameResolver,
   287  		}
   288  		return f(ng)
   289  	}, opts)
   290  
   291  	if err == nil {
   292  		ng.txnCacheLock.Lock()
   293  		defer ng.txnCacheLock.Unlock()
   294  		if len(ng.toDeleteMC) > 0 {
   295  			var memkeys []string
   296  			for k := range ng.toDeleteMC {
   297  				memkeys = append(memkeys, k)
   298  			}
   299  			g.memcacheDeleteError(memcache.DeleteMulti(g.Context, memkeys))
   300  		}
   301  		for k := range ng.toDelete {
   302  			g.cache.Delete(k)
   303  		}
   304  	} else {
   305  		g.error(err)
   306  	}
   307  
   308  	return err
   309  }
   310  
   311  // Put saves the entity src into the datastore based on src's key k. If k
   312  // is an incomplete key, the returned key will be a unique key generated by
   313  // the datastore.
   314  func (g *Goon) Put(src interface{}) (*datastore.Key, error) {
   315  	v := reflect.ValueOf(src)
   316  	if v.Kind() != reflect.Ptr {
   317  		return nil, fmt.Errorf("goon: expected pointer to a struct, got %#v", src)
   318  	}
   319  	ks, err := g.PutMulti([]interface{}{src})
   320  	if err != nil {
   321  		if me, ok := err.(appengine.MultiError); ok {
   322  			return nil, me[0]
   323  		}
   324  		return nil, err
   325  	}
   326  	return ks[0], nil
   327  }
   328  
   329  // PutMulti is a batch version of Put.
   330  //
   331  // src must be a *[]S, *[]*S, *[]I, []S, []*S, or []I, for some struct type S,
   332  // or some interface type I. If *[]I or []I, each element must be a struct pointer.
   333  func (g *Goon) PutMulti(src interface{}) ([]*datastore.Key, error) {
   334  	keys, err := g.extractKeys(src, true) // allow incomplete keys on a Put request
   335  	if err != nil {
   336  		return nil, err
   337  	}
   338  
   339  	v := reflect.Indirect(reflect.ValueOf(src))
   340  	mu := new(sync.Mutex)
   341  	multiErr, any := make(appengine.MultiError, len(keys)), false
   342  	goroutines := (len(keys)-1)/datastorePutMultiMaxItems + 1
   343  	var wg sync.WaitGroup
   344  	wg.Add(goroutines)
   345  	for i := 0; i < goroutines; i++ {
   346  		go func(i int) {
   347  			defer wg.Done()
   348  			lo := i * datastorePutMultiMaxItems
   349  			hi := (i + 1) * datastorePutMultiMaxItems
   350  			if hi > len(keys) {
   351  				hi = len(keys)
   352  			}
   353  			rkeys, pmerr := datastore.PutMulti(g.Context, keys[lo:hi], v.Slice(lo, hi).Interface())
   354  			if pmerr != nil {
   355  				mu.Lock()
   356  				any = true // this flag tells PutMulti to return multiErr later
   357  				mu.Unlock()
   358  				merr, ok := pmerr.(appengine.MultiError)
   359  				if !ok {
   360  					g.error(pmerr)
   361  					for j := lo; j < hi; j++ {
   362  						multiErr[j] = pmerr
   363  					}
   364  					return
   365  				}
   366  				copy(multiErr[lo:hi], merr)
   367  			}
   368  
   369  			for i, key := range keys[lo:hi] {
   370  				if multiErr[lo+i] != nil {
   371  					continue // there was an error writing this value, go to next
   372  				}
   373  				vi := v.Index(lo + i).Interface()
   374  				if key.Incomplete() {
   375  					g.setStructKey(vi, rkeys[i])
   376  					keys[lo+i] = rkeys[i]
   377  				}
   378  			}
   379  		}(i)
   380  	}
   381  	wg.Wait()
   382  
   383  	// Caches need to be updated after the datastore to prevent a common race condition,
   384  	// where a concurrent request will fetch the not-yet-updated data from the datastore
   385  	// and populate the caches with it.
   386  	cachekeys := make([]string, 0, len(keys))
   387  	for _, key := range keys {
   388  		if !key.Incomplete() {
   389  			cachekeys = append(cachekeys, cacheKey(key))
   390  		}
   391  	}
   392  	if g.inTransaction {
   393  		g.txnCacheLock.Lock()
   394  		for _, ck := range cachekeys {
   395  			g.toDelete[ck] = struct{}{}
   396  			g.toDeleteMC[ck] = struct{}{}
   397  		}
   398  		g.txnCacheLock.Unlock()
   399  	} else {
   400  		g.cache.DeleteMulti(cachekeys)
   401  		g.memcacheDeleteError(memcache.DeleteMulti(g.Context, cachekeys))
   402  	}
   403  
   404  	if any {
   405  		return keys, realError(multiErr)
   406  	}
   407  	return keys, nil
   408  }
   409  
   410  // FlushLocalCache clears the local memory cache.
   411  func (g *Goon) FlushLocalCache() {
   412  	g.cache.Flush()
   413  }
   414  
   415  // ClearCache removes the provided entity from cache.
   416  // Takes either *S or *datastore.Key.
   417  // The 'mem' and 'local' booleans dictate the type of caches to clear.
   418  func (g *Goon) ClearCache(src interface{}, mem, local bool) error {
   419  	if !mem && !local {
   420  		// Nothing to clear ..
   421  		return nil
   422  	}
   423  	var srcs interface{}
   424  	if key, ok := src.(*datastore.Key); ok {
   425  		srcs = []*datastore.Key{key}
   426  	} else {
   427  		v := reflect.ValueOf(src)
   428  		if v.Kind() != reflect.Ptr {
   429  			return fmt.Errorf("goon: expected pointer to a struct, got %#v", src)
   430  		}
   431  		srcs = []interface{}{src}
   432  	}
   433  	err := g.ClearCacheMulti(srcs, mem, local)
   434  	if err != nil {
   435  		// Look for an embedded error if it's multi
   436  		if me, ok := err.(appengine.MultiError); ok {
   437  			return me[0]
   438  		}
   439  	}
   440  	return err
   441  }
   442  
   443  // ClearCacheMulti removes the provided entities from cache.
   444  // Takes either []*S or []*datastore.Key.
   445  // The 'mem' and 'local' booleans dictate the type of caches to clear.
   446  func (g *Goon) ClearCacheMulti(src interface{}, mem, local bool) error {
   447  	if !mem && !local {
   448  		// Nothing to clear ..
   449  		return nil
   450  	}
   451  	keys, ok := src.([]*datastore.Key)
   452  	if !ok {
   453  		var err error
   454  		keys, err = g.extractKeys(src, false) // don't allow incomplete keys on a Clear request
   455  		if err != nil {
   456  			return err
   457  		}
   458  	}
   459  	if len(keys) == 0 {
   460  		return nil
   461  		// not an error, and it was "successful", so return nil
   462  	}
   463  	cachekeys := make([]string, 0, len(keys))
   464  	for _, key := range keys {
   465  		cachekeys = append(cachekeys, cacheKey(key))
   466  	}
   467  	if g.inTransaction {
   468  		g.txnCacheLock.Lock()
   469  		for _, ck := range cachekeys {
   470  			if local {
   471  				g.toDelete[ck] = struct{}{}
   472  			}
   473  			if mem {
   474  				g.toDeleteMC[ck] = struct{}{}
   475  			}
   476  		}
   477  		g.txnCacheLock.Unlock()
   478  	} else {
   479  		if local {
   480  			g.cache.DeleteMulti(cachekeys)
   481  		}
   482  		if mem {
   483  			g.memcacheDeleteError(memcache.DeleteMulti(g.Context, cachekeys))
   484  		}
   485  	}
   486  	return nil
   487  }
   488  
   489  type memcacheTask struct {
   490  	items []*memcache.Item
   491  	size  int
   492  }
   493  
   494  // NB! putMemcache is expected to treat cacheItem as immutable!
   495  func (g *Goon) putMemcache(citems []*cacheItem) error {
   496  	// Go over all the cache items and generate memcache tasks from them,
   497  	// by splitting them up based on payload size
   498  	items := make([]*memcache.Item, len(citems))
   499  	tasks := make([]memcacheTask, 0, 1) // In most cases there's just a single task
   500  	lastSplit := 0
   501  	payloadSize := 0
   502  	for i, citem := range citems {
   503  		items[i] = &memcache.Item{
   504  			Key:   citem.key,
   505  			Value: citem.value,
   506  		}
   507  		itemSize := memcacheOverhead + len(citem.key) + len(citem.value)
   508  		if payloadSize+itemSize > memcacheMaxRPCSize {
   509  			tasks = append(tasks, memcacheTask{items: items[lastSplit:i], size: payloadSize})
   510  			lastSplit = i
   511  			payloadSize = 0
   512  		}
   513  		payloadSize += itemSize
   514  	}
   515  	tasks = append(tasks, memcacheTask{items: items[lastSplit:len(citems)], size: payloadSize})
   516  	// Execute all the tasks with goroutines
   517  	count := len(tasks)
   518  	errc := make(chan error, count)
   519  	for i := 0; i < count; i++ {
   520  		go func(idx int) {
   521  			tc, cf := context.WithTimeout(g.Context, memcachePutTimeout(tasks[idx].size))
   522  			errc <- memcache.SetMulti(tc, tasks[idx].items)
   523  			cf()
   524  		}(i)
   525  	}
   526  	// Wait for all goroutines to finish and log any errors.
   527  	// Also return a non-deterministic error if there are any.
   528  	var rerr error
   529  	for i := 0; i < count; i++ {
   530  		err := <-errc
   531  		if err != nil {
   532  			if appengine.IsTimeoutError(err) {
   533  				g.timeoutError(err)
   534  			} else {
   535  				g.error(err)
   536  			}
   537  			rerr = err
   538  		}
   539  	}
   540  	return rerr
   541  }
   542  
   543  // Get loads the entity based on dst's key into dst
   544  // If there is no such entity for the key, Get returns
   545  // datastore.ErrNoSuchEntity.
   546  func (g *Goon) Get(dst interface{}) error {
   547  	v := reflect.ValueOf(dst)
   548  	if v.Kind() != reflect.Ptr {
   549  		return fmt.Errorf("goon: expected pointer to a struct, got %#v", dst)
   550  	}
   551  	if !v.CanSet() {
   552  		v = v.Elem()
   553  	}
   554  	dsts := []interface{}{dst}
   555  	if err := g.GetMulti(dsts); err != nil {
   556  		// Look for an embedded error if it's multi
   557  		if me, ok := err.(appengine.MultiError); ok {
   558  			return me[0]
   559  		}
   560  		// Not multi, normal error
   561  		return err
   562  	}
   563  	v.Set(reflect.Indirect(reflect.ValueOf(dsts[0])))
   564  	return nil
   565  }
   566  
   567  // GetMulti is a batch version of Get.
   568  //
   569  // dst must be a *[]S, *[]*S, *[]I, []S, []*S, or []I, for some struct type S,
   570  // or some interface type I. If *[]I or []I, each element must be a struct pointer.
   571  func (g *Goon) GetMulti(dst interface{}) error {
   572  	keys, err := g.extractKeys(dst, false) // don't allow incomplete keys on a Get request
   573  	if err != nil {
   574  		return err
   575  	}
   576  
   577  	v := reflect.Indirect(reflect.ValueOf(dst))
   578  
   579  	multiErr, anyErr := make(appengine.MultiError, len(keys)), false
   580  	var extraErr error
   581  
   582  	if g.inTransaction {
   583  		// todo: support getMultiLimit in transactions
   584  		if err := datastore.GetMulti(g.Context, keys, v.Interface()); err != nil {
   585  			if merr, ok := err.(appengine.MultiError); ok {
   586  				for i := 0; i < len(keys); i++ {
   587  					if merr[i] != nil && (!IgnoreFieldMismatch || !errFieldMismatch(merr[i])) {
   588  						anyErr = true // this flag tells GetMulti to return multiErr later
   589  						multiErr[i] = merr[i]
   590  					}
   591  				}
   592  			} else {
   593  				g.error(err)
   594  				anyErr = true // this flag tells GetMulti to return multiErr later
   595  				for i := 0; i < len(keys); i++ {
   596  					multiErr[i] = err
   597  				}
   598  			}
   599  			if anyErr {
   600  				return realError(multiErr)
   601  			}
   602  		}
   603  		return nil
   604  	}
   605  
   606  	var dskeys []*datastore.Key
   607  	var dsdst []interface{}
   608  	var dixs []int // dskeys[5] === keys[dixs[5]]
   609  
   610  	var mckeys []string
   611  	var mixs []int // mckeys[3] =~= keys[mixs[3]]
   612  
   613  	lckeys := make([]string, 0, len(keys))
   614  	for _, key := range keys {
   615  		// NB! Current implementation has optimizations in place
   616  		// that expect memcache & local cache keys to match.
   617  		lckeys = append(lckeys, cacheKey(key))
   618  	}
   619  
   620  	lcvalues := g.cache.GetMulti(lckeys)
   621  
   622  	for i, key := range keys {
   623  		vi := v.Index(i)
   624  		if vi.Kind() == reflect.Struct {
   625  			vi = vi.Addr()
   626  		}
   627  		d := vi.Interface()
   628  
   629  		if data := lcvalues[i]; data != nil {
   630  			// Attempt to deserialize the cached value into the struct
   631  			err := deserializeStruct(d, data)
   632  			if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) {
   633  				if err == datastore.ErrNoSuchEntity || errFieldMismatch(err) {
   634  					anyErr = true // this flag tells GetMulti to return multiErr later
   635  					multiErr[i] = err
   636  				} else {
   637  					g.error(err)
   638  					return err
   639  				}
   640  			}
   641  		} else {
   642  			mckeys = append(mckeys, lckeys[i])
   643  			mixs = append(mixs, i)
   644  			dskeys = append(dskeys, key)
   645  			dsdst = append(dsdst, d)
   646  			dixs = append(dixs, i)
   647  		}
   648  	}
   649  
   650  	if len(mckeys) == 0 {
   651  		if anyErr {
   652  			return realError(multiErr)
   653  		}
   654  		return nil
   655  	}
   656  
   657  	// memcache.GetMulti is limited to memcacheMaxRPCSize for the data returned.
   658  	// Thus if the returned data is bigger than memcacheMaxRPCSize - memcacheMaxItemSize
   659  	// then we do another memcache.GetMulti on the missing keys.
   660  	memvalues := make(map[string]*memcache.Item, len(mckeys))
   661  	mcKeysSet := make(map[string]struct{}, len(mckeys))
   662  	for _, mk := range mckeys {
   663  		mcKeysSet[mk] = struct{}{}
   664  	}
   665  	for {
   666  		nextmckeys := make([]string, 0, len(mcKeysSet))
   667  		for mk := range mcKeysSet {
   668  			nextmckeys = append(nextmckeys, mk)
   669  		}
   670  		tc, cf := context.WithTimeout(g.Context, memcacheGetTimeout(len(nextmckeys)))
   671  		mvs, err := memcache.GetMulti(tc, nextmckeys)
   672  		cf()
   673  		// timing out or another error from memcache isn't something to fail over, but do log it
   674  		if appengine.IsTimeoutError(err) {
   675  			g.timeoutError(err)
   676  			break
   677  		} else if err != nil {
   678  			g.error(err)
   679  			break
   680  		}
   681  		payloadSize := 0
   682  		for k, v := range mvs {
   683  			memvalues[k] = v
   684  			payloadSize += memcacheOverhead + len(v.Key) + len(v.Value)
   685  			delete(mcKeysSet, k)
   686  		}
   687  		if len(mcKeysSet) == 0 || payloadSize < memcacheMaxRPCSize-memcacheMaxItemSize {
   688  			break
   689  		}
   690  	}
   691  
   692  	if len(memvalues) > 0 {
   693  		// since memcache fetch was successful, reset the datastore fetch list and repopulate it
   694  		dskeys = dskeys[:0]
   695  		dsdst = dsdst[:0]
   696  		dixs = dixs[:0]
   697  		// we only want to check the returned map if there weren't any errors
   698  		// unlike the datastore, memcache will return a smaller map with no error if some of the keys were missed
   699  
   700  		for i, m := range mckeys {
   701  			d := v.Index(mixs[i]).Interface()
   702  			if v.Index(mixs[i]).Kind() == reflect.Struct {
   703  				d = v.Index(mixs[i]).Addr().Interface()
   704  			}
   705  			if s, present := memvalues[m]; present {
   706  				// Mirror any memcache entries in local cache
   707  				g.cache.Set(&cacheItem{key: m, value: s.Value})
   708  				// Attempt to deserialize the cached value into the struct
   709  				err := deserializeStruct(d, s.Value)
   710  				if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) {
   711  					if err == datastore.ErrNoSuchEntity || errFieldMismatch(err) {
   712  						anyErr = true // this flag tells GetMulti to return multiErr later
   713  						multiErr[mixs[i]] = err
   714  					} else {
   715  						g.error(err)
   716  						return err
   717  					}
   718  				}
   719  			} else {
   720  				dskeys = append(dskeys, keys[mixs[i]])
   721  				dsdst = append(dsdst, d)
   722  				dixs = append(dixs, mixs[i])
   723  			}
   724  		}
   725  		if len(dskeys) == 0 {
   726  			if anyErr {
   727  				return realError(multiErr)
   728  			}
   729  			return nil
   730  		}
   731  	}
   732  
   733  	mu := new(sync.Mutex)
   734  	goroutines := (len(dskeys)-1)/datastoreGetMultiMaxItems + 1
   735  	var wg sync.WaitGroup
   736  	wg.Add(goroutines)
   737  	for i := 0; i < goroutines; i++ {
   738  		go func(i int) {
   739  			defer wg.Done()
   740  			lo := i * datastoreGetMultiMaxItems
   741  			hi := (i + 1) * datastoreGetMultiMaxItems
   742  			if hi > len(dskeys) {
   743  				hi = len(dskeys)
   744  			}
   745  			toCache := make([]*cacheItem, 0, hi-lo)
   746  			propLists := make([]datastore.PropertyList, hi-lo)
   747  			handleProp := func(i, idx int, exists bool) {
   748  				// Serialize the properties
   749  				data, err := serializeProperties(propLists[i], exists)
   750  				if err != nil {
   751  					g.error(err)
   752  					multiErr[idx] = err
   753  					return
   754  				}
   755  				// Prepare the properties for caching
   756  				toCache = append(toCache, &cacheItem{key: lckeys[idx], value: data})
   757  				// Deserialize the properties into a struct
   758  				if exists {
   759  					err = deserializeProperties(dsdst[lo+i], propLists[i])
   760  					if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) {
   761  						multiErr[idx] = err
   762  					}
   763  				}
   764  			}
   765  			gmerr := datastore.GetMulti(g.Context, dskeys[lo:hi], propLists)
   766  			if gmerr != nil {
   767  				mu.Lock()
   768  				anyErr = true // this flag tells GetMulti to return multiErr later
   769  				mu.Unlock()
   770  				merr, ok := gmerr.(appengine.MultiError)
   771  				if !ok {
   772  					g.error(gmerr)
   773  					for j := lo; j < hi; j++ {
   774  						multiErr[j] = gmerr
   775  					}
   776  					return
   777  				}
   778  				for i, idx := range dixs[lo:hi] {
   779  					if merr[i] == nil {
   780  						handleProp(i, idx, true)
   781  					} else {
   782  						if merr[i] == datastore.ErrNoSuchEntity {
   783  							handleProp(i, idx, false)
   784  						}
   785  						multiErr[idx] = merr[i]
   786  					}
   787  				}
   788  			} else {
   789  				for i, idx := range dixs[lo:hi] {
   790  					handleProp(i, idx, true)
   791  				}
   792  			}
   793  			if len(toCache) > 0 {
   794  				// Populate memcache in a goroutine because there's network involved
   795  				// and we can be doing useful work while waiting for I/O
   796  				errc := make(chan error)
   797  				go func() {
   798  					errc <- g.putMemcache(toCache)
   799  				}()
   800  				// Populate local cache
   801  				g.cache.SetMulti(toCache)
   802  				// Wait for memcache population to finish
   803  				err := <-errc
   804  				// .. but only propagate the memcache error if configured to do so
   805  				if propagateMemcachePutError && err != nil {
   806  					mu.Lock()
   807  					extraErr = err
   808  					mu.Unlock()
   809  				}
   810  			}
   811  		}(i)
   812  	}
   813  	wg.Wait()
   814  	if anyErr {
   815  		return realError(multiErr)
   816  	} else if extraErr != nil {
   817  		return extraErr
   818  	}
   819  	return nil
   820  }
   821  
   822  // Delete deletes the provided entity.
   823  // Takes either *S or *datastore.Key.
   824  func (g *Goon) Delete(src interface{}) error {
   825  	var srcs interface{}
   826  	if key, ok := src.(*datastore.Key); ok {
   827  		srcs = []*datastore.Key{key}
   828  	} else {
   829  		v := reflect.ValueOf(src)
   830  		if v.Kind() != reflect.Ptr {
   831  			return fmt.Errorf("goon: expected pointer to a struct, got %#v", src)
   832  		}
   833  		srcs = []interface{}{src}
   834  	}
   835  	err := g.DeleteMulti(srcs)
   836  	if err != nil {
   837  		// Look for an embedded error if it's multi
   838  		if me, ok := err.(appengine.MultiError); ok {
   839  			return me[0]
   840  		}
   841  	}
   842  	return err
   843  }
   844  
   845  // DeleteMulti is a batch version of Delete.
   846  // Takes either []*S or []*datastore.Key.
   847  func (g *Goon) DeleteMulti(src interface{}) error {
   848  	keys, ok := src.([]*datastore.Key)
   849  	if !ok {
   850  		var err error
   851  		keys, err = g.extractKeys(src, false) // don't allow incomplete keys on a Delete request
   852  		if err != nil {
   853  			return err
   854  		}
   855  	}
   856  	if len(keys) == 0 {
   857  		return nil
   858  		// not an error, and it was "successful", so return nil
   859  	}
   860  
   861  	mu := new(sync.Mutex)
   862  	multiErr, any := make(appengine.MultiError, len(keys)), false
   863  	goroutines := (len(keys)-1)/datastoreDeleteMultiMaxItems + 1
   864  	var wg sync.WaitGroup
   865  	wg.Add(goroutines)
   866  	for i := 0; i < goroutines; i++ {
   867  		go func(i int) {
   868  			defer wg.Done()
   869  			lo := i * datastoreDeleteMultiMaxItems
   870  			hi := (i + 1) * datastoreDeleteMultiMaxItems
   871  			if hi > len(keys) {
   872  				hi = len(keys)
   873  			}
   874  			dmerr := datastore.DeleteMulti(g.Context, keys[lo:hi])
   875  			if dmerr != nil {
   876  				mu.Lock()
   877  				any = true // this flag tells DeleteMulti to return multiErr later
   878  				mu.Unlock()
   879  				merr, ok := dmerr.(appengine.MultiError)
   880  				if !ok {
   881  					g.error(dmerr)
   882  					for j := lo; j < hi; j++ {
   883  						multiErr[j] = dmerr
   884  					}
   885  					return
   886  				}
   887  				copy(multiErr[lo:hi], merr)
   888  			}
   889  		}(i)
   890  	}
   891  	wg.Wait()
   892  
   893  	// Caches need to be updated after the datastore to prevent a common race condition,
   894  	// where a concurrent request will fetch the not-yet-updated data from the datastore
   895  	// and populate the caches with it.
   896  	cachekeys := make([]string, 0, len(keys))
   897  	for _, key := range keys {
   898  		cachekeys = append(cachekeys, cacheKey(key))
   899  	}
   900  	if g.inTransaction {
   901  		g.txnCacheLock.Lock()
   902  		for _, ck := range cachekeys {
   903  			g.toDelete[ck] = struct{}{}
   904  			g.toDeleteMC[ck] = struct{}{}
   905  		}
   906  		g.txnCacheLock.Unlock()
   907  	} else {
   908  		g.cache.DeleteMulti(cachekeys)
   909  		g.memcacheDeleteError(memcache.DeleteMulti(g.Context, cachekeys))
   910  	}
   911  
   912  	if any {
   913  		return realError(multiErr)
   914  	}
   915  	return nil
   916  }
   917  
   918  // NotFound returns true if err is an appengine.MultiError and err[idx] is a datastore.ErrNoSuchEntity.
   919  func NotFound(err error, idx int) bool {
   920  	if merr, ok := err.(appengine.MultiError); ok {
   921  		return idx < len(merr) && merr[idx] == datastore.ErrNoSuchEntity
   922  	}
   923  	return false
   924  }
   925  
   926  // errFieldMismatch returns true if err is *datastore.ErrFieldMismatch
   927  func errFieldMismatch(err error) bool {
   928  	_, ok := err.(*datastore.ErrFieldMismatch)
   929  	return ok
   930  }
   931  
   932  // Returns a single error if each error in MultiError is the same
   933  // otherwise, returns multiError or nil (if multiError is empty)
   934  func realError(multiError appengine.MultiError) error {
   935  	if len(multiError) == 0 {
   936  		return nil
   937  	}
   938  	init := multiError[0]
   939  	// some errors are *always* returned in MultiError form from the datastore
   940  	if _, ok := init.(*datastore.ErrFieldMismatch); ok { // returned in GetMulti
   941  		return multiError
   942  	}
   943  	if init == datastore.ErrInvalidEntityType || // returned in GetMulti
   944  		init == datastore.ErrNoSuchEntity { // returned in GetMulti
   945  		return multiError
   946  	}
   947  	// check if all errors are the same
   948  	for i := 1; i < len(multiError); i++ {
   949  		// since type error could hold structs, pointers, etc,
   950  		// the only way to compare non-nil errors is by their string output
   951  		if init == nil || multiError[i] == nil {
   952  			if init != multiError[i] {
   953  				return multiError
   954  			}
   955  		} else if init.Error() != multiError[i].Error() {
   956  			return multiError
   957  		}
   958  	}
   959  	// datastore.ErrInvalidKey is returned as a single error in PutMulti
   960  	return init
   961  }