go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/impl/memory/memcache.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 memory 16 17 import ( 18 "context" 19 "encoding/binary" 20 "sync" 21 "time" 22 23 "go.chromium.org/luci/common/clock" 24 "go.chromium.org/luci/common/errors" 25 26 "go.chromium.org/luci/gae/service/info" 27 mc "go.chromium.org/luci/gae/service/memcache" 28 ) 29 30 type mcItem struct { 31 key string 32 value []byte 33 flags uint32 34 expiration time.Duration 35 36 CasID uint64 37 } 38 39 var _ mc.Item = (*mcItem)(nil) 40 41 func (m *mcItem) Key() string { return m.key } 42 func (m *mcItem) Value() []byte { return m.value } 43 func (m *mcItem) Flags() uint32 { return m.flags } 44 func (m *mcItem) Expiration() time.Duration { return m.expiration } 45 46 func (m *mcItem) SetKey(key string) mc.Item { 47 m.key = key 48 return m 49 } 50 func (m *mcItem) SetValue(val []byte) mc.Item { 51 m.value = val 52 return m 53 } 54 func (m *mcItem) SetFlags(flg uint32) mc.Item { 55 m.flags = flg 56 return m 57 } 58 func (m *mcItem) SetExpiration(exp time.Duration) mc.Item { 59 m.expiration = exp 60 return m 61 } 62 63 func (m *mcItem) SetAll(other mc.Item) { 64 if other == nil { 65 *m = mcItem{key: m.key} 66 } else { 67 k := m.key 68 *m = *other.(*mcItem) 69 m.key = k 70 } 71 } 72 73 type mcDataItem struct { 74 value []byte 75 flags uint32 76 expiration time.Time 77 casID uint64 78 } 79 80 func (m *mcDataItem) toUserItem(key string) *mcItem { 81 value := make([]byte, len(m.value)) 82 copy(value, m.value) 83 // Expiration is defined to be 0 when retrieving items from memcache. 84 // https://cloud.google.com/appengine/docs/go/memcache/reference#Item 85 // ¯\_(ツ)_/¯ 86 return &mcItem{key, value, m.flags, 0, m.casID} 87 } 88 89 type memcacheData struct { 90 lock sync.Mutex 91 items map[string]*mcDataItem 92 casID uint64 93 94 stats mc.Statistics 95 } 96 97 func (m *memcacheData) mkDataItemLocked(now time.Time, i mc.Item) (ret *mcDataItem) { 98 m.casID++ 99 100 exp := time.Time{} 101 if i.Expiration() != 0 { 102 exp = now.Add(i.Expiration()).Truncate(time.Second) 103 } 104 value := make([]byte, len(i.Value())) 105 copy(value, i.Value()) 106 return &mcDataItem{ 107 flags: i.Flags(), 108 expiration: exp, 109 value: value, 110 casID: m.casID, 111 } 112 } 113 114 func (m *memcacheData) setItemLocked(now time.Time, i mc.Item) { 115 if cur, ok := m.items[i.Key()]; ok { 116 m.stats.Items-- 117 m.stats.Bytes -= uint64(len(cur.value)) 118 } 119 m.stats.Items++ 120 m.stats.Bytes += uint64(len(i.Value())) 121 m.items[i.Key()] = m.mkDataItemLocked(now, i) 122 } 123 124 func (m *memcacheData) delItemLocked(k string) { 125 if itm, ok := m.items[k]; ok { 126 m.stats.Items-- 127 m.stats.Bytes -= uint64(len(itm.value)) 128 delete(m.items, k) 129 } 130 } 131 132 func (m *memcacheData) reset() { 133 m.stats = mc.Statistics{} 134 m.items = map[string]*mcDataItem{} 135 } 136 137 func (m *memcacheData) hasItemLocked(now time.Time, key string) bool { 138 ret, ok := m.items[key] 139 if ok && !ret.expiration.IsZero() && ret.expiration.Before(now) { 140 m.delItemLocked(key) 141 return false 142 } 143 return ok 144 } 145 146 func (m *memcacheData) retrieveLocked(now time.Time, key string) (*mcDataItem, error) { 147 if !m.hasItemLocked(now, key) { 148 m.stats.Misses++ 149 return nil, mc.ErrCacheMiss 150 } 151 152 ret := m.items[key] 153 m.stats.Hits++ 154 m.stats.ByteHits += uint64(len(ret.value)) 155 return ret, nil 156 } 157 158 // memcacheImpl binds the current connection's memcache data to an 159 // implementation of {gae.Memcache, gae.Testable}. 160 type memcacheImpl struct { 161 data *memcacheData 162 ctx context.Context 163 } 164 165 var _ mc.RawInterface = (*memcacheImpl)(nil) 166 167 // useMC adds a gae.Memcache implementation to context, accessible 168 // by gae.GetMC(c) 169 func useMC(c context.Context) context.Context { 170 lck := sync.Mutex{} 171 // TODO(riannucci): just use namespace for automatic key prefixing. Flush 172 // actually wipes the ENTIRE memcache, regardless of namespace. 173 mcdMap := map[string]*memcacheData{} 174 175 return mc.SetRawFactory(c, func(ic context.Context) mc.RawInterface { 176 lck.Lock() 177 defer lck.Unlock() 178 179 ns := info.GetNamespace(ic) 180 mcd, ok := mcdMap[ns] 181 if !ok { 182 mcd = &memcacheData{items: map[string]*mcDataItem{}} 183 mcdMap[ns] = mcd 184 } 185 186 return &memcacheImpl{ 187 mcd, 188 ic, 189 } 190 }) 191 } 192 193 func (m *memcacheImpl) NewItem(key string) mc.Item { 194 return &mcItem{key: key} 195 } 196 197 func doCBs(items []mc.Item, cb mc.RawCB, inner func(mc.Item) error) { 198 // This weird construction is so that we: 199 // - don't take the lock for the entire multi operation, since it could imply 200 // false atomicity. 201 // - don't allow cb to block the actual batch operation, since that would 202 // allow binding in ways that aren't possible under the real 203 // implementation (like a recursive deadlock) 204 errs := make([]error, len(items)) 205 for i, itm := range items { 206 errs[i] = inner(itm) 207 } 208 for _, e := range errs { 209 cb(e) 210 } 211 } 212 213 func (m *memcacheImpl) AddMulti(items []mc.Item, cb mc.RawCB) error { 214 now := clock.Now(m.ctx) 215 doCBs(items, cb, func(itm mc.Item) error { 216 m.data.lock.Lock() 217 defer m.data.lock.Unlock() 218 if !m.data.hasItemLocked(now, itm.Key()) { 219 m.data.setItemLocked(now, itm) 220 return nil 221 } 222 return mc.ErrNotStored 223 }) 224 return nil 225 } 226 227 func (m *memcacheImpl) CompareAndSwapMulti(items []mc.Item, cb mc.RawCB) error { 228 now := clock.Now(m.ctx) 229 doCBs(items, cb, func(itm mc.Item) error { 230 m.data.lock.Lock() 231 defer m.data.lock.Unlock() 232 233 if cur, err := m.data.retrieveLocked(now, itm.Key()); err == nil { 234 casid := uint64(0) 235 if mi, ok := itm.(*mcItem); ok && mi != nil { 236 casid = mi.CasID 237 } 238 239 if cur.casID == casid { 240 m.data.setItemLocked(now, itm) 241 } else { 242 return mc.ErrCASConflict 243 } 244 return nil 245 } 246 return mc.ErrNotStored 247 }) 248 return nil 249 } 250 251 func (m *memcacheImpl) SetMulti(items []mc.Item, cb mc.RawCB) error { 252 now := clock.Now(m.ctx) 253 doCBs(items, cb, func(itm mc.Item) error { 254 m.data.lock.Lock() 255 defer m.data.lock.Unlock() 256 m.data.setItemLocked(now, itm) 257 return nil 258 }) 259 return nil 260 } 261 262 func (m *memcacheImpl) GetMulti(keys []string, cb mc.RawItemCB) error { 263 now := clock.Now(m.ctx) 264 265 itms := make([]mc.Item, len(keys)) 266 errs := make([]error, len(keys)) 267 268 for i, k := range keys { 269 itms[i], errs[i] = func() (mc.Item, error) { 270 m.data.lock.Lock() 271 defer m.data.lock.Unlock() 272 val, err := m.data.retrieveLocked(now, k) 273 if err != nil { 274 return nil, err 275 } 276 return val.toUserItem(k), nil 277 }() 278 } 279 280 for i, itm := range itms { 281 cb(itm, errs[i]) 282 } 283 284 return nil 285 } 286 287 func (m *memcacheImpl) DeleteMulti(keys []string, cb mc.RawCB) error { 288 now := clock.Now(m.ctx) 289 290 errs := make([]error, len(keys)) 291 292 for i, k := range keys { 293 errs[i] = func() error { 294 m.data.lock.Lock() 295 defer m.data.lock.Unlock() 296 _, err := m.data.retrieveLocked(now, k) 297 if err != nil { 298 return err 299 } 300 m.data.delItemLocked(k) 301 return nil 302 }() 303 } 304 305 for _, e := range errs { 306 cb(e) 307 } 308 309 return nil 310 } 311 312 func (m *memcacheImpl) Flush() error { 313 m.data.lock.Lock() 314 defer m.data.lock.Unlock() 315 316 m.data.reset() 317 return nil 318 } 319 320 func (m *memcacheImpl) Increment(key string, delta int64, initialValue *uint64) (uint64, error) { 321 now := clock.Now(m.ctx) 322 323 m.data.lock.Lock() 324 defer m.data.lock.Unlock() 325 326 cur := uint64(0) 327 switch item, err := m.data.retrieveLocked(now, key); { 328 case err == mc.ErrCacheMiss && initialValue != nil: 329 cur = *initialValue 330 case err != nil: 331 return 0, err 332 case len(item.value) != 8: 333 return 0, errors.New("memcache Increment: got invalid current value") 334 default: 335 cur = binary.LittleEndian.Uint64(item.value) 336 } 337 338 if delta < 0 { 339 if uint64(-delta) > cur { 340 cur = 0 341 } else { 342 cur -= uint64(-delta) 343 } 344 } else { 345 cur += uint64(delta) 346 } 347 348 newval := make([]byte, 8) 349 binary.LittleEndian.PutUint64(newval, cur) 350 m.data.setItemLocked(now, m.NewItem(key).SetValue(newval)) 351 352 return cur, nil 353 } 354 355 func (m *memcacheImpl) Stats() (*mc.Statistics, error) { 356 m.data.lock.Lock() 357 defer m.data.lock.Unlock() 358 359 ret := m.data.stats 360 return &ret, nil 361 }