blob: cee947e0d263545c230c0c1da628e57b6a5bb1cf [file] [log] [blame]
// Copyright 2020 The LUCI Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package dscache
import (
"context"
"time"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/gae/service/memcache"
)
// States for a memcache entry. itemFlagUnknown exists to distinguish the
// default zero state from a valid state, but shouldn't ever be observed in
// memcache.
const (
itemFlagUnknown uint32 = iota
itemFlagHasData
itemFlagHasLock
)
type memcacheItem struct {
item memcache.Item
}
type memcacheImpl struct{}
func (memcacheImpl) PutLocks(ctx context.Context, keys []string, timeout time.Duration) error {
if len(keys) == 0 {
return nil
}
items := make([]memcache.Item, len(keys))
for i, key := range keys {
items[i] = memcache.NewItem(ctx, key).
SetFlags(itemFlagHasLock).
SetExpiration(timeout)
}
return memcache.Set(ctx, items...)
}
func (memcacheImpl) DropLocks(ctx context.Context, keys []string) error {
if len(keys) == 0 {
return nil
}
return errors.Filter(memcache.Delete(ctx, keys...), memcache.ErrCacheMiss)
}
func (memcacheImpl) TryLockAndFetch(ctx context.Context, keys []string, nonce []byte, timeout time.Duration) ([]CacheItem, error) {
if len(keys) == 0 {
return nil, nil
}
mcItems := make([]memcache.Item, len(keys))
for i, key := range keys {
if key == "" {
continue
}
mcItems[i] = memcache.NewItem(ctx, key).
SetFlags(itemFlagHasLock).
SetExpiration(timeout).
SetValue(nonce)
}
if err := memcache.Add(ctx, mcItems...); err != nil {
// Ignore this error. Either we couldn't add them because they exist
// (so, not an issue), or because memcache is having sad times (in which
// case we'll see so in the Get which immediately follows this).
}
// We'll return this error as is in the end, along with all items that were
// fetched successfully.
err := errors.Filter(memcache.Get(ctx, mcItems...), memcache.ErrCacheMiss)
items := make([]CacheItem, len(mcItems))
for i, mcItem := range mcItems {
if mcItem == nil {
continue // cache miss or a gap in "keys"
}
if flag := mcItem.Flags(); flag == itemFlagUnknown || flag > itemFlagHasLock {
continue // unrecognized memcache entry, treat it as a cache miss
}
items[i] = memcacheItem{mcItem}
}
return items, err
}
func (memcacheImpl) CompareAndSwap(ctx context.Context, items []CacheItem) error {
if len(items) == 0 {
return nil
}
mcItems := make([]memcache.Item, len(items))
for i, item := range items {
mcItems[i] = item.(memcacheItem).item
}
return memcache.CompareAndSwap(ctx, mcItems...)
}
// Implement CacheItem interface for memcacheItem.
func (m memcacheItem) Key() string {
return m.item.Key()
}
func (m memcacheItem) Nonce() []byte {
if m.item.Flags() == itemFlagHasLock {
return m.item.Value()
}
return nil
}
func (m memcacheItem) Data() []byte {
if m.item.Flags() == itemFlagHasData {
return m.item.Value()
}
return nil
}
func (m memcacheItem) Prefix() []byte {
return nil
}
func (m memcacheItem) PromoteToData(data []byte, exp time.Duration) {
if m.item.Flags() != itemFlagHasLock {
panic("only locks should be promoted")
}
m.item.SetFlags(itemFlagHasData)
m.item.SetExpiration(exp)
m.item.SetValue(data)
}
func (m memcacheItem) PromoteToIndefiniteLock() {
if m.item.Flags() != itemFlagHasLock {
panic("only locks should be promoted")
}
m.item.SetFlags(itemFlagHasLock)
m.item.SetExpiration(0)
m.item.SetValue(nil)
}