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..9b4e65fa306180abe698d104cb304eb06cbf6437 | 
| --- /dev/null | 
| +++ b/go/src/infra/gae/libs/wrapper/memory/memcache.go | 
| @@ -0,0 +1,181 @@ | 
| +// 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 | 
| + | 
| + // 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{} | 
| 
 
M-A Ruel
2015/05/29 02:01:23
Sometimes you use var err error, sometimes this fo
 
iannucci
2015/05/29 02:42:29
.... I always prefer `foo := something{}`, but you
 
 | 
| + 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) mkItemLocked(i *memcache.Item) *unsafe.Item { | 
| + m.data.casID++ | 
| + 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.mkItemLocked(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.mkItemLocked(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.lock.Lock() | 
| + defer m.data.lock.Unlock() | 
| + | 
| + m.data.items[i.Key] = m.mkItemLocked(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) | 
| 
 
M-A Ruel
2015/05/29 02:01:23
It seems inefficient to look at expiration just to
 
iannucci
2015/05/29 02:42:29
It does seem silly, but that's actually how memcac
 
 | 
| + return nil | 
| + } | 
| + return memcache.ErrCacheMiss | 
| +} |