| Index: common/data/treapstore/store.go
|
| diff --git a/common/data/treapstore/store.go b/common/data/treapstore/store.go
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..14599ff9dbe0329c6929989605059ab217edec3f
|
| --- /dev/null
|
| +++ b/common/data/treapstore/store.go
|
| @@ -0,0 +1,248 @@
|
| +// Copyright 2016 The LUCI Authors. All rights reserved.
|
| +// Use of this source code is governed under the Apache License, Version 2.0
|
| +// that can be found in the LICENSE file.
|
| +
|
| +// Package treapstore is a lightweight append-only in-memory key-value store
|
| +// built on top a treap (tree + heap) implementation.
|
| +//
|
| +// treapstore is specifically focused on supporting the in-memory datastore
|
| +// implementation at "github.com/luci/gae/impl/memory".
|
| +package treapstore
|
| +
|
| +import (
|
| + "fmt"
|
| + "math/rand"
|
| + "sort"
|
| + "sync"
|
| +
|
| + "github.com/luci/gtreap"
|
| +)
|
| +
|
| +// emptyTreap is an empty gtreap.Treap.
|
| +var emptyTreap = gtreap.NewTreap(nil)
|
| +
|
| +// Store is a general-purpose concurrently-accessible copy-on-write store built
|
| +// on top of the github.com/steveyen/gtreap treap (tree + heap) implementation.
|
| +//
|
| +// A Store is composed of a series of named Collection instances, each of which
|
| +// can hold data elements.
|
| +//
|
| +// A Store is created by calling New, and is initially in a read/write mode.
|
| +// Derivative Stores can be created by calling a Store's Snapshot capability.
|
| +// These Stores will be read-only, meaning their collections and those
|
| +// collections' data data may only be accessed, not modified.
|
| +//
|
| +// A Store's zero value is a valid read-only empty Store, but is not terribly
|
| +// useful.
|
| +type Store struct {
|
| + collLock *sync.RWMutex
|
| + colls map[string]*Collection
|
| + collNames []string // collNames is copy-on-write while holding collLock.
|
| +}
|
| +
|
| +// New creates a new read/write Store.
|
| +func New() *Store {
|
| + return &Store{
|
| + collLock: &sync.RWMutex{},
|
| + }
|
| +}
|
| +
|
| +// IsReadOnly returns true if this Store is read-only.
|
| +func (s *Store) IsReadOnly() bool { return s.collLock == nil }
|
| +
|
| +func (s *Store) assertNotReadOnly() {
|
| + if s.IsReadOnly() {
|
| + panic("store is read-only")
|
| + }
|
| +}
|
| +
|
| +// Snapshot creates a read-only copy of the Store and all of its Collection
|
| +// instances. Because a Store is copy-on-write, this is a cheap operation.
|
| +func (s *Store) Snapshot() *Store {
|
| + if s.IsReadOnly() {
|
| + return s
|
| + }
|
| +
|
| + s.collLock.RLock()
|
| + defer s.collLock.RUnlock()
|
| +
|
| + // Return a read-only Store with the new Collection set.
|
| + snap := &Store{
|
| + collLock: nil,
|
| + colls: make(map[string]*Collection, len(s.colls)),
|
| + collNames: s.collNames,
|
| + }
|
| +
|
| + // Create a read-only Collection for each coll.
|
| + for k, coll := range s.colls {
|
| + newColl := &Collection{
|
| + name: coll.name,
|
| + }
|
| + newColl.setRoot(coll.currentRoot())
|
| + snap.colls[k] = newColl
|
| + }
|
| + return snap
|
| +}
|
| +
|
| +// GetCollectionNames returns the names of the Collections in this Store, sorted
|
| +// alphabetically.
|
| +func (s *Store) GetCollectionNames() []string {
|
| + if s.collLock != nil {
|
| + s.collLock.RLock()
|
| + defer s.collLock.RUnlock()
|
| + }
|
| +
|
| + // Clone our collection names slice.
|
| + if len(s.collNames) == 0 {
|
| + return nil
|
| + }
|
| + return append([]string(nil), s.collNames...)
|
| +}
|
| +
|
| +// GetCollection returns the Collection with the specified name. If no such
|
| +// Collection exists, GetCollection will return nil.
|
| +func (s *Store) GetCollection(name string) *Collection {
|
| + if s.collLock != nil {
|
| + s.collLock.RLock()
|
| + defer s.collLock.RUnlock()
|
| + }
|
| + return s.colls[name]
|
| +}
|
| +
|
| +// CreateCollection returns a Collection with the specified name. If the
|
| +// collection already exists, or if s is read-only, CreateCollection will panic.
|
| +func (s *Store) CreateCollection(name string, compare gtreap.Compare) *Collection {
|
| + s.assertNotReadOnly()
|
| +
|
| + s.collLock.Lock()
|
| + defer s.collLock.Unlock()
|
| +
|
| + // Try to get the existing Collection again now that we hold the write lock.
|
| + if _, ok := s.colls[name]; ok {
|
| + panic(fmt.Errorf("collection %q already exists", name))
|
| + }
|
| +
|
| + // Create a new read/write Collection.
|
| + coll := &Collection{
|
| + name: name,
|
| + rootLock: &sync.RWMutex{},
|
| + }
|
| + coll.setRoot(gtreap.NewTreap(compare))
|
| +
|
| + if s.colls == nil {
|
| + s.colls = make(map[string]*Collection)
|
| + }
|
| + s.colls[name] = coll
|
| + s.collNames = s.insertCollectionName(name)
|
| + return coll
|
| +}
|
| +
|
| +// insertCollectionName returns a copy of s.collNames with name inserted
|
| +// in its appropriate sorted position.
|
| +func (s *Store) insertCollectionName(name string) []string {
|
| + sidx := sort.SearchStrings(s.collNames, name)
|
| + r := make([]string, 0, len(s.collNames)+1)
|
| + return append(append(append(r, s.collNames[:sidx]...), name), s.collNames[sidx:]...)
|
| +}
|
| +
|
| +// Collection is a collection of Items.
|
| +//
|
| +// Collections belonging to read/write Store instances are, themselves,
|
| +// read/write. Collections belonging to Snapshot instances are read-only.
|
| +//
|
| +// A Collection's zero value is a valid read-only empty Collection, which is not
|
| +// terribly useful.
|
| +type Collection struct {
|
| + name string
|
| +
|
| + rootLock *sync.RWMutex
|
| + root *gtreap.Treap
|
| +}
|
| +
|
| +func (c *Collection) assertNotReadOnly() {
|
| + if c.IsReadOnly() {
|
| + panic("collection is read-only")
|
| + }
|
| +}
|
| +
|
| +// Name returns this Collection's name.
|
| +func (c *Collection) Name() string { return c.name }
|
| +
|
| +// IsReadOnly returns true if this Collection is read-only.
|
| +func (c *Collection) IsReadOnly() bool { return c.rootLock == nil }
|
| +
|
| +// Min returns the smallest item in the collection.
|
| +func (c *Collection) Min() gtreap.Item {
|
| + root := c.currentRoot()
|
| + if root == nil {
|
| + return nil
|
| + }
|
| + return root.Min()
|
| +}
|
| +
|
| +// Max returns the largest item in the collection.
|
| +func (c *Collection) Max() gtreap.Item {
|
| + root := c.currentRoot()
|
| + if root == nil {
|
| + return nil
|
| + }
|
| + return root.Max()
|
| +}
|
| +
|
| +// Get returns the item in the Store that matches i, or nil if no such item
|
| +// exists.
|
| +func (c *Collection) Get(i gtreap.Item) gtreap.Item {
|
| + root := c.currentRoot()
|
| + if root == nil {
|
| + return nil
|
| + }
|
| + return root.Get(i)
|
| +}
|
| +
|
| +// Put adds an item to the Store.
|
| +//
|
| +// If the Store is read-only, Put will panic.
|
| +func (c *Collection) Put(i gtreap.Item) {
|
| + c.assertNotReadOnly()
|
| +
|
| + // Lock around the entire Upsert operation to serialize Puts.
|
| + priority := rand.Int()
|
| + c.rootLock.Lock()
|
| + c.root = c.root.Upsert(i, priority)
|
| + c.rootLock.Unlock()
|
| +}
|
| +
|
| +// Delete deletes an item from the Collection, if such an item exists.
|
| +func (c *Collection) Delete(i gtreap.Item) {
|
| + c.assertNotReadOnly()
|
| +
|
| + c.rootLock.Lock()
|
| + c.root = c.root.Delete(i)
|
| + c.rootLock.Unlock()
|
| +}
|
| +
|
| +func (c *Collection) currentRoot() *gtreap.Treap {
|
| + if c.rootLock != nil {
|
| + c.rootLock.RLock()
|
| + defer c.rootLock.RUnlock()
|
| + }
|
| + return c.root
|
| +}
|
| +
|
| +func (c *Collection) setRoot(root *gtreap.Treap) {
|
| + if c.rootLock != nil {
|
| + c.rootLock.Lock()
|
| + defer c.rootLock.Unlock()
|
| + }
|
| + c.root = root
|
| +}
|
| +
|
| +// Iterator returns an iterator over the Store, starting at the supplied pivot
|
| +// item.
|
| +func (c *Collection) Iterator(pivot gtreap.Item) *gtreap.Iterator {
|
| + root := c.currentRoot()
|
| + if root == nil {
|
| + root = emptyTreap
|
| + }
|
| + return root.Iterator(pivot)
|
| +}
|
|
|