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

Side by Side 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 3 years, 11 months 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 unified diff | Download patch
« no previous file with comments | « no previous file | common/data/treapstore/store_test.go » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 // Copyright 2016 The LUCI Authors. All rights reserved.
2 // Use of this source code is governed under the Apache License, Version 2.0
3 // that can be found in the LICENSE file.
4
5 // Package treapstore is a lightweight append-only in-memory key-value store
6 // built on top a treap (tree + heap) implementation.
7 //
8 // treapstore is specifically focused on supporting the in-memory datastore
9 // implementation at "github.com/luci/gae/impl/memory".
10 package treapstore
11
12 import (
13 "fmt"
14 "math/rand"
15 "sort"
16 "sync"
17
18 "github.com/luci/gtreap"
19 )
20
21 // emptyTreap is an empty gtreap.Treap.
22 var emptyTreap = gtreap.NewTreap(nil)
23
24 // Store is a general-purpose concurrently-accessible copy-on-write store built
25 // on top of the github.com/steveyen/gtreap treap (tree + heap) implementation.
26 //
27 // A Store is composed of a series of named Collection instances, each of which
28 // can hold data elements.
29 //
30 // A Store is created by calling New, and is initially in a read/write mode.
31 // Derivative Stores can be created by calling a Store's Snapshot capability.
32 // These Stores will be read-only, meaning their collections and those
33 // collections' data data may only be accessed, not modified.
34 //
35 // A Store's zero value is a valid read-only empty Store, but is not terribly
36 // useful.
37 type Store struct {
38 collLock *sync.RWMutex
39 colls map[string]*Collection
40 collNames []string // collNames is copy-on-write while holding collLock.
41 }
42
43 // New creates a new read/write Store.
44 func New() *Store {
45 return &Store{
46 collLock: &sync.RWMutex{},
47 }
48 }
49
50 // IsReadOnly returns true if this Store is read-only.
51 func (s *Store) IsReadOnly() bool { return s.collLock == nil }
52
53 func (s *Store) assertNotReadOnly() {
54 if s.IsReadOnly() {
55 panic("store is read-only")
56 }
57 }
58
59 // Snapshot creates a read-only copy of the Store and all of its Collection
60 // instances. Because a Store is copy-on-write, this is a cheap operation.
61 func (s *Store) Snapshot() *Store {
62 if s.IsReadOnly() {
63 return s
64 }
65
66 s.collLock.RLock()
67 defer s.collLock.RUnlock()
68
69 // Return a read-only Store with the new Collection set.
70 snap := &Store{
71 collLock: nil,
72 colls: make(map[string]*Collection, len(s.colls)),
73 collNames: s.collNames,
74 }
75
76 // Create a read-only Collection for each coll.
77 for k, coll := range s.colls {
78 newColl := &Collection{
79 name: coll.name,
80 }
81 newColl.setRoot(coll.currentRoot())
82 snap.colls[k] = newColl
83 }
84 return snap
85 }
86
87 // GetCollectionNames returns the names of the Collections in this Store, sorted
88 // alphabetically.
89 func (s *Store) GetCollectionNames() []string {
90 if s.collLock != nil {
91 s.collLock.RLock()
92 defer s.collLock.RUnlock()
93 }
94
95 // Clone our collection names slice.
96 if len(s.collNames) == 0 {
97 return nil
98 }
99 return append([]string(nil), s.collNames...)
100 }
101
102 // GetCollection returns the Collection with the specified name. If no such
103 // Collection exists, GetCollection will return nil.
104 func (s *Store) GetCollection(name string) *Collection {
105 if s.collLock != nil {
106 s.collLock.RLock()
107 defer s.collLock.RUnlock()
108 }
109 return s.colls[name]
110 }
111
112 // CreateCollection returns a Collection with the specified name. If the
113 // collection already exists, or if s is read-only, CreateCollection will panic.
114 func (s *Store) CreateCollection(name string, compare gtreap.Compare) *Collectio n {
115 s.assertNotReadOnly()
116
117 s.collLock.Lock()
118 defer s.collLock.Unlock()
119
120 // Try to get the existing Collection again now that we hold the write l ock.
121 if _, ok := s.colls[name]; ok {
122 panic(fmt.Errorf("collection %q already exists", name))
123 }
124
125 // Create a new read/write Collection.
126 coll := &Collection{
127 name: name,
128 rootLock: &sync.RWMutex{},
129 }
130 coll.setRoot(gtreap.NewTreap(compare))
131
132 if s.colls == nil {
133 s.colls = make(map[string]*Collection)
134 }
135 s.colls[name] = coll
136 s.collNames = s.insertCollectionName(name)
137 return coll
138 }
139
140 // insertCollectionName returns a copy of s.collNames with name inserted
141 // in its appropriate sorted position.
142 func (s *Store) insertCollectionName(name string) []string {
143 sidx := sort.SearchStrings(s.collNames, name)
144 r := make([]string, 0, len(s.collNames)+1)
145 return append(append(append(r, s.collNames[:sidx]...), name), s.collName s[sidx:]...)
146 }
147
148 // Collection is a collection of Items.
149 //
150 // Collections belonging to read/write Store instances are, themselves,
151 // read/write. Collections belonging to Snapshot instances are read-only.
152 //
153 // A Collection's zero value is a valid read-only empty Collection, which is not
154 // terribly useful.
155 type Collection struct {
156 name string
157
158 rootLock *sync.RWMutex
159 root *gtreap.Treap
160 }
161
162 func (c *Collection) assertNotReadOnly() {
163 if c.IsReadOnly() {
164 panic("collection is read-only")
165 }
166 }
167
168 // Name returns this Collection's name.
169 func (c *Collection) Name() string { return c.name }
170
171 // IsReadOnly returns true if this Collection is read-only.
172 func (c *Collection) IsReadOnly() bool { return c.rootLock == nil }
173
174 // Min returns the smallest item in the collection.
175 func (c *Collection) Min() gtreap.Item {
176 root := c.currentRoot()
177 if root == nil {
178 return nil
179 }
180 return root.Min()
181 }
182
183 // Max returns the largest item in the collection.
184 func (c *Collection) Max() gtreap.Item {
185 root := c.currentRoot()
186 if root == nil {
187 return nil
188 }
189 return root.Max()
190 }
191
192 // Get returns the item in the Store that matches i, or nil if no such item
193 // exists.
194 func (c *Collection) Get(i gtreap.Item) gtreap.Item {
195 root := c.currentRoot()
196 if root == nil {
197 return nil
198 }
199 return root.Get(i)
200 }
201
202 // Put adds an item to the Store.
203 //
204 // If the Store is read-only, Put will panic.
205 func (c *Collection) Put(i gtreap.Item) {
206 c.assertNotReadOnly()
207
208 // Lock around the entire Upsert operation to serialize Puts.
209 priority := rand.Int()
210 c.rootLock.Lock()
211 c.root = c.root.Upsert(i, priority)
212 c.rootLock.Unlock()
213 }
214
215 // Delete deletes an item from the Collection, if such an item exists.
216 func (c *Collection) Delete(i gtreap.Item) {
217 c.assertNotReadOnly()
218
219 c.rootLock.Lock()
220 c.root = c.root.Delete(i)
221 c.rootLock.Unlock()
222 }
223
224 func (c *Collection) currentRoot() *gtreap.Treap {
225 if c.rootLock != nil {
226 c.rootLock.RLock()
227 defer c.rootLock.RUnlock()
228 }
229 return c.root
230 }
231
232 func (c *Collection) setRoot(root *gtreap.Treap) {
233 if c.rootLock != nil {
234 c.rootLock.Lock()
235 defer c.rootLock.Unlock()
236 }
237 c.root = root
238 }
239
240 // Iterator returns an iterator over the Store, starting at the supplied pivot
241 // item.
242 func (c *Collection) Iterator(pivot gtreap.Item) *gtreap.Iterator {
243 root := c.currentRoot()
244 if root == nil {
245 root = emptyTreap
246 }
247 return root.Iterator(pivot)
248 }
OLDNEW
« 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