func (g *Goon) putMemcache(srcs []interface{}, exists []byte) error { items := make([]*memcache.Item, len(srcs)) payloadSize := 0 for i, src := range srcs { toSerialize := src if exists[i] == 0 { toSerialize = nil } data, err := serializeStruct(toSerialize) if err != nil { g.error(err) return err } key, _, err := g.getStructKey(src) if err != nil { return err } // payloadSize will overflow if we push 2+ gigs on a 32bit machine payloadSize += len(data) items[i] = &memcache.Item{ Key: memkey(key), Value: data, } } memcacheTimeout := MemcachePutTimeoutSmall if payloadSize >= MemcachePutTimeoutThreshold { memcacheTimeout = MemcachePutTimeoutLarge } errc := make(chan error) go func() { errc <- memcache.SetMulti(appengine.Timeout(g.Context, memcacheTimeout), items) }() g.putMemoryMulti(srcs, exists) err := <-errc if appengine.IsTimeoutError(err) { g.timeoutError(err) err = nil } else if err != nil { g.error(err) } return err }
// GetMulti is a batch version of Get. // // dst must be a *[]S, *[]*S, *[]I, []S, []*S, or []I, for some struct type S, // or some interface type I. If *[]I or []I, each element must be a struct pointer. func (g *Goon) GetMulti(dst interface{}) error { keys, err := g.extractKeys(dst, false) // don't allow incomplete keys on a Get request if err != nil { return err } v := reflect.Indirect(reflect.ValueOf(dst)) if g.inTransaction { // todo: support getMultiLimit in transactions return datastore.GetMulti(g.Context, keys, v.Interface()) } var dskeys []*datastore.Key var dsdst []interface{} var dixs []int var memkeys []string var mixs []int g.cacheLock.RLock() for i, key := range keys { m := memkey(key) vi := v.Index(i) if vi.Kind() == reflect.Struct { vi = vi.Addr() } if s, present := g.cache[m]; present { if vi.Kind() == reflect.Interface { vi = vi.Elem() } reflect.Indirect(vi).Set(reflect.Indirect(reflect.ValueOf(s))) } else { memkeys = append(memkeys, m) mixs = append(mixs, i) dskeys = append(dskeys, key) dsdst = append(dsdst, vi.Interface()) dixs = append(dixs, i) } } g.cacheLock.RUnlock() if len(memkeys) == 0 { return nil } multiErr, any := make(appengine.MultiError, len(keys)), false memvalues, err := memcache.GetMulti(appengine.Timeout(g.Context, MemcacheGetTimeout), memkeys) if appengine.IsTimeoutError(err) { g.timeoutError(err) err = nil } else if err != nil { g.error(err) // timing out or another error from memcache isn't something to fail over, but do log it // No memvalues found, prepare the datastore fetch list already prepared above } else if len(memvalues) > 0 { // since memcache fetch was successful, reset the datastore fetch list and repopulate it dskeys = dskeys[:0] dsdst = dsdst[:0] dixs = dixs[:0] // we only want to check the returned map if there weren't any errors // unlike the datastore, memcache will return a smaller map with no error if some of the keys were missed for i, m := range memkeys { d := v.Index(mixs[i]).Interface() if v.Index(mixs[i]).Kind() == reflect.Struct { d = v.Index(mixs[i]).Addr().Interface() } if s, present := memvalues[m]; present { err := deserializeStruct(d, s.Value) if err == datastore.ErrNoSuchEntity { any = true // this flag tells GetMulti to return multiErr later multiErr[mixs[i]] = err } else if err != nil { g.error(err) return err } else { g.putMemory(d) } } else { dskeys = append(dskeys, keys[mixs[i]]) dsdst = append(dsdst, d) dixs = append(dixs, mixs[i]) } } if len(dskeys) == 0 { if any { return realError(multiErr) } return nil } } goroutines := (len(dskeys)-1)/getMultiLimit + 1 var wg sync.WaitGroup wg.Add(goroutines) for i := 0; i < goroutines; i++ { go func(i int) { defer wg.Done() var toCache []interface{} var exists []byte lo := i * getMultiLimit hi := (i + 1) * getMultiLimit if hi > len(dskeys) { hi = len(dskeys) } gmerr := datastore.GetMulti(g.Context, dskeys[lo:hi], dsdst[lo:hi]) if gmerr != nil { any = true // this flag tells GetMulti to return multiErr later merr, ok := gmerr.(appengine.MultiError) if !ok { g.error(gmerr) for j := lo; j < hi; j++ { multiErr[j] = gmerr } return } for i, idx := range dixs[lo:hi] { if merr[i] == nil { toCache = append(toCache, dsdst[lo+i]) exists = append(exists, 1) } else { if merr[i] == datastore.ErrNoSuchEntity { toCache = append(toCache, dsdst[lo+i]) exists = append(exists, 0) } multiErr[idx] = merr[i] } } } else { toCache = append(toCache, dsdst[lo:hi]...) exists = append(exists, bytes.Repeat([]byte{1}, hi-lo)...) } if len(toCache) > 0 { if err := g.putMemcache(toCache, exists); err != nil { g.error(err) // since putMemcache() gives no guarantee it will actually store the data in memcache // we log and swallow this error } } }(i) } wg.Wait() if any { return realError(multiErr) } return nil }