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 }