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  }