| Index: service/memcache/interface.go
 | 
| diff --git a/service/memcache/interface.go b/service/memcache/interface.go
 | 
| index 6d4d33b2d880660f5b499adce34836c6d57bb7e1..5167667587da615f095f0827977a3dd127969776 100644
 | 
| --- a/service/memcache/interface.go
 | 
| +++ b/service/memcache/interface.go
 | 
| @@ -4,72 +4,177 @@
 | 
|  
 | 
|  package memcache
 | 
|  
 | 
| -// Interface is the full interface to the memcache service.
 | 
| +import (
 | 
| +	"github.com/luci/luci-go/common/errors"
 | 
| +	"golang.org/x/net/context"
 | 
| +)
 | 
| +
 | 
| +func filterItems(lme errors.LazyMultiError, items []Item, nilErr error) ([]Item, []int) {
 | 
| +	idxMap := make([]int, 0, len(items))
 | 
| +	retItems := make([]Item, 0, len(items))
 | 
| +	for i, itm := range items {
 | 
| +		if itm != nil {
 | 
| +			idxMap = append(idxMap, i)
 | 
| +			retItems = append(retItems, itm)
 | 
| +		} else {
 | 
| +			lme.Assign(i, nilErr)
 | 
| +		}
 | 
| +	}
 | 
| +	return retItems, idxMap
 | 
| +}
 | 
| +
 | 
| +func multiCall(items []Item, nilErr error, inner func(items []Item, cb RawCB) error) error {
 | 
| +	lme := errors.NewLazyMultiError(len(items))
 | 
| +	realItems, idxMap := filterItems(lme, items, nilErr)
 | 
| +	j := 0
 | 
| +	err := inner(realItems, func(err error) {
 | 
| +		lme.Assign(idxMap[j], err)
 | 
| +		j++
 | 
| +	})
 | 
| +	if err == nil {
 | 
| +		err = lme.Get()
 | 
| +		if len(items) == 1 {
 | 
| +			err = errors.SingleError(err)
 | 
| +		}
 | 
| +	}
 | 
| +	return err
 | 
| +}
 | 
| +
 | 
| +// NewItem creates a new, mutable, memcache item.
 | 
| +func NewItem(c context.Context, key string) Item {
 | 
| +	return Raw(c).NewItem(key)
 | 
| +}
 | 
| +
 | 
| +// Add writes items to memcache iff they don't already exist.
 | 
| +//
 | 
| +// If only one item is provided its error will be returned directly. If more
 | 
| +// than one item is provided, an errors.MultiError will be returned in the
 | 
| +// event of an error, with a given error index corresponding to the error
 | 
| +// encountered when processing the item at that index.
 | 
| +func Add(c context.Context, items ...Item) error {
 | 
| +	return multiCall(items, ErrNotStored, Raw(c).AddMulti)
 | 
| +}
 | 
| +
 | 
| +// Set writes items into memcache unconditionally.
 | 
| +//
 | 
| +// If only one item is provided its error will be returned directly. If more
 | 
| +// than one item is provided, an errors.MultiError will be returned in the
 | 
| +// event of an error, with a given error index corresponding to the error
 | 
| +// encountered when processing the item at that index.
 | 
| +func Set(c context.Context, items ...Item) error {
 | 
| +	return multiCall(items, ErrNotStored, Raw(c).SetMulti)
 | 
| +}
 | 
| +
 | 
| +func getMultiImpl(raw RawInterface, items []Item) error {
 | 
| +	lme := errors.NewLazyMultiError(len(items))
 | 
| +	realItems, idxMap := filterItems(lme, items, ErrCacheMiss)
 | 
| +	if len(realItems) == 0 {
 | 
| +		return lme.Get()
 | 
| +	}
 | 
| +
 | 
| +	keys := make([]string, len(realItems))
 | 
| +	for i, itm := range realItems {
 | 
| +		keys[i] = itm.Key()
 | 
| +	}
 | 
| +
 | 
| +	j := 0
 | 
| +	err := raw.GetMulti(keys, func(item Item, err error) {
 | 
| +		i := idxMap[j]
 | 
| +		if !lme.Assign(i, err) {
 | 
| +			items[i].SetAll(item)
 | 
| +		}
 | 
| +		j++
 | 
| +	})
 | 
| +	if err == nil {
 | 
| +		err = lme.Get()
 | 
| +		if len(items) == 1 {
 | 
| +			err = errors.SingleError(err)
 | 
| +		}
 | 
| +	}
 | 
| +	return err
 | 
| +}
 | 
