go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/service/memcache/interface.go (about)

     1  // Copyright 2015 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package memcache
    16  
    17  import (
    18  	"context"
    19  
    20  	"go.chromium.org/luci/common/errors"
    21  )
    22  
    23  func filterItems(lme errors.LazyMultiError, items []Item, nilErr error) ([]Item, []int) {
    24  	idxMap := make([]int, 0, len(items))
    25  	retItems := make([]Item, 0, len(items))
    26  	for i, itm := range items {
    27  		if itm != nil {
    28  			idxMap = append(idxMap, i)
    29  			retItems = append(retItems, itm)
    30  		} else {
    31  			lme.Assign(i, nilErr)
    32  		}
    33  	}
    34  	return retItems, idxMap
    35  }
    36  
    37  func multiCall(items []Item, nilErr error, inner func(items []Item, cb RawCB) error) error {
    38  	lme := errors.NewLazyMultiError(len(items))
    39  	realItems, idxMap := filterItems(lme, items, nilErr)
    40  	j := 0
    41  	err := inner(realItems, func(err error) {
    42  		lme.Assign(idxMap[j], err)
    43  		j++
    44  	})
    45  	if err == nil {
    46  		err = lme.Get()
    47  		if len(items) == 1 {
    48  			err = errors.SingleError(err)
    49  		}
    50  	}
    51  	return err
    52  }
    53  
    54  // NewItem creates a new, mutable, memcache item.
    55  func NewItem(c context.Context, key string) Item {
    56  	return Raw(c).NewItem(key)
    57  }
    58  
    59  // Add writes items to memcache iff they don't already exist.
    60  //
    61  // If only one item is provided its error will be returned directly. If more
    62  // than one item is provided, an errors.MultiError will be returned in the
    63  // event of an error, with a given error index corresponding to the error
    64  // encountered when processing the item at that index.
    65  func Add(c context.Context, items ...Item) error {
    66  	return multiCall(items, ErrNotStored, Raw(c).AddMulti)
    67  }
    68  
    69  // Set writes items into memcache unconditionally.
    70  //
    71  // If only one item is provided its error will be returned directly. If more
    72  // than one item is provided, an errors.MultiError will be returned in the
    73  // event of an error, with a given error index corresponding to the error
    74  // encountered when processing the item at that index.
    75  func Set(c context.Context, items ...Item) error {
    76  	return multiCall(items, ErrNotStored, Raw(c).SetMulti)
    77  }
    78  
    79  func getMultiImpl(raw RawInterface, items []Item) error {
    80  	lme := errors.NewLazyMultiError(len(items))
    81  	realItems, idxMap := filterItems(lme, items, ErrCacheMiss)
    82  	if len(realItems) == 0 {
    83  		return lme.Get()
    84  	}
    85  
    86  	keys := make([]string, len(realItems))
    87  	for i, itm := range realItems {
    88  		keys[i] = itm.Key()
    89  	}
    90  
    91  	j := 0
    92  	err := raw.GetMulti(keys, func(item Item, err error) {
    93  		i := idxMap[j]
    94  		if !lme.Assign(i, err) {
    95  			items[i].SetAll(item)
    96  		}
    97  		j++
    98  	})
    99  	if err == nil {
   100  		err = lme.Get()
   101  		if len(items) == 1 {
   102  			err = errors.SingleError(err)
   103  		}
   104  	}
   105  	return err
   106  }
   107  
   108  // Get retrieves items from memcache.
   109  func Get(c context.Context, items ...Item) error {
   110  	return getMultiImpl(Raw(c), items)
   111  }
   112  
   113  // GetKey is a convenience method for generating and retrieving an Item instance
   114  // for the specified from memcache key.
   115  //
   116  // On a cache miss ErrCacheMiss will be returned. Item will always be
   117  // returned, even on a miss, but it's value may be empty if it was a miss.
   118  func GetKey(c context.Context, key string) (Item, error) {
   119  	raw := Raw(c)
   120  	ret := raw.NewItem(key)
   121  	err := getMultiImpl(raw, []Item{ret})
   122  	return ret, err
   123  }
   124  
   125  // Delete deletes items from memcache.
   126  //
   127  // If only one item is provided its error will be returned directly. If more
   128  // than one item is provided, an errors.MultiError will be returned in the
   129  // event of an error, with a given error index corresponding to the error
   130  // encountered when processing the item at that index.
   131  func Delete(c context.Context, keys ...string) error {
   132  	lme := errors.NewLazyMultiError(len(keys))
   133  	i := 0
   134  	err := Raw(c).DeleteMulti(keys, func(err error) {
   135  		lme.Assign(i, err)
   136  		i++
   137  	})
   138  	if err == nil {
   139  		err = lme.Get()
   140  		if len(keys) == 1 {
   141  			err = errors.SingleError(err)
   142  		}
   143  	}
   144  	return err
   145  }
   146  
   147  // CompareAndSwap writes the given item that was previously returned by Get, if
   148  // the value was neither modified or evicted between the Get and the
   149  // CompareAndSwap calls.
   150  //
   151  // Example:
   152  //
   153  //	itm := memcache.NewItem(context, "aKey")
   154  //	memcache.Get(context, itm) // check error
   155  //	itm.SetValue(append(itm.Value(), []byte("more bytes")))
   156  //	memcache.CompareAndSwap(context, itm) // check error
   157  //
   158  // If only one item is provided its error will be returned directly. If more
   159  // than one item is provided, an errors.MultiError will be returned in the
   160  // event of an error, with a given error index corresponding to the error
   161  // encountered when processing the item at that index.
   162  func CompareAndSwap(c context.Context, items ...Item) error {
   163  	return multiCall(items, ErrNotStored, Raw(c).CompareAndSwapMulti)
   164  }
   165  
   166  // Increment adds delta to the uint64 contained at key. If the memcache key
   167  // is missing, it's populated with initialValue before applying delta (i.e.
   168  // the final value would be initialValue+delta).
   169  //
   170  // Underflow caps at 0, overflow wraps back to 0.
   171  //
   172  // The value is stored as little-endian uint64, see
   173  // "encoding/binary".LittleEndian. If the value is not exactly 8 bytes,
   174  // it's assumed to contain non-number data and this method will return an
   175  // error.
   176  func Increment(c context.Context, key string, delta int64, initialValue uint64) (uint64, error) {
   177  	return Raw(c).Increment(key, delta, &initialValue)
   178  }
   179  
   180  // IncrementExisting is like Increment, except that the value must exist
   181  // already.
   182  func IncrementExisting(c context.Context, key string, delta int64) (uint64, error) {
   183  	return Raw(c).Increment(key, delta, nil)
   184  }
   185  
   186  // Flush dumps the entire memcache state.
   187  func Flush(c context.Context) error {
   188  	return Raw(c).Flush()
   189  }
   190  
   191  // Stats gets some best-effort statistics about the current state of memcache.
   192  func Stats(c context.Context) (*Statistics, error) {
   193  	return Raw(c).Stats()
   194  }