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 }