Chromium Code Reviews| Index: go/src/infra/gae/libs/wrapper/memory/memcache.go |
| diff --git a/go/src/infra/gae/libs/wrapper/memory/memcache.go b/go/src/infra/gae/libs/wrapper/memory/memcache.go |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..4556d4df224ec8f93ea7d3da7d9ec5221553e30b |
| --- /dev/null |
| +++ b/go/src/infra/gae/libs/wrapper/memory/memcache.go |
| @@ -0,0 +1,178 @@ |
| +// Copyright 2015 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +package memory |
| + |
| +import ( |
| + "infra/gae/libs/wrapper" |
| + "infra/gae/libs/wrapper/gae/commonErrors" |
| + "infra/gae/libs/wrapper/unsafe" |
| + "sync" |
| + "time" |
| + |
| + "golang.org/x/net/context" |
| + |
| + "appengine/memcache" |
| +) |
| + |
| +type memcacheData struct { |
| + lock sync.Mutex |
| + items map[string]*unsafe.Item |
| + casID uint64 |
| +} |
| + |
| +// memcacheImpl binds the current connection's memcache data to an |
| +// implementation of {wrapper.Memcache, wrapper.Testable}. |
| +type memcacheImpl struct { |
| + wrapper.Memcache |
| + *wrapper.BrokenFeatures |
|
M-A Ruel
2015/05/28 22:42:38
Why a pointer?
iannucci
2015/05/28 23:00:35
gone
|
| + |
| + // TODO(riannucci): bind+use namespace too |
| + |
| + data *memcacheData |
| + timeNow func() time.Time |
| +} |
| + |
| +// UseMC adds a wrapper.Memcache implementation to context, accessible |
| +// by wrapper.GetMC(c) |
| +func UseMC(c context.Context) context.Context { |
| + lck := sync.Mutex{} |
| + mcdMap := map[string]*memcacheData{} |
| + |
| + return wrapper.SetMCFactory(c, func(ic context.Context) wrapper.Memcache { |
| + lck.Lock() |
| + defer lck.Unlock() |
| + |
| + ns := curGID(ic).namespace |
| + mcd, ok := mcdMap[ns] |
| + if !ok { |
| + mcd = &memcacheData{items: map[string]*unsafe.Item{}} |
| + mcdMap[ns] = mcd |
| + } |
| + |
| + return &memcacheImpl{ |
| + wrapper.DummyMC(), |
| + &wrapper.BrokenFeatures{DefaultError: commonErrors.ErrServerErrorMC}, |
| + mcd, |
| + func() time.Time { return wrapper.GetTimeNow(ic) }, |
| + } |
| + }) |
| +} |
| + |
| +func (m *memcacheImpl) mkItem(i *memcache.Item) *unsafe.Item { |
| + m.data.casID++ |
|
M-A Ruel
2015/05/28 22:42:38
Not thread safe.
iannucci
2015/05/28 23:00:35
renamed method, only (supposed) to be called from
|
| + var exp time.Duration |
| + if i.Expiration != 0 { |
| + exp = time.Duration(m.timeNow().Add(i.Expiration).UnixNano()) |
| + } |
| + newItem := unsafe.Item{ |
| + Key: i.Key, |
| + Value: make([]byte, len(i.Value)), |
| + Flags: i.Flags, |
| + Expiration: exp, |
| + CasID: m.data.casID, |
| + } |
| + copy(newItem.Value, i.Value) |
| + return &newItem |
| +} |
| + |
| +func copyBack(i *unsafe.Item) *memcache.Item { |
| + ret := &memcache.Item{ |
| + Key: i.Key, |
| + Value: make([]byte, len(i.Value)), |
| + Flags: i.Flags, |
| + } |
| + copy(ret.Value, i.Value) |
| + unsafe.MCSetCasID(ret, i.CasID) |
| + |
| + return ret |
| +} |
| + |
| +func (m *memcacheImpl) retrieve(key string) (*unsafe.Item, bool) { |
| + ret, ok := m.data.items[key] |
| + if ok && ret.Expiration != 0 && ret.Expiration < time.Duration(m.timeNow().UnixNano()) { |
| + ret = nil |
| + ok = false |
| + delete(m.data.items, key) |
| + } |
| + return ret, ok |
| +} |
| + |
| +// Add implements context.MCSingleReadWriter.Add. |
| +func (m *memcacheImpl) Add(i *memcache.Item) error { |
| + if err := m.IsBroken(); err != nil { |
| + return err |
| + } |
| + |
| + m.data.lock.Lock() |
| + defer m.data.lock.Unlock() |
| + |
| + if _, ok := m.retrieve(i.Key); !ok { |
| + m.data.items[i.Key] = m.mkItem(i) |
| + return nil |
| + } |
| + return memcache.ErrNotStored |
| +} |
| + |
| +// CompareAndSwap implements context.MCSingleReadWriter.CompareAndSwap. |
| +func (m *memcacheImpl) CompareAndSwap(item *memcache.Item) error { |
| + if err := m.IsBroken(); err != nil { |
| + return err |
| + } |
| + |
| + m.data.lock.Lock() |
| + defer m.data.lock.Unlock() |
| + |
| + if cur, ok := m.retrieve(item.Key); ok { |
| + if cur.CasID == unsafe.MCGetCasID(item) { |
| + m.data.items[item.Key] = m.mkItem(item) |
| + } else { |
| + return memcache.ErrCASConflict |
| + } |
| + } else { |
| + return memcache.ErrNotStored |
| + } |
| + return nil |
| +} |
| + |
| +// Set implements context.MCSingleReadWriter.Set. |
| +func (m *memcacheImpl) Set(i *memcache.Item) error { |
| + if err := m.IsBroken(); err != nil { |
| + return err |
| + } |
| + |
| + m.data.items[i.Key] = m.mkItem(i) |
| + return nil |
| +} |
| + |
| +// Get implements context.MCSingleReadWriter.Get. |
| +func (m *memcacheImpl) Get(key string) (*memcache.Item, error) { |
| + if err := m.IsBroken(); err != nil { |
| + return nil, err |
| + } |
| + |
| + m.data.lock.Lock() |
| + defer m.data.lock.Unlock() |
| + |
| + if val, ok := m.retrieve(key); ok { |
| + return copyBack(val), nil |
| + } |
| + return nil, memcache.ErrCacheMiss |
| +} |
| + |
| +// Delete implements context.MCSingleReadWriter.Delete. |
| +func (m *memcacheImpl) Delete(key string) error { |
| + if err := m.IsBroken(); err != nil { |
| + return err |
| + } |
| + |
| + m.data.lock.Lock() |
| + defer m.data.lock.Unlock() |
| + |
| + if _, ok := m.retrieve(key); ok { |
| + delete(m.data.items, key) |
| + return nil |
| + } |
| + return memcache.ErrCacheMiss |
| +} |