Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(648)

Unified Diff: common/data/treapstore/store.go

Issue 2607663002: Add treapstore, a treap-based in-memory data store (Closed)
Patch Set: Fix test race, better docs, valid zero values. Created 4 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « no previous file | common/data/treapstore/store_test.go » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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)
+}
« no previous file with comments | « no previous file | common/data/treapstore/store_test.go » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698