go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/common/data/treapstore/store.go (about)

     1  // Copyright 2016 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  // Package treapstore is a lightweight append-only in-memory key-value store
    16  // built on top a treap (tree + heap) implementation.
    17  //
    18  // treapstore is specifically focused on supporting the in-memory datastore
    19  // implementation at "go.chromium.org/luci/gae/impl/memory".
    20  package treapstore
    21  
    22  import (
    23  	"fmt"
    24  	"math/rand"
    25  	"sort"
    26  	"sync"
    27  
    28  	"github.com/luci/gtreap"
    29  )
    30  
    31  // emptyTreap is an empty gtreap.Treap.
    32  var emptyTreap = gtreap.NewTreap(nil)
    33  
    34  // Store is a general-purpose concurrently-accessible copy-on-write store built
    35  // on top of the github.com/steveyen/gtreap treap (tree + heap) implementation.
    36  //
    37  // A Store is composed of a series of named Collection instances, each of which
    38  // can hold data elements.
    39  //
    40  // A Store is created by calling New, and is initially in a read/write mode.
    41  // Derivative Stores can be created by calling a Store's Snapshot capability.
    42  // These Stores will be read-only, meaning their collections and those
    43  // collections' data data may only be accessed, not modified.
    44  //
    45  // A Store's zero value is a valid read-only empty Store, but is not terribly
    46  // useful.
    47  type Store struct {
    48  	collLock  *sync.RWMutex
    49  	colls     map[string]*Collection
    50  	collNames []string // collNames is copy-on-write while holding collLock.
    51  }
    52  
    53  // New creates a new read/write Store.
    54  func New() *Store {
    55  	return &Store{
    56  		collLock: &sync.RWMutex{},
    57  	}
    58  }
    59  
    60  // IsReadOnly returns true if this Store is read-only.
    61  func (s *Store) IsReadOnly() bool { return s.collLock == nil }
    62  
    63  func (s *Store) assertNotReadOnly() {
    64  	if s.IsReadOnly() {
    65  		panic("store is read-only")
    66  	}
    67  }
    68  
    69  // Snapshot creates a read-only copy of the Store and all of its Collection
    70  // instances. Because a Store is copy-on-write, this is a cheap operation.
    71  func (s *Store) Snapshot() *Store {
    72  	if s.IsReadOnly() {
    73  		return s
    74  	}
    75  
    76  	s.collLock.RLock()
    77  	defer s.collLock.RUnlock()
    78  
    79  	// Return a read-only Store with the new Collection set.
    80  	snap := &Store{
    81  		collLock:  nil,
    82  		colls:     make(map[string]*Collection, len(s.colls)),
    83  		collNames: s.collNames,
    84  	}
    85  
    86  	// Create a read-only Collection for each coll.
    87  	for k, coll := range s.colls {
    88  		newColl := &Collection{
    89  			name: coll.name,
    90  		}
    91  		newColl.setRoot(coll.currentRoot())
    92  		snap.colls[k] = newColl
    93  	}
    94  	return snap
    95  }
    96  
    97  // GetCollectionNames returns the names of the Collections in this Store, sorted
    98  // alphabetically.
    99  func (s *Store) GetCollectionNames() []string {
   100  	if s.collLock != nil {
   101  		s.collLock.RLock()
   102  		defer s.collLock.RUnlock()
   103  	}
   104  
   105  	// Clone our collection names slice.
   106  	if len(s.collNames) == 0 {
   107  		return nil
   108  	}
   109  	return append([]string(nil), s.collNames...)
   110  }
   111  
   112  // GetCollection returns the Collection with the specified name. If no such
   113  // Collection exists, GetCollection will return nil.
   114  func (s *Store) GetCollection(name string) *Collection {
   115  	if s.collLock != nil {
   116  		s.collLock.RLock()
   117  		defer s.collLock.RUnlock()
   118  	}
   119  	return s.colls[name]
   120  }
   121  
   122  // CreateCollection returns a Collection with the specified name. If the
   123  // collection already exists, or if s is read-only, CreateCollection will panic.
   124  func (s *Store) CreateCollection(name string, compare gtreap.Compare) *Collection {
   125  	s.assertNotReadOnly()
   126  
   127  	s.collLock.Lock()
   128  	defer s.collLock.Unlock()
   129  
   130  	// Try to get the existing Collection again now that we hold the write lock.
   131  	if _, ok := s.colls[name]; ok {
   132  		panic(fmt.Errorf("collection %q already exists", name))
   133  	}
   134  
   135  	// Create a new read/write Collection.
   136  	coll := &Collection{
   137  		name:     name,
   138  		rootLock: &sync.RWMutex{},
   139  	}
   140  	coll.setRoot(gtreap.NewTreap(compare))
   141  
   142  	if s.colls == nil {
   143  		s.colls = make(map[string]*Collection)
   144  	}
   145  	s.colls[name] = coll
   146  	s.collNames = s.insertCollectionName(name)
   147  	return coll
   148  }
   149  
   150  // insertCollectionName returns a copy of s.collNames with name inserted
   151  // in its appropriate sorted position.
   152  func (s *Store) insertCollectionName(name string) []string {
   153  	sidx := sort.SearchStrings(s.collNames, name)
   154  	r := make([]string, 0, len(s.collNames)+1)
   155  	return append(append(append(r, s.collNames[:sidx]...), name), s.collNames[sidx:]...)
   156  }
   157  
   158  // Collection is a collection of Items.
   159  //
   160  // Collections belonging to read/write Store instances are, themselves,
   161  // read/write. Collections belonging to Snapshot instances are read-only.
   162  //
   163  // A Collection's zero value is a valid read-only empty Collection, which is not
   164  // terribly useful.
   165  type Collection struct {
   166  	name string
   167  
   168  	rootLock *sync.RWMutex
   169  	root     *gtreap.Treap
   170  }
   171  
   172  func (c *Collection) assertNotReadOnly() {
   173  	if c.IsReadOnly() {
   174  		panic("collection is read-only")
   175  	}
   176  }
   177  
   178  // Name returns this Collection's name.
   179  func (c *Collection) Name() string { return c.name }
   180  
   181  // IsReadOnly returns true if this Collection is read-only.
   182  func (c *Collection) IsReadOnly() bool { return c.rootLock == nil }
   183  
   184  // Min returns the smallest item in the collection.
   185  func (c *Collection) Min() gtreap.Item {
   186  	root := c.currentRoot()
   187  	if root == nil {
   188  		return nil
   189  	}
   190  	return root.Min()
   191  }
   192  
   193  // Max returns the largest item in the collection.
   194  func (c *Collection) Max() gtreap.Item {
   195  	root := c.currentRoot()
   196  	if root == nil {
   197  		return nil
   198  	}
   199  	return root.Max()
   200  }
   201  
   202  // Get returns the item in the Store that matches i, or nil if no such item
   203  // exists.
   204  func (c *Collection) Get(i gtreap.Item) gtreap.Item {
   205  	root := c.currentRoot()
   206  	if root == nil {
   207  		return nil
   208  	}
   209  	return root.Get(i)
   210  }
   211  
   212  // Put adds an item to the Store.
   213  //
   214  // If the Store is read-only, Put will panic.
   215  func (c *Collection) Put(i gtreap.Item) {
   216  	c.assertNotReadOnly()
   217  
   218  	// Lock around the entire Upsert operation to serialize Puts.
   219  	priority := rand.Int()
   220  	c.rootLock.Lock()
   221  	c.root = c.root.Upsert(i, priority)
   222  	c.rootLock.Unlock()
   223  }
   224  
   225  // Delete deletes an item from the Collection, if such an item exists.
   226  func (c *Collection) Delete(i gtreap.Item) {
   227  	c.assertNotReadOnly()
   228  
   229  	c.rootLock.Lock()
   230  	c.root = c.root.Delete(i)
   231  	c.rootLock.Unlock()
   232  }
   233  
   234  func (c *Collection) currentRoot() *gtreap.Treap {
   235  	if c.rootLock != nil {
   236  		c.rootLock.RLock()
   237  		defer c.rootLock.RUnlock()
   238  	}
   239  	return c.root
   240  }
   241  
   242  func (c *Collection) setRoot(root *gtreap.Treap) {
   243  	if c.rootLock != nil {
   244  		c.rootLock.Lock()
   245  		defer c.rootLock.Unlock()
   246  	}
   247  	c.root = root
   248  }
   249  
   250  // VisitAscend traverses the Collection ascendingly, invoking visitor for each
   251  // visited item.
   252  //
   253  // If visitor returns false, iteration will stop prematurely.
   254  //
   255  // VisitAscend is a more efficient traversal than using an Iterator, and is
   256  // useful in times when entry-by-entry iteration is not required.
   257  func (c *Collection) VisitAscend(pivot gtreap.Item, visitor gtreap.ItemVisitor) {
   258  	c.currentRoot().VisitAscend(pivot, visitor)
   259  }
   260  
   261  // Iterator returns an iterator over the Collection, starting at the supplied
   262  // pivot item.
   263  func (c *Collection) Iterator(pivot gtreap.Item) *gtreap.Iterator {
   264  	root := c.currentRoot()
   265  	if root == nil {
   266  		root = emptyTreap
   267  	}
   268  	return root.Iterator(pivot)
   269  }