Chromium Code Reviews

Side by Side Diff: go/src/infra/gae/libs/wrapper/memory/datastore_data.go

Issue 1152383003: Simple memory testing for gae/wrapper (Closed) Base URL: https://chromium.googlesource.com/infra/infra.git@better_context_lite
Patch Set: fixes Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff |
OLDNEW
(Empty)
1 // Copyright 2015 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 package memory
6
7 import (
8 "errors"
9 "math/rand"
10 "sync"
11 "sync/atomic"
12
13 "appengine/datastore"
14 pb "appengine_internal/datastore"
15
16 "github.com/mjibson/goon"
17
18 "infra/gae/libs/wrapper"
19 )
20
21 ////////////////////////////////// knrKeeper ///////////////////////////////////
22
23 type knrKeeper struct {
24 lock sync.RWMutex
M-A Ruel 2015/05/25 18:21:09 When the readers hold the lock for a trivial amoun
iannucci 2015/05/27 19:33:31 sgtm, done
25 fun goon.KindNameResolver
26 }
27
28 func newKnrKeeper(fun goon.KindNameResolver) *knrKeeper {
iannucci 2015/05/27 19:33:31 also removed, now embedding non-pointer
29 if fun == nil {
30 fun = goon.DefaultKindName
31 }
32 return &knrKeeper{fun: fun}
33 }
34
35 func (k *knrKeeper) KindNameResolver() goon.KindNameResolver {
36 k.lock.RLock()
37 defer k.lock.RUnlock()
38 return k.fun
39 }
40
41 func (k *knrKeeper) SetKindNameResolver(knr goon.KindNameResolver) {
42 k.lock.Lock()
43 defer k.lock.Unlock()
44 if knr == nil {
45 knr = goon.DefaultKindName
46 }
47 k.fun = knr
48 }
49
50 //////////////////////////////// dataStoreData /////////////////////////////////
51
52 type dataStoreData struct {
53 *wrapper.BrokenFeatures
54 *knrKeeper
55
56 sync.RWMutex
M-A Ruel 2015/05/25 18:21:09 You are sure you want to expose RLock and friends?
iannucci 2015/05/27 19:33:31 yeah maybe not. un-embedded.
57 store *boomStore
58 snap *boomStore
59
60 /* collections:
61 * ents:ns -> key -> value
62 * (rootkind, rootid, __entity_group__,1 ) -> {__version__: int}
63 * (rootkind, rootid, __entity_group_ids __,1) -> {__version__: int}
64 * (__entity_group_ids__,1) -> {__versio n__: int}
65 * idx -> kind,A?,[-?prop]*
66 * idx:ns:kind -> key = nil
67 * idx:ns:kind|prop -> propval|key = [prev val]
68 * idx:ns:kind|-prop -> -propval|key = [next val]
69 * idx:ns:kind|A|?prop|?prop -> A|propval|propval|key = [prev/next va l]|[prev/next val]
70 * idx:ns:kind|?prop|?prop -> propval|propval|key = [prev/next val] |[prev/next val]
71 */
72 }
73
74 ////////////////////////////// New(dataStoreData) //////////////////////////////
75
76 func newDataStoreData() *dataStoreData {
77 store := newBoomStore()
78 return &dataStoreData{
79 BrokenFeatures: wrapper.NewBrokenFeatures(newDSError(pb.Error_IN TERNAL_ERROR)),
M-A Ruel 2015/05/25 18:21:09 By enabling support for dummy BrokenFeatures as ex
iannucci 2015/05/27 19:33:31 not entirely because the default error is still im
M-A Ruel 2015/05/27 20:14:47 Yes but you don't need a function for that, simply
80 knrKeeper: newKnrKeeper(nil),
81 store: store,
82 snap: store.Snapshot(), // empty but better than a nil pointer.
83 }
84 }
85
86 func groupMetaKey(key *datastore.Key) []byte {
87 return keyBytes(noNS, newKey("", "__entity_group__", "", 1, rootKey(key) ))
88 }
89
90 func groupIDsKey(key *datastore.Key) []byte {
91 return keyBytes(noNS, newKey("", "__entity_group_ids__", "", 1, rootKey( key)))
92 }
93
94 func rootIDsKey(kind string) []byte {
95 return keyBytes(noNS, newKey("", "__entity_root_ids__", kind, 0, nil))
96 }
97
98 func curval(ents *boomCollection, key []byte) (int64, error) {
99 var err error
100 v := ents.Get(key)
101 num := int64(0)
102 numData := &propertyList{}
103 if v != nil {
104 err = numData.UnmarshalBinary(v)
M-A Ruel 2015/05/25 18:21:09 UnmarshalBinary mutates propertyList? Do not like.
iannucci 2015/05/27 19:33:31 I'm emulating the appengine sdk's behavior here (e
M-A Ruel 2015/05/27 20:14:47 No.
105 num = (*numData)[0].Value.(int64)
106 }
107 return num, err
108 }
109
110 func plusPlusLocked(ents *boomCollection, key []byte) (int64, error) {
111 v := ents.Get(key)
112
113 num := int64(0)
114 numData := &propertyList{}
115 if v == nil {
116 num++
117 *numData = append(*numData, datastore.Property{Name: "__version_ _"})
118 } else {
119 err := numData.UnmarshalBinary(v)
120 if err != nil {
121 return 0, err
122 }
123 num = (*numData)[0].Value.(int64)
124 num++
125 }
126 (*numData)[0].Value = num
127 incData, err := numData.MarshalBinary()
128 if err != nil {
129 return 0, err
130 }
131 ents.Set(key, incData)
132
133 return num, nil
134 }
135
136 func (d *dataStoreData) entsKeyLocked(key *datastore.Key) (*boomCollection, *dat astore.Key, error) {
137 coll := "ents:" + key.Namespace()
138 ents := d.store.GetCollection(coll)
139 if ents == nil {
140 ents = d.store.SetCollection(coll, nil)
141 }
142
143 if key.Incomplete() {
144 var idKey []byte
145 if key.Parent() == nil {
146 idKey = rootIDsKey(key.Kind())
147 } else {
148 idKey = groupIDsKey(key)
149 }
150
151 id, err := plusPlusLocked(ents, idKey)
152 if err != nil {
153 return nil, nil, err
154 }
155 key = newKey(key.Namespace(), key.Kind(), "", id, key.Parent())
156 }
157
158 return ents, key, nil
159 }
160
161 func putPrelim(ns string, knr goon.KindNameResolver, src interface{}) (key *data store.Key, data *propertyList, err error) {
M-A Ruel 2015/05/25 18:21:09 I'd prefer not using named return values here for
iannucci 2015/05/27 19:33:31 done
162 key = newKeyObj(ns, knr, src)
163 if !KeyCouldBeValid(ns, key, UserKeyOnly) {
164 // TODO(riannucci): different error for Put-ing to reserved Keys ?
165 return nil, nil, datastore.ErrInvalidKey
166 }
167
168 data, err = toPL(src)
169 return
170 }
171
172 func (d *dataStoreData) put(ns string, src interface{}) (*datastore.Key, error) {
173 key, plData, err := putPrelim(ns, d.KindNameResolver(), src)
174 if err != nil {
175 return nil, err
176 }
177 key, err = d.putInner(key, plData)
178 if err != nil {
179 return nil, err
180 }
181 return key, setStructKey(src, key, d.KindNameResolver())
182 }
183
184 func (d *dataStoreData) putInner(key *datastore.Key, data *propertyList) (*datas tore.Key, error) {
185 dataBytes, err := data.MarshalBinary()
186 if err != nil {
187 return nil, err
188 }
189
190 d.Lock()
191 defer d.Unlock()
192
193 ents, key, err := d.entsKeyLocked(key)
194 if err != nil {
195 return nil, err
196 }
197
198 _, err = plusPlusLocked(ents, groupMetaKey(key))
M-A Ruel 2015/05/25 18:21:09 It's fine to group 198 & 199 on one line.
iannucci 2015/05/27 19:33:31 done, am glad I know this trick now.
199 if err != nil {
200 return nil, err
201 }
202
203 old := ents.Get(keyBytes(noNS, key))
204 oldPl := (*propertyList)(nil)
205 if old != nil {
206 oldPl = &propertyList{}
207 err = oldPl.UnmarshalBinary(old)
208 if err != nil {
209 return nil, err
210 }
211 }
212 ents.Set(keyBytes(noNS, key), dataBytes)
213
214 return key, nil
215 }
216
217 func getInner(ns string, knr goon.KindNameResolver, dst interface{}, f func(*dat astore.Key) (*boomCollection, error)) error {
218 key := newKeyObj(ns, knr, dst)
219 if !KeyValid(ns, key, AllowSpecialKeys) {
220 return datastore.ErrInvalidKey
221 }
222
223 ents, err := f(key)
224 if err != nil {
225 return err
226 }
227 if ents == nil {
228 return datastore.ErrNoSuchEntity
M-A Ruel 2015/05/25 18:21:09 That's weird, I would expect f() to do it.
iannucci 2015/05/27 19:33:31 renamed f to make its function clearer
229 }
230 pdata := ents.Get(keyBytes(noNS, key))
231 if pdata == nil {
232 return datastore.ErrNoSuchEntity
233 }
234 pl := &propertyList{}
235 err = pl.UnmarshalBinary(pdata)
236 if err != nil {
237 return err
238 }
239 return fromPL(pl, dst)
240 }
241
242 func (d *dataStoreData) get(ns string, dst interface{}) error {
243 return getInner(ns, d.KindNameResolver(), dst, func(*datastore.Key) (*bo omCollection, error) {
244 d.RLock()
245 defer d.RUnlock()
246 return d.store.Snapshot().GetCollection("ents:" + ns), nil
247 })
248 }
249
250 func (d *dataStoreData) del(ns string, key *datastore.Key) error {
251 if !KeyValid(ns, key, UserKeyOnly) {
252 return datastore.ErrInvalidKey
253 }
254
255 keyBuf := keyBytes(noNS, key)
256
257 d.Lock()
258 defer d.Unlock()
259
260 ents := d.store.GetCollection("ents:" + ns)
261 if ents == nil {
262 return nil
263 }
264
265 _, err := plusPlusLocked(ents, groupMetaKey(key))
266 if err != nil {
267 return err
268 }
269
270 old := ents.Get(keyBuf)
271 oldPl := (*propertyList)(nil)
272 if old != nil {
273 oldPl = &propertyList{}
274 err = oldPl.UnmarshalBinary(old)
M-A Ruel 2015/05/25 18:21:09 Why do you unmarshal an entity when you are about
iannucci 2015/05/27 19:33:31 oh, that's a leftover from the next CL. We load th
275 if err != nil {
276 return err
277 }
278 }
279
280 ents.Delete(keyBuf)
281 return nil
282 }
283
284 ///////////////////////// memContextObj(dataStoreData) /////////////////////////
285
286 func (d *dataStoreData) canApplyTxn(obj memContextObj) bool {
287 // TODO(riannucci): implement with Flush/FlushRevert for persistance.
288
289 txn := obj.(*txnDataStoreData)
M-A Ruel 2015/05/25 18:21:09 There should be a lock to ensure no mutation happe
iannucci 2015/05/27 19:33:31 There is a lock, it's in RunInTransaction. It lock
290 for rk, muts := range txn.muts {
291 if len(muts) == 0 { // read-only
292 continue
293 }
294 k, err := keyFromByteString(withNS, rk)
295 if err != nil {
296 panic(err)
297 }
298 entKey := "ents:" + k.Namespace()
299 mkey := groupMetaKey(k)
300 entsHead := d.store.GetCollection(entKey)
301 entsSnap := txn.snap.GetCollection(entKey)
302 vHead, err := curval(entsHead, mkey)
303 if err != nil {
304 panic(err)
305 }
306 vSnap, err := curval(entsSnap, mkey)
307 if err != nil {
308 panic(err)
309 }
310 if vHead != vSnap {
311 return false
312 }
313 }
314 return true
315 }
316
317 func (d *dataStoreData) applyTxn(r *rand.Rand, obj memContextObj) {
318 txn := obj.(*txnDataStoreData)
319 for _, muts := range txn.muts {
320 if len(muts) == 0 { // read-only
321 continue
322 }
323 for _, m := range muts {
324 var err error
325 if m.data == nil {
326 err = d.del(m.key.Namespace(), m.key)
327 } else {
328 _, err = d.putInner(m.key, m.data)
329 }
330 if err != nil {
331 panic(err)
332 }
333 }
334 }
335 }
336
337 func (d *dataStoreData) mkTxn(o *datastore.TransactionOptions) (memContextObj, e rror) {
338 return &txnDataStoreData{
339 BrokenFeatures: d.BrokenFeatures,
340 parent: d,
341 knrKeeper: newKnrKeeper(d.KindNameResolver()),
342 isXG: o != nil && o.XG,
343 snap: d.store.Snapshot(),
344 muts: map[string][]txnMutation{},
345 }, nil
346 }
347
348 func (d *dataStoreData) endTxn() {}
349
350 /////////////////////////////// txnDataStoreData ///////////////////////////////
351 type txnMutation struct {
352 key *datastore.Key
353 data *propertyList
354 }
355
356 type txnDataStoreData struct {
357 *wrapper.BrokenFeatures
358 *knrKeeper
359 sync.Mutex
360
361 parent *dataStoreData
362
363 // boolean 0 or 1, use atomic.*Int32 to access.
364 closed int32
365 isXG bool
366
367 snap *boomStore
368
369 // string is the raw-bytes encoding of the entity root incl. namespace
370 muts map[string][]txnMutation
371 // TODO(riannucci): account for 'transaction size' limit of 10MB by summ ing
372 // length of encoded keys + values.
373 }
374
375 const xgEGLimit = 25
376
377 /////////////////////// memContextObj(txnDataStoreData) ////////////////////////
378
379 func (*txnDataStoreData) canApplyTxn(memContextObj) bool { return false }
380 func (td *txnDataStoreData) endTxn() {
381 if atomic.LoadInt32(&td.closed) == 1 {
382 panic("cannot end transaction twice")
383 }
384 atomic.StoreInt32(&td.closed, 1)
385 }
386 func (*txnDataStoreData) applyTxn(*rand.Rand, memContextObj) {
387 panic("txnDataStoreData cannot apply transactions")
388 }
389 func (*txnDataStoreData) mkTxn(*datastore.TransactionOptions) (memContextObj, er ror) {
390 return nil, errors.New("datastore: nested transactions are not supported ")
391 }
392
393 /////////////////// wrapper.BrokenFeatures(txnDataStoreData) ///////////////////
394
395 func (td *txnDataStoreData) IsBroken() error {
396 // Slightly different from the SDK... datastore and taskqueue each imple ment
397 // this here, where in the SDK only datastore.transaction.Call does.
398 if atomic.LoadInt32(&td.closed) == 1 {
399 return errors.New("datastore: transaction context has expired")
400 }
401 return td.BrokenFeatures.IsBroken()
402 }
403
404 // writeMutation ensures that this transaction can support the given key/value
405 // mutation.
406 //
407 // if getOnly is true, don't record the actual mutation data, just ensure that
408 // the key is in an included entity group (or add an empty entry for tha t
409 // group).
410 //
411 // if !getOnly && data == nil, this counts as a deletion instead of a Put.
412 //
413 // will return an error if we're crossing too many entity groups.
M-A Ruel 2015/05/25 18:21:08 s/will return/Returns/ s/we're/the keys are/
iannucci 2015/05/27 19:33:31 done
414 func (td *txnDataStoreData) writeMutation(getOnly bool, key *datastore.Key, data *propertyList) error {
415 rk := string(keyBytes(withNS, rootKey(key)))
416
417 td.Lock()
418 defer td.Unlock()
419
420 if _, ok := td.muts[rk]; !ok {
421 limit := 1
422 if td.isXG {
423 limit = xgEGLimit
424 }
425 if len(td.muts)+1 > limit {
426 msg := "cross-group transaction need to be explicitly sp ecified (xg=True)"
427 if td.isXG {
428 msg = "operating on too many entity groups in a single transaction"
429 }
430 return newDSError(pb.Error_BAD_REQUEST, msg)
431 }
432 td.muts[rk] = []txnMutation{}
433 }
434 if !getOnly {
435 td.muts[rk] = append(td.muts[rk], txnMutation{key, data})
436 }
437
438 return nil
439 }
440
441 func (td *txnDataStoreData) put(ns string, src interface{}) (*datastore.Key, err or) {
442 key, plData, err := putPrelim(ns, td.KindNameResolver(), src)
443 if err != nil {
444 return nil, err
445 }
446
447 key, err = func(key *datastore.Key) (*datastore.Key, error) {
M-A Ruel 2015/05/25 18:21:09 func() would work, since it's in a closure. perso
iannucci 2015/05/27 19:33:31 done
448 td.parent.Lock()
449 defer td.parent.Unlock()
450 _, key, err := td.parent.entsKeyLocked(key)
451 return key, err
452 }(key)
453 if err != nil {
454 return nil, err
455 }
456
457 err = td.writeMutation(false, key, plData)
M-A Ruel 2015/05/25 18:21:09 45-458 fit one line
iannucci 2015/05/27 19:33:31 done
458 if err != nil {
459 return nil, err
460 }
461
462 return key, setStructKey(src, key, td.KindNameResolver())
463 }
464
465 func (td *txnDataStoreData) get(ns string, dst interface{}) error {
466 return getInner(ns, td.KindNameResolver(), dst, func(key *datastore.Key) (*boomCollection, error) {
467 err := td.writeMutation(true, key, nil)
M-A Ruel 2015/05/25 18:21:09 Fits one line
iannucci 2015/05/27 19:33:31 done (I wish gofmt did this)
468 if err != nil {
469 return nil, err
470 }
471 return td.snap.GetCollection("ents:" + ns), nil
472 })
473 }
474
475 func (td *txnDataStoreData) del(ns string, key *datastore.Key) error {
476 if !KeyValid(ns, key, UserKeyOnly) {
477 return datastore.ErrInvalidKey
478 }
479
M-A Ruel 2015/05/25 18:21:09 empty line not necessary
iannucci 2015/05/27 19:33:31 done
480 return td.writeMutation(false, key, nil)
481 }
OLDNEW

Powered by Google App Engine