| Index: impl/prod/memcache.go
 | 
| diff --git a/impl/prod/memcache.go b/impl/prod/memcache.go
 | 
| index 5b7b4c456b01c0ed44c33757b86fcd0818ec9669..cc4a3d1824008de3623416f7e556257d06d3ecf9 100644
 | 
| --- a/impl/prod/memcache.go
 | 
| +++ b/impl/prod/memcache.go
 | 
| @@ -8,15 +8,15 @@ import (
 | 
|  	"time"
 | 
|  
 | 
|  	mc "github.com/luci/gae/service/memcache"
 | 
| -	"github.com/luci/luci-go/common/errors"
 | 
|  	"golang.org/x/net/context"
 | 
| +	"google.golang.org/appengine"
 | 
|  	"google.golang.org/appengine/memcache"
 | 
|  )
 | 
|  
 | 
|  // useMC adds a gae.Memcache implementation to context, accessible
 | 
|  // by gae.GetMC(c)
 | 
|  func useMC(c context.Context) context.Context {
 | 
| -	return mc.SetFactory(c, func(ci context.Context) mc.Interface {
 | 
| +	return mc.SetRawFactory(c, func(ci context.Context) mc.RawInterface {
 | 
|  		return mcImpl{ci}
 | 
|  	})
 | 
|  }
 | 
| @@ -31,7 +31,6 @@ var _ mc.Item = mcItem{}
 | 
|  
 | 
|  func (i mcItem) Key() string               { return i.i.Key }
 | 
|  func (i mcItem) Value() []byte             { return i.i.Value }
 | 
| -func (i mcItem) Object() interface{}       { return i.i.Object }
 | 
|  func (i mcItem) Flags() uint32             { return i.i.Flags }
 | 
|  func (i mcItem) Expiration() time.Duration { return i.i.Expiration }
 | 
|  
 | 
| @@ -43,10 +42,6 @@ func (i mcItem) SetValue(v []byte) mc.Item {
 | 
|  	i.i.Value = v
 | 
|  	return i
 | 
|  }
 | 
| -func (i mcItem) SetObject(o interface{}) mc.Item {
 | 
| -	i.i.Object = o
 | 
| -	return i
 | 
| -}
 | 
|  func (i mcItem) SetFlags(f uint32) mc.Item {
 | 
|  	i.i.Flags = f
 | 
|  	return i
 | 
| @@ -56,6 +51,14 @@ func (i mcItem) SetExpiration(d time.Duration) mc.Item {
 | 
|  	return i
 | 
|  }
 | 
|  
 | 
| +func (i mcItem) SetAll(other mc.Item) {
 | 
| +	o := other.(mcItem)
 | 
| +	*i.i = *o.i
 | 
| +	val := i.i.Value
 | 
| +	i.i.Value = make([]byte, len(val))
 | 
| +	copy(i.i.Value, val)
 | 
| +}
 | 
| +
 | 
|  // mcR2FErr (MC real-to-fake w/ error) converts a *memcache.Item to a mc.Item,
 | 
|  // and passes along an error.
 | 
|  func mcR2FErr(i *memcache.Item, err error) (mc.Item, error) {
 | 
| @@ -91,62 +94,60 @@ func (m mcImpl) NewItem(key string) mc.Item {
 | 
|  	return mcItem{&memcache.Item{Key: key}}
 | 
|  }
 | 
|  
 | 
| -//////// MCSingleReadWriter
 | 
| -func (m mcImpl) Add(item mc.Item) error {
 | 
| -	return memcache.Add(m.Context, mcF2R(item))
 | 
| -}
 | 
| -func (m mcImpl) Set(item mc.Item) error {
 | 
| -	return memcache.Set(m.Context, mcF2R(item))
 | 
| -}
 | 
| -func (m mcImpl) Delete(key string) error {
 | 
| -	return memcache.Delete(m.Context, key)
 | 
| -}
 | 
| -func (m mcImpl) Get(key string) (mc.Item, error) {
 | 
| -	return mcR2FErr(memcache.Get(m.Context, key))
 | 
| -}
 | 
| -func (m mcImpl) CompareAndSwap(item mc.Item) error {
 | 
| -	return memcache.CompareAndSwap(m.Context, mcF2R(item))
 | 
| +func doCB(err error, cb mc.RawCB) error {
 | 
| +	if me, ok := err.(appengine.MultiError); ok {
 | 
| +		for _, err := range me {
 | 
| +			cb(err)
 | 
| +		}
 | 
| +		err = nil
 | 
| +	}
 | 
| +	return err
 | 
|  }
 | 
|  
 | 
| -//////// MCMultiReadWriter
 | 
| -func (m mcImpl) DeleteMulti(keys []string) error {
 | 
| -	return errors.Fix(memcache.DeleteMulti(m.Context, keys))
 | 
| +func (m mcImpl) DeleteMulti(keys []string, cb mc.RawCB) error {
 | 
| +	return doCB(memcache.DeleteMulti(m.Context, keys), cb)
 | 
|  }
 | 
| -func (m mcImpl) AddMulti(items []mc.Item) error {
 | 
| -	return errors.Fix(memcache.AddMulti(m.Context, mcMF2R(items)))
 | 
| +
 | 
| +func (m mcImpl) AddMulti(items []mc.Item, cb mc.RawCB) error {
 | 
| +	return doCB(memcache.AddMulti(m.Context, mcMF2R(items)), cb)
 | 
|  }
 | 
| -func (m mcImpl) SetMulti(items []mc.Item) error {
 | 
| -	return errors.Fix(memcache.SetMulti(m.Context, mcMF2R(items)))
 | 
| +
 | 
| +func (m mcImpl) SetMulti(items []mc.Item, cb mc.RawCB) error {
 | 
| +	return doCB(memcache.SetMulti(m.Context, mcMF2R(items)), cb)
 | 
|  }
 | 
| -func (m mcImpl) GetMulti(keys []string) (map[string]mc.Item, error) {
 | 
| +
 | 
| +func (m mcImpl) GetMulti(keys []string, cb mc.RawItemCB) error {
 | 
|  	realItems, err := memcache.GetMulti(m.Context, keys)
 | 
|  	if err != nil {
 | 
| -		return nil, errors.Fix(err)
 | 
| +		return err
 | 
|  	}
 | 
| -	items := make(map[string]mc.Item, len(realItems))
 | 
| -	for k, itm := range realItems {
 | 
| -		items[k] = mcItem{itm}
 | 
| +	for _, k := range keys {
 | 
| +		itm := realItems[k]
 | 
| +		if itm == nil {
 | 
| +			cb(nil, memcache.ErrCacheMiss)
 | 
| +		} else {
 | 
| +			cb(mcItem{itm}, nil)
 | 
| +		}
 | 
|  	}
 | 
| -	return items, err
 | 
| -}
 | 
| -func (m mcImpl) CompareAndSwapMulti(items []mc.Item) error {
 | 
| -	return errors.Fix(memcache.CompareAndSwapMulti(m.Context, mcMF2R(items)))
 | 
| +	return nil
 | 
|  }
 | 
|  
 | 
| -//////// MCIncrementer
 | 
| -func (m mcImpl) Increment(key string, delta int64, initialValue uint64) (uint64, error) {
 | 
| -	return memcache.Increment(m.Context, key, delta, initialValue)
 | 
| +func (m mcImpl) CompareAndSwapMulti(items []mc.Item, cb mc.RawCB) error {
 | 
| +	return doCB(memcache.CompareAndSwapMulti(m.Context, mcMF2R(items)), cb)
 | 
|  }
 | 
| -func (m mcImpl) IncrementExisting(key string, delta int64) (uint64, error) {
 | 
| -	return memcache.IncrementExisting(m.Context, key, delta)
 | 
| +
 | 
| +func (m mcImpl) Increment(key string, delta int64, initialValue *uint64) (uint64, error) {
 | 
| +	if initialValue == nil {
 | 
| +		return memcache.IncrementExisting(m.Context, key, delta)
 | 
| +	} else {
 | 
| +		return memcache.Increment(m.Context, key, delta, *initialValue)
 | 
| +	}
 | 
|  }
 | 
|  
 | 
| -//////// MCFlusher
 | 
|  func (m mcImpl) Flush() error {
 | 
|  	return memcache.Flush(m.Context)
 | 
|  }
 | 
|  
 | 
| -//////// MCStatter
 | 
|  func (m mcImpl) Stats() (*mc.Statistics, error) {
 | 
|  	stats, err := memcache.Stats(m.Context)
 | 
|  	if err != nil {
 | 
| 
 |