go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/gae/filter/dscache/memcache.go (about) 1 // Copyright 2020 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 dscache 16 17 import ( 18 "context" 19 "time" 20 21 "go.chromium.org/luci/common/errors" 22 23 "go.chromium.org/luci/gae/service/memcache" 24 ) 25 26 // States for a memcache entry. itemFlagUnknown exists to distinguish the 27 // default zero state from a valid state, but shouldn't ever be observed in 28 // memcache. 29 const ( 30 itemFlagUnknown uint32 = iota 31 itemFlagHasData 32 itemFlagHasLock 33 ) 34 35 type memcacheItem struct { 36 item memcache.Item 37 } 38 39 type memcacheImpl struct{} 40 41 func (memcacheImpl) PutLocks(ctx context.Context, keys []string, timeout time.Duration) error { 42 if len(keys) == 0 { 43 return nil 44 } 45 items := make([]memcache.Item, len(keys)) 46 for i, key := range keys { 47 items[i] = memcache.NewItem(ctx, key). 48 SetFlags(itemFlagHasLock). 49 SetExpiration(timeout) 50 } 51 return memcache.Set(ctx, items...) 52 } 53 54 func (memcacheImpl) DropLocks(ctx context.Context, keys []string) error { 55 if len(keys) == 0 { 56 return nil 57 } 58 return errors.Filter(memcache.Delete(ctx, keys...), memcache.ErrCacheMiss) 59 } 60 61 func (memcacheImpl) TryLockAndFetch(ctx context.Context, keys []string, nonce []byte, timeout time.Duration) ([]CacheItem, error) { 62 if len(keys) == 0 { 63 return nil, nil 64 } 65 66 mcItems := make([]memcache.Item, len(keys)) 67 for i, key := range keys { 68 if key == "" { 69 continue 70 } 71 mcItems[i] = memcache.NewItem(ctx, key). 72 SetFlags(itemFlagHasLock). 73 SetExpiration(timeout). 74 SetValue(nonce) 75 } 76 77 if err := memcache.Add(ctx, mcItems...); err != nil { 78 // Ignore this error. Either we couldn't add them because they exist 79 // (so, not an issue), or because memcache is having sad times (in which 80 // case we'll see so in the Get which immediately follows this). 81 } 82 83 // We'll return this error as is in the end, along with all items that were 84 // fetched successfully. 85 err := errors.Filter(memcache.Get(ctx, mcItems...), memcache.ErrCacheMiss) 86 87 items := make([]CacheItem, len(mcItems)) 88 for i, mcItem := range mcItems { 89 if mcItem == nil { 90 continue // cache miss or a gap in "keys" 91 } 92 if flag := mcItem.Flags(); flag == itemFlagUnknown || flag > itemFlagHasLock { 93 continue // unrecognized memcache entry, treat it as a cache miss 94 } 95 items[i] = memcacheItem{mcItem} 96 } 97 98 return items, err 99 } 100 101 func (memcacheImpl) CompareAndSwap(ctx context.Context, items []CacheItem) error { 102 if len(items) == 0 { 103 return nil 104 } 105 mcItems := make([]memcache.Item, len(items)) 106 for i, item := range items { 107 mcItems[i] = item.(memcacheItem).item 108 } 109 return memcache.CompareAndSwap(ctx, mcItems...) 110 } 111 112 // Implement CacheItem interface for memcacheItem. 113 114 func (m memcacheItem) Key() string { 115 return m.item.Key() 116 } 117 118 func (m memcacheItem) Nonce() []byte { 119 if m.item.Flags() == itemFlagHasLock { 120 return m.item.Value() 121 } 122 return nil 123 } 124 125 func (m memcacheItem) Data() []byte { 126 if m.item.Flags() == itemFlagHasData { 127 return m.item.Value() 128 } 129 return nil 130 } 131 132 func (m memcacheItem) Prefix() []byte { 133 return nil 134 } 135 136 func (m memcacheItem) PromoteToData(data []byte, exp time.Duration) { 137 if m.item.Flags() != itemFlagHasLock { 138 panic("only locks should be promoted") 139 } 140 m.item.SetFlags(itemFlagHasData) 141 m.item.SetExpiration(exp) 142 m.item.SetValue(data) 143 } 144 145 func (m memcacheItem) PromoteToIndefiniteLock() { 146 if m.item.Flags() != itemFlagHasLock { 147 panic("only locks should be promoted") 148 } 149 m.item.SetFlags(itemFlagHasLock) 150 m.item.SetExpiration(0) 151 m.item.SetValue(nil) 152 }