| Index: impl/memory/gkvlite_utils.go
 | 
| diff --git a/impl/memory/gkvlite_utils.go b/impl/memory/gkvlite_utils.go
 | 
| index ed7aa5cd5b0f0cacdfa650769fe26f8e897092d4..adbc45df0d69c3de9ad0c22d3c8b0b9930e1ed3d 100644
 | 
| --- a/impl/memory/gkvlite_utils.go
 | 
| +++ b/impl/memory/gkvlite_utils.go
 | 
| @@ -9,13 +9,21 @@ import (
 | 
|  	"runtime"
 | 
|  	"sync"
 | 
|  
 | 
| +	"github.com/luci/gae/service/datastore"
 | 
|  	"github.com/luci/gkvlite"
 | 
|  )
 | 
|  
 | 
| -func gkvCollide(o, n *memCollection, f func(k, ov, nv []byte)) {
 | 
| +func gkvCollide(o, n memCollection, f func(k, ov, nv []byte)) {
 | 
| +	if o != nil && !o.IsReadOnly() {
 | 
| +		panic("old collection is r/w")
 | 
| +	}
 | 
| +	if n != nil && !n.IsReadOnly() {
 | 
| +		panic("new collection is r/w")
 | 
| +	}
 | 
| +
 | 
|  	// TODO(riannucci): reimplement in terms of *iterator.
 | 
|  	oldItems, newItems := make(chan *gkvlite.Item), make(chan *gkvlite.Item)
 | 
| -	walker := func(c *memCollection, ch chan<- *gkvlite.Item, wg *sync.WaitGroup) {
 | 
| +	walker := func(c memCollection, ch chan<- *gkvlite.Item, wg *sync.WaitGroup) {
 | 
|  		defer close(ch)
 | 
|  		defer wg.Done()
 | 
|  		if c != nil {
 | 
| @@ -66,78 +74,128 @@ func gkvCollide(o, n *memCollection, f func(k, ov, nv []byte)) {
 | 
|  // This is reasonable for in-memory Store objects, since the only errors that
 | 
|  // should occur happen with file IO on the underlying file (which of course
 | 
|  // doesn't exist).
 | 
| -type memStore gkvlite.Store
 | 
| +type memStore interface {
 | 
| +	datastore.TestingSnapshot
 | 
|  
 | 
| -func (*memStore) ImATestingSnapshot() {}
 | 
| +	GetCollection(name string) memCollection
 | 
| +	GetCollectionNames() []string
 | 
| +	GetOrCreateCollection(name string) memCollection
 | 
| +	Snapshot() memStore
 | 
|  
 | 
| -func newMemStore() *memStore {
 | 
| -	ret, err := gkvlite.NewStore(nil)
 | 
| -	memoryCorruption(err)
 | 
| -	return (*memStore)(ret)
 | 
| +	IsReadOnly() bool
 | 
| +}
 | 
| +
 | 
| +// memCollection is a gkvlite.Collection which will panic for anything which
 | 
| +// might otherwise return an error.
 | 
| +//
 | 
| +// This is reasonable for in-memory Store objects, since the only errors that
 | 
| +// should occur happen with file IO on the underlying file (which of course
 | 
| +// doesn't exist.
 | 
| +type memCollection interface {
 | 
| +	Name() string
 | 
| +	Delete(k []byte) bool
 | 
| +	Get(k []byte) []byte
 | 
| +	GetTotals() (numItems, numBytes uint64)
 | 
| +	MinItem(withValue bool) *gkvlite.Item
 | 
| +	Set(k, v []byte)
 | 
| +	VisitItemsAscend(target []byte, withValue bool, visitor gkvlite.ItemVisitor)
 | 
| +
 | 
| +	IsReadOnly() bool
 | 
|  }
 | 
|  
 | 
| -func (ms *memStore) Snapshot() *memStore {
 | 
| -	ret := (*memStore)((*gkvlite.Store)(ms).Snapshot())
 | 
| -	runtime.SetFinalizer((*gkvlite.Store)(ret), func(s *gkvlite.Store) {
 | 
| -		go s.Close()
 | 
| -	})
 | 
| +type memStoreImpl struct {
 | 
| +	s  *gkvlite.Store
 | 
| +	ro bool
 | 
| +}
 | 
| +
 | 
| +var _ memStore = (*memStoreImpl)(nil)
 | 
| +
 | 
| +func (*memStoreImpl) ImATestingSnapshot() {}
 | 
| +
 | 
| +func (ms *memStoreImpl) IsReadOnly() bool { return ms.ro }
 | 
| +
 | 
| +func newMemStore() memStore {
 | 
| +	store, err := gkvlite.NewStore(nil)
 | 
| +	memoryCorruption(err)
 | 
| +	ret := memStore(&memStoreImpl{store, false})
 | 
| +	if *logMemCollectionFolder != "" {
 | 
| +		ret = wrapTracingMemStore(ret)
 | 
| +	}
 | 
|  	return ret
 | 
|  }
 | 
|  
 | 
| -func (ms *memStore) MakePrivateCollection(cmp gkvlite.KeyCompare) *memCollection {
 | 
| -	return (*memCollection)((*gkvlite.Store)(ms).MakePrivateCollection(cmp))
 | 
| +func (ms *memStoreImpl) Snapshot() memStore {
 | 
| +	if ms.ro {
 | 
| +		return ms
 | 
| +	}
 | 
| +	ret := ms.s.Snapshot()
 | 
| +	runtime.SetFinalizer(ret, func(s *gkvlite.Store) { go s.Close() })
 | 
| +	return &memStoreImpl{ret, true}
 | 
|  }
 | 
|  
 | 
| -func (ms *memStore) GetCollection(name string) *memCollection {
 | 
| -	return (*memCollection)((*gkvlite.Store)(ms).GetCollection(name))
 | 
| +func (ms *memStoreImpl) GetCollection(name string) memCollection {
 | 
| +	coll := ms.s.GetCollection(name)
 | 
| +	if coll == nil {
 | 
| +		return nil
 | 
| +	}
 | 
| +	return &memCollectionImpl{coll, ms.ro}
 | 
|  }
 | 
|  
 | 
| -func (ms *memStore) SetCollection(name string, cmp gkvlite.KeyCompare) *memCollection {
 | 
| -	return (*memCollection)((*gkvlite.Store)(ms).SetCollection(name, cmp))
 | 
| +func (ms *memStoreImpl) GetOrCreateCollection(name string) memCollection {
 | 
| +	coll := ms.GetCollection(name)
 | 
| +	if coll == nil {
 | 
| +		coll = &memCollectionImpl{(ms.s.SetCollection(name, nil)), ms.ro}
 | 
| +	}
 | 
| +	return coll
 | 
|  }
 | 
|  
 | 
| -func (ms *memStore) GetCollectionNames() []string {
 | 
| -	return (*gkvlite.Store)(ms).GetCollectionNames()
 | 
| +func (ms *memStoreImpl) GetCollectionNames() []string {
 | 
| +	return ms.s.GetCollectionNames()
 | 
|  }
 | 
|  
 | 
| -// memCollection is a gkvlite.Collection which will panic for anything which
 | 
| -// might otherwise return an error.
 | 
| -//
 | 
| -// This is reasonable for in-memory Store objects, since the only errors that
 | 
| -// should occur happen with file IO on the underlying file (which of course
 | 
| -// doesn't exist.
 | 
| -type memCollection gkvlite.Collection
 | 
| +type memCollectionImpl struct {
 | 
| +	c  *gkvlite.Collection
 | 
| +	ro bool
 | 
| +}
 | 
| +
 | 
| +var _ memCollection = (*memCollectionImpl)(nil)
 | 
|  
 | 
| -func (mc *memCollection) Get(k []byte) []byte {
 | 
| -	ret, err := (*gkvlite.Collection)(mc).Get(k)
 | 
| +func (mc *memCollectionImpl) Name() string     { return mc.c.Name() }
 | 
| +func (mc *memCollectionImpl) IsReadOnly() bool { return mc.ro }
 | 
| +
 | 
| +func (mc *memCollectionImpl) Get(k []byte) []byte {
 | 
| +	ret, err := mc.c.Get(k)
 | 
|  	memoryCorruption(err)
 | 
|  	return ret
 | 
|  }
 | 
|  
 | 
| -func (mc *memCollection) MinItem(withValue bool) *gkvlite.Item {
 | 
| -	ret, err := (*gkvlite.Collection)(mc).MinItem(withValue)
 | 
| +func (mc *memCollectionImpl) MinItem(withValue bool) *gkvlite.Item {
 | 
| +	ret, err := mc.c.MinItem(withValue)
 | 
|  	memoryCorruption(err)
 | 
|  	return ret
 | 
|  }
 | 
|  
 | 
| -func (mc *memCollection) Set(k, v []byte) {
 | 
| -	err := (*gkvlite.Collection)(mc).Set(k, v)
 | 
| +func (mc *memCollectionImpl) Set(k, v []byte) {
 | 
| +	err := mc.c.Set(k, v)
 | 
|  	memoryCorruption(err)
 | 
|  }
 | 
|  
 | 
| -func (mc *memCollection) Delete(k []byte) bool {
 | 
| -	ret, err := (*gkvlite.Collection)(mc).Delete(k)
 | 
| +func (mc *memCollectionImpl) Delete(k []byte) bool {
 | 
| +	ret, err := mc.c.Delete(k)
 | 
|  	memoryCorruption(err)
 | 
|  	return ret
 | 
|  }
 | 
|  
 | 
| -func (mc *memCollection) VisitItemsAscend(target []byte, withValue bool, visitor gkvlite.ItemVisitor) {
 | 
| -	err := (*gkvlite.Collection)(mc).VisitItemsAscend(target, withValue, visitor)
 | 
| +func (mc *memCollectionImpl) VisitItemsAscend(target []byte, withValue bool, visitor gkvlite.ItemVisitor) {
 | 
| +	if !mc.ro {
 | 
| +		panic("attempting to VisitItemsAscend from r/w memCollection")
 | 
| +	}
 | 
| +	err := mc.c.VisitItemsAscend(target, withValue, visitor)
 | 
|  	memoryCorruption(err)
 | 
|  }
 | 
|  
 | 
| -func (mc *memCollection) GetTotals() (numItems, numBytes uint64) {
 | 
| -	numItems, numBytes, err := (*gkvlite.Collection)(mc).GetTotals()
 | 
| +func (mc *memCollectionImpl) GetTotals() (numItems, numBytes uint64) {
 | 
| +	numItems, numBytes, err := mc.c.GetTotals()
 | 
|  	memoryCorruption(err)
 | 
|  	return
 | 
|  }
 | 
| 
 |