| +
 | 
| +// Get retrieves items from memcache.
 | 
| +func Get(c context.Context, items ...Item) error {
 | 
| +	return getMultiImpl(Raw(c), items)
 | 
| +}
 | 
| +
 | 
| +// GetKey is a convenience method for generating and retrieving an Item instance
 | 
| +// for the specified from memcache key.
 | 
| +//
 | 
| +// On a cache miss ErrCacheMiss will be returned. Item will always be
 | 
| +// returned, even on a miss, but it's value may be empty if it was a miss.
 | 
| +func GetKey(c context.Context, key string) (Item, error) {
 | 
| +	raw := Raw(c)
 | 
| +	ret := raw.NewItem(key)
 | 
| +	err := getMultiImpl(raw, []Item{ret})
 | 
| +	return ret, err
 | 
| +}
 | 
| +
 | 
| +// Delete deletes items from memcache.
 | 
| +//
 | 
| +// If only one item is provided its error will be returned directly. If more
 | 
| +// than one item is provided, an errors.MultiError will be returned in the
 | 
| +// event of an error, with a given error index corresponding to the error
 | 
| +// encountered when processing the item at that index.
 | 
| +func Delete(c context.Context, keys ...string) error {
 | 
| +	lme := errors.NewLazyMultiError(len(keys))
 | 
| +	i := 0
 | 
| +	err := Raw(c).DeleteMulti(keys, func(err error) {
 | 
| +		lme.Assign(i, err)
 | 
| +		i++
 | 
| +	})
 | 
| +	if err == nil {
 | 
| +		err = lme.Get()
 | 
| +		if len(keys) == 1 {
 | 
| +			err = errors.SingleError(err)
 | 
| +		}
 | 
| +	}
 | 
| +	return err
 | 
| +}
 | 
| +
 | 
| +// CompareAndSwap writes the given item that was previously returned by Get, if
 | 
| +// the value was neither modified or evicted between the Get and the
 | 
| +// CompareAndSwap calls.
 | 
| +//
 | 
| +// Example:
 | 
| +//   itm := memcache.NewItem(context, "aKey")
 | 
| +//   memcache.Get(context, itm) // check error
 | 
| +//   itm.SetValue(append(itm.Value(), []byte("more bytes")))
 | 
| +//   memcache.CompareAndSwap(context, itm) // check error
 | 
| +//
 | 
| +// If only one item is provided its error will be returned directly. If more
 | 
| +// than one item is provided, an errors.MultiError will be returned in the
 | 
| +// event of an error, with a given error index corresponding to the error
 | 
| +// encountered when processing the item at that index.
 | 
| +func CompareAndSwap(c context.Context, items ...Item) error {
 | 
| +	return multiCall(items, ErrNotStored, Raw(c).CompareAndSwapMulti)
 | 
| +}
 | 
| +
 | 
| +// Increment adds delta to the uint64 contained at key. If the memcache key
 | 
| +// is missing, it's populated with initialValue before applying delta (i.e.
 | 
| +// the final value would be initialValue+delta).
 | 
| +//
 | 
| +// Underflow caps at 0, overflow wraps back to 0.
 | 
|  //
 | 
| -// The *Multi methods may return a "github.com/luci/luci-go/common/errors".MultiError
 | 
| -// if the rpc to the server was successful, but, e.g. some of the items were
 | 
| -// missing. They may also return a regular error, if, for example, the rpc
 | 
| -// failed outright.
 | 
| -type Interface interface {
 | 
| -	// NewItem creates a new, mutable, memcache item.
 | 
| -	NewItem(key string) Item
 | 
| -
 | 
| -	// Add puts a single item into memcache, but only if it didn't exist in
 | 
| -	// memcache before.
 | 
| -	Add(item Item) error
 | 
| -
 | 
| -	// Set the item in memcache, whether or not it exists.
 | 
| -	Set(item Item) error
 | 
| -
 | 
| -	// Get retrieves an item from memcache.
 | 
| -	//
 | 
| -	// On a cache miss ErrCacheMiss will be returned. Item will always be
 | 
| -	// returned, even on a miss, but it's value may be empty if it was a miss.
 | 
| -	Get(key string) (Item, error)
 | 
| -
 | 
| -	// Delete removes an item from memcache.
 | 
| -	Delete(key string) error
 | 
| -
 | 
| -	// CompareAndSwap accepts an item which is the result of Get() or GetMulti().
 | 
| -	// The Get functions add a secret field to item ('CasID'), which is used as
 | 
| -	// the "compare" value for the "CompareAndSwap". The actual "Value" field of
 | 
| -	// the object set by the Get functions is the "swap" value.
 | 
| -	//
 | 
| -	// Example:
 | 
| -	//   mc := memcache.Get(context)
 | 
| -	//   itm := mc.NewItem("aKey")
 | 
| -	//   mc.Get(itm) // check error
 | 
| -	//   itm.SetValue(append(itm.Value(), []byte("more bytes")))
 | 
| -	//   mc.CompareAndSwap(itm) // check error
 | 
| -	CompareAndSwap(item Item) error
 | 
| -
 | 
| -	// Batch operations; GetMulti takes a []Item instead of []string to improve
 | 
| -	// ergonomics when streamlining these operations.
 | 
| -	AddMulti(items []Item) error
 | 
| -	SetMulti(items []Item) error
 | 
| -	GetMulti(items []Item) error
 | 
| -	DeleteMulti(keys []string) error
 | 
| -	CompareAndSwapMulti(items []Item) error
 | 
| -
 | 
| -	// Increment adds delta to the uint64 contained at key. If the memcache key
 | 
| -	// is missing, it's populated with initialValue before applying delta (i.e.
 | 
| -	// the final value would be initialValue+delta).
 | 
| -	//
 | 
| -	// Underflow caps at 0, overflow wraps back to 0.
 | 
| -	//
 | 
| -	// If key contains a value which is not exactly 8 bytes, it's assumed to
 | 
| -	// contain non-number data and this method will return an error.
 | 
| -	Increment(key string, delta int64, initialValue uint64) (newValue uint64, err error)
 | 
| -
 | 
| -	// IncrementExisting is like Increment, except that the valu must exist
 | 
| -	// already.
 | 
| -	IncrementExisting(key string, delta int64) (newValue uint64, err error)
 | 
| -
 | 
| -	// Flush dumps the entire memcache state.
 | 
| -	Flush() error
 | 
| -
 | 
| -	// Stats gets some best-effort statistics about the current state of memcache.
 | 
| -	Stats() (*Statistics, error)
 | 
| -
 | 
| -	Raw() RawInterface
 | 
| +// If key contains a value which is not exactly 8 bytes, it's assumed to
 | 
| +// contain non-number data and this method will return an error.
 | 
| +func Increment(c context.Context, key string, delta int64, initialValue uint64) (uint64, error) {
 | 
| +	return Raw(c).Increment(key, delta, &initialValue)
 | 
| +}
 | 
| +
 | 
| +// IncrementExisting is like Increment, except that the valu must exist
 | 
| +// already.
 | 
| +func IncrementExisting(c context.Context, key string, delta int64) (uint64, error) {
 | 
| +	return Raw(c).Increment(key, delta, nil)
 | 
| +}
 | 
| +
 | 
| +// Flush dumps the entire memcache state.
 | 
| +func Flush(c context.Context) error {
 | 
| +	return Raw(c).Flush()
 | 
| +}
 | 
| +
 | 
| +// Stats gets some best-effort statistics about the current state of memcache.
 | 
| +func Stats(c context.Context) (*Statistics, error) {
 | 
| +	return Raw(c).Stats()
 | 
|  }
 | 
| 
 |