github.com/mjibson/goon@v1.1.0/goon.go (about) 1 /* 2 * Copyright (c) 2012 The Goon Authors 3 * 4 * Permission to use, copy, modify, and distribute this software for any 5 * purpose with or without fee is hereby granted, provided that the above 6 * copyright notice and this permission notice appear in all copies. 7 * 8 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES 9 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 10 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR 11 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 12 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 13 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 14 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 15 */ 16 17 package goon 18 19 import ( 20 "context" 21 "encoding/ascii85" 22 "fmt" 23 "net/http" 24 "path/filepath" 25 "reflect" 26 "runtime" 27 "sync" 28 "time" 29 30 "golang.org/x/crypto/blake2b" 31 "google.golang.org/appengine" 32 "google.golang.org/appengine/datastore" 33 "google.golang.org/appengine/log" 34 "google.golang.org/appengine/memcache" 35 ) 36 37 var ( 38 // LogErrors issues appengine.Context.Errorf on any error. 39 LogErrors = true 40 // LogTimeoutErrors issues appengine.Context.Warningf on memcache timeout errors. 41 LogTimeoutErrors = false 42 43 // MemcachePutTimeoutThreshold is the number of bytes after which the large 44 // timeout setting is added to the small timeout setting. This repeats. 45 // Which means that with a 20 KiB threshold and a 100 KiB memcache payload, 46 // the final timeout is MemcachePutTimeoutSmall + 5*MemcachePutTimeoutLarge 47 MemcachePutTimeoutThreshold = 20 * 1024 // 20 KiB 48 // MemcachePutTimeoutSmall is the minimum time to wait during memcache 49 // Put operations before aborting them and using the datastore. 50 MemcachePutTimeoutSmall = 5 * time.Millisecond 51 // MemcachePutTimeoutLarge is the amount of extra time to wait for larger 52 // memcache Put requests. See also MemcachePutTimeoutThreshold. 53 MemcachePutTimeoutLarge = 1 * time.Millisecond 54 // MemcacheGetTimeout is the amount of time to wait for all memcache Get 55 // requests, per key fetched. Because we can't really know how big entities 56 // we are requesting, this setting should be for the maximum size entity. 57 // The final timeout is limited to the number of maximum sized entities 58 // an RPC result can contain, so the timeout won't grow insanely large 59 // if you're fetching a ton of small entities. 60 MemcacheGetTimeout = 31250 * time.Microsecond // 31.25 milliseconds 61 62 // IgnoreFieldMismatch decides whether *datastore.ErrFieldMismatch errors 63 // should be silently ignored. This allows you to easily remove fields from structs. 64 IgnoreFieldMismatch = true 65 ) 66 67 var ( 68 // Determines if memcache.PutMulti errors are returned by goon. 69 // Currently only meant for use during goon development testing. 70 propagateMemcachePutError = false 71 ) 72 73 // Goon holds the app engine context and the request memory cache. 74 type Goon struct { 75 Context context.Context 76 cache *cache 77 inTransaction bool 78 txnCacheLock sync.Mutex // protects toDelete / toDeleteMC 79 toDelete map[string]struct{} 80 toDeleteMC map[string]struct{} 81 // KindNameResolver is used to determine what Kind to give an Entity. 82 // Defaults to DefaultKindName 83 KindNameResolver KindNameResolver 84 } 85 86 // MemcacheKey returns the string form of the provided datastore key. 87 var MemcacheKey = func(k *datastore.Key) string { 88 return k.Encode() 89 } 90 91 // Versioning, so that incompatible changes to the cache system won't cause problems 92 var cacheKeyPrefix = fmt.Sprintf("g%X:", serializationFormatVersion) 93 94 // cacheKey returns the fully legal string key used for cache systems 95 func cacheKey(k *datastore.Key) string { 96 // By default we just use the prefix + MemcacheKey result 97 key := cacheKeyPrefix + MemcacheKey(k) 98 // However if the resulting key length exceeds the maximum allowed .. 99 if len(key) > memcacheMaxKeySize { 100 // .. then we need to shorten it while still staying unique. 101 // We pass the key through the BLAKE2b hash function, 102 // which is cryptographically secure but also very fast. 103 // We request an output of 24 bytes (192-bit) for speed reasons. 104 h, err := blake2b.New(24, nil) 105 if err != nil { 106 panic(fmt.Sprintf("Unexpected error initializing blake2b: %v", err)) 107 } 108 h.Write([]byte(key)) 109 hash := h.Sum(make([]byte, 0, 24)) 110 // After hashing, we encode the results with ascii85. 111 // Ascii85 works in 4 byte chunks, generating 5 letters for each. 112 // Which means we turn our 24 byte hash into a 30 letter string. 113 // 114 // We want letters instead of using the hash directly for debug reasons. 115 // As it will be easier for people to observe & manage keys manually. 116 // 117 // We aim the length of 30, because 32 is the maximum length 118 // where the Go std map does key lookups directly without hashing. 119 encoded := make([]byte, 30, 30) 120 ascii85.Encode(encoded, hash) 121 key = string(encoded) 122 } 123 return key 124 } 125 126 // Returns the duration that should be used for a memcache.GetMulti timeout. 127 func memcacheGetTimeout(keyCount int) time.Duration { 128 // Takes the number of keys given to memcache.GetMulti, 129 // clamps that to the maximum number of max sized items, 130 // and multiplies it with the per-max-sized-item timeout. 131 if keyCount > memcacheMaxRPCSize/memcacheMaxItemSize { 132 keyCount = memcacheMaxRPCSize / memcacheMaxItemSize 133 } 134 return time.Duration(keyCount) * MemcacheGetTimeout 135 } 136 137 // Returns the duration that should be used for a memcache.PutMulti timeout. 138 func memcachePutTimeout(payloadSize int) time.Duration { 139 // Takes the payload size given to memcache.PutMulti, 140 // and returns a dynamic duration based on that size. 141 return MemcachePutTimeoutSmall + time.Duration(payloadSize/MemcachePutTimeoutThreshold)*MemcachePutTimeoutLarge 142 } 143 144 // Memcache limits 145 // These are based on the constants in SDK/google/appengine/api/memcache/memcache_stub.py 146 // Also documented in https://cloud.google.com/appengine/docs/standard/go/memcache/ 147 const ( 148 // The Google provided overhead is 73 bytes, which seems wrong in practice. 149 // Testing has shown that Put/PutMulti will succeed with even a lower overhead. 150 // However by 'succeed' I mean not erroring. The value never gets stored. 151 // The actual overhead seems to be 76 at which point the items will 152 // start being stored in memcache and show up in the statistics. 153 memcacheOverhead = 76 154 memcacheMaxKeySize = 250 155 memcacheMaxItemSize = 1 << 20 // 1 MiB 156 memcacheMaxValueSize = memcacheMaxItemSize - memcacheMaxKeySize - memcacheOverhead 157 memcacheMaxRPCSize = 32 << 20 // 32 MiB 158 ) 159 160 // Datastore limits 161 const ( 162 datastoreGetMultiMaxItems = 1000 163 datastorePutMultiMaxItems = 500 164 datastoreDeleteMultiMaxItems = 500 165 166 // The maximum GetMulti result RPC size was determined experimentally on 2019-05-20 167 datastoreGetMultiMaxRPCSize = 50 << 20 // 50 MiB 168 ) 169 170 // NewGoon creates a new Goon object from the given request. 171 func NewGoon(r *http.Request) *Goon { 172 return FromContext(appengine.NewContext(r)) 173 } 174 175 // FromContext creates a new Goon object from the given appengine Context. 176 // Useful with profiling packages like appstats. 177 func FromContext(c context.Context) *Goon { 178 return &Goon{ 179 Context: c, 180 cache: newCache(defaultCacheLimit), 181 KindNameResolver: DefaultKindName, 182 } 183 } 184 185 func (g *Goon) error(err error) { 186 if !LogErrors { 187 return 188 } 189 _, filename, line, ok := runtime.Caller(1) 190 if ok { 191 log.Errorf(g.Context, "goon - %s:%d - %v", filepath.Base(filename), line, err) 192 } else { 193 log.Errorf(g.Context, "goon - %v", err) 194 } 195 } 196 197 func (g *Goon) timeoutError(err error) { 198 if LogTimeoutErrors { 199 log.Warningf(g.Context, "goon memcache timeout: %v", err) 200 } 201 } 202 203 func (g *Goon) memcacheDeleteError(err error) { 204 if err == nil || !LogErrors { 205 return 206 } 207 if me, ok := err.(appengine.MultiError); ok { 208 for i := range me { 209 if me[i] != nil && me[i] != memcache.ErrCacheMiss { 210 err = me[i] 211 goto reportError 212 } 213 } 214 return 215 } 216 reportError: 217 _, filename, line, ok := runtime.Caller(1) 218 if ok { 219 log.Errorf(g.Context, "goon - %s:%d - memcache.DeleteMulti failed: %v - the goon cache may be out of sync now!", filepath.Base(filename), line, err) 220 } else { 221 log.Errorf(g.Context, "goon - memcache.DeleteMulti failed: %v - the goon cache may be out of sync now!", err) 222 } 223 } 224 225 func (g *Goon) extractKeys(src interface{}, putRequest bool) ([]*datastore.Key, error) { 226 v := reflect.Indirect(reflect.ValueOf(src)) 227 if v.Kind() != reflect.Slice { 228 return nil, fmt.Errorf("goon: value must be a slice or pointer-to-slice") 229 } 230 l := v.Len() 231 232 keys := make([]*datastore.Key, l) 233 for i := 0; i < l; i++ { 234 vi := v.Index(i) 235 key, hasStringId, err := g.getStructKey(vi.Interface()) 236 if err != nil { 237 return nil, err 238 } 239 if !putRequest && key.Incomplete() { 240 return nil, fmt.Errorf("goon: cannot find a key for struct - %v", vi.Interface()) 241 } else if putRequest && key.Incomplete() && hasStringId { 242 return nil, fmt.Errorf("goon: empty string id on put") 243 } 244 keys[i] = key 245 } 246 return keys, nil 247 } 248 249 // Key is the same as KeyError, except nil is returned on error or if the key 250 // is incomplete. 251 func (g *Goon) Key(src interface{}) *datastore.Key { 252 if k, err := g.KeyError(src); err == nil { 253 return k 254 } 255 return nil 256 } 257 258 // Kind returns src's datastore Kind or "" on error. 259 func (g *Goon) Kind(src interface{}) string { 260 if k, err := g.KeyError(src); err == nil { 261 return k.Kind() 262 } 263 return "" 264 } 265 266 // KeyError returns the key of src based on its properties. 267 func (g *Goon) KeyError(src interface{}) (*datastore.Key, error) { 268 key, _, err := g.getStructKey(src) 269 return key, err 270 } 271 272 // RunInTransaction runs f in a transaction. It calls f with a transaction 273 // context tg that f should use for all App Engine operations. Neither cache nor 274 // memcache are used or set during a transaction. 275 // 276 // Otherwise similar to appengine/datastore.RunInTransaction: 277 // https://developers.google.com/appengine/docs/go/datastore/reference#RunInTransaction 278 func (g *Goon) RunInTransaction(f func(tg *Goon) error, opts *datastore.TransactionOptions) error { 279 var ng *Goon 280 err := datastore.RunInTransaction(g.Context, func(tc context.Context) error { 281 ng = &Goon{ 282 Context: tc, 283 inTransaction: true, 284 toDelete: make(map[string]struct{}), 285 toDeleteMC: make(map[string]struct{}), 286 KindNameResolver: g.KindNameResolver, 287 } 288 return f(ng) 289 }, opts) 290 291 if err == nil { 292 ng.txnCacheLock.Lock() 293 defer ng.txnCacheLock.Unlock() 294 if len(ng.toDeleteMC) > 0 { 295 var memkeys []string 296 for k := range ng.toDeleteMC { 297 memkeys = append(memkeys, k) 298 } 299 g.memcacheDeleteError(memcache.DeleteMulti(g.Context, memkeys)) 300 } 301 for k := range ng.toDelete { 302 g.cache.Delete(k) 303 } 304 } else { 305 g.error(err) 306 } 307 308 return err 309 } 310 311 // Put saves the entity src into the datastore based on src's key k. If k 312 // is an incomplete key, the returned key will be a unique key generated by 313 // the datastore. 314 func (g *Goon) Put(src interface{}) (*datastore.Key, error) { 315 v := reflect.ValueOf(src) 316 if v.Kind() != reflect.Ptr { 317 return nil, fmt.Errorf("goon: expected pointer to a struct, got %#v", src) 318 } 319 ks, err := g.PutMulti([]interface{}{src}) 320 if err != nil { 321 if me, ok := err.(appengine.MultiError); ok { 322 return nil, me[0] 323 } 324 return nil, err 325 } 326 return ks[0], nil 327 } 328 329 // PutMulti is a batch version of Put. 330 // 331 // src must be a *[]S, *[]*S, *[]I, []S, []*S, or []I, for some struct type S, 332 // or some interface type I. If *[]I or []I, each element must be a struct pointer. 333 func (g *Goon) PutMulti(src interface{}) ([]*datastore.Key, error) { 334 keys, err := g.extractKeys(src, true) // allow incomplete keys on a Put request 335 if err != nil { 336 return nil, err 337 } 338 339 v := reflect.Indirect(reflect.ValueOf(src)) 340 mu := new(sync.Mutex) 341 multiErr, any := make(appengine.MultiError, len(keys)), false 342 goroutines := (len(keys)-1)/datastorePutMultiMaxItems + 1 343 var wg sync.WaitGroup 344 wg.Add(goroutines) 345 for i := 0; i < goroutines; i++ { 346 go func(i int) { 347 defer wg.Done() 348 lo := i * datastorePutMultiMaxItems 349 hi := (i + 1) * datastorePutMultiMaxItems 350 if hi > len(keys) { 351 hi = len(keys) 352 } 353 rkeys, pmerr := datastore.PutMulti(g.Context, keys[lo:hi], v.Slice(lo, hi).Interface()) 354 if pmerr != nil { 355 mu.Lock() 356 any = true // this flag tells PutMulti to return multiErr later 357 mu.Unlock() 358 merr, ok := pmerr.(appengine.MultiError) 359 if !ok { 360 g.error(pmerr) 361 for j := lo; j < hi; j++ { 362 multiErr[j] = pmerr 363 } 364 return 365 } 366 copy(multiErr[lo:hi], merr) 367 } 368 369 for i, key := range keys[lo:hi] { 370 if multiErr[lo+i] != nil { 371 continue // there was an error writing this value, go to next 372 } 373 vi := v.Index(lo + i).Interface() 374 if key.Incomplete() { 375 g.setStructKey(vi, rkeys[i]) 376 keys[lo+i] = rkeys[i] 377 } 378 } 379 }(i) 380 } 381 wg.Wait() 382 383 // Caches need to be updated after the datastore to prevent a common race condition, 384 // where a concurrent request will fetch the not-yet-updated data from the datastore 385 // and populate the caches with it. 386 cachekeys := make([]string, 0, len(keys)) 387 for _, key := range keys { 388 if !key.Incomplete() { 389 cachekeys = append(cachekeys, cacheKey(key)) 390 } 391 } 392 if g.inTransaction { 393 g.txnCacheLock.Lock() 394 for _, ck := range cachekeys { 395 g.toDelete[ck] = struct{}{} 396 g.toDeleteMC[ck] = struct{}{} 397 } 398 g.txnCacheLock.Unlock() 399 } else { 400 g.cache.DeleteMulti(cachekeys) 401 g.memcacheDeleteError(memcache.DeleteMulti(g.Context, cachekeys)) 402 } 403 404 if any { 405 return keys, realError(multiErr) 406 } 407 return keys, nil 408 } 409 410 // FlushLocalCache clears the local memory cache. 411 func (g *Goon) FlushLocalCache() { 412 g.cache.Flush() 413 } 414 415 // ClearCache removes the provided entity from cache. 416 // Takes either *S or *datastore.Key. 417 // The 'mem' and 'local' booleans dictate the type of caches to clear. 418 func (g *Goon) ClearCache(src interface{}, mem, local bool) error { 419 if !mem && !local { 420 // Nothing to clear .. 421 return nil 422 } 423 var srcs interface{} 424 if key, ok := src.(*datastore.Key); ok { 425 srcs = []*datastore.Key{key} 426 } else { 427 v := reflect.ValueOf(src) 428 if v.Kind() != reflect.Ptr { 429 return fmt.Errorf("goon: expected pointer to a struct, got %#v", src) 430 } 431 srcs = []interface{}{src} 432 } 433 err := g.ClearCacheMulti(srcs, mem, local) 434 if err != nil { 435 // Look for an embedded error if it's multi 436 if me, ok := err.(appengine.MultiError); ok { 437 return me[0] 438 } 439 } 440 return err 441 } 442 443 // ClearCacheMulti removes the provided entities from cache. 444 // Takes either []*S or []*datastore.Key. 445 // The 'mem' and 'local' booleans dictate the type of caches to clear. 446 func (g *Goon) ClearCacheMulti(src interface{}, mem, local bool) error { 447 if !mem && !local { 448 // Nothing to clear .. 449 return nil 450 } 451 keys, ok := src.([]*datastore.Key) 452 if !ok { 453 var err error 454 keys, err = g.extractKeys(src, false) // don't allow incomplete keys on a Clear request 455 if err != nil { 456 return err 457 } 458 } 459 if len(keys) == 0 { 460 return nil 461 // not an error, and it was "successful", so return nil 462 } 463 cachekeys := make([]string, 0, len(keys)) 464 for _, key := range keys { 465 cachekeys = append(cachekeys, cacheKey(key)) 466 } 467 if g.inTransaction { 468 g.txnCacheLock.Lock() 469 for _, ck := range cachekeys { 470 if local { 471 g.toDelete[ck] = struct{}{} 472 } 473 if mem { 474 g.toDeleteMC[ck] = struct{}{} 475 } 476 } 477 g.txnCacheLock.Unlock() 478 } else { 479 if local { 480 g.cache.DeleteMulti(cachekeys) 481 } 482 if mem { 483 g.memcacheDeleteError(memcache.DeleteMulti(g.Context, cachekeys)) 484 } 485 } 486 return nil 487 } 488 489 type memcacheTask struct { 490 items []*memcache.Item 491 size int 492 } 493 494 // NB! putMemcache is expected to treat cacheItem as immutable! 495 func (g *Goon) putMemcache(citems []*cacheItem) error { 496 // Go over all the cache items and generate memcache tasks from them, 497 // by splitting them up based on payload size 498 items := make([]*memcache.Item, len(citems)) 499 tasks := make([]memcacheTask, 0, 1) // In most cases there's just a single task 500 lastSplit := 0 501 payloadSize := 0 502 for i, citem := range citems { 503 items[i] = &memcache.Item{ 504 Key: citem.key, 505 Value: citem.value, 506 } 507 itemSize := memcacheOverhead + len(citem.key) + len(citem.value) 508 if payloadSize+itemSize > memcacheMaxRPCSize { 509 tasks = append(tasks, memcacheTask{items: items[lastSplit:i], size: payloadSize}) 510 lastSplit = i 511 payloadSize = 0 512 } 513 payloadSize += itemSize 514 } 515 tasks = append(tasks, memcacheTask{items: items[lastSplit:len(citems)], size: payloadSize}) 516 // Execute all the tasks with goroutines 517 count := len(tasks) 518 errc := make(chan error, count) 519 for i := 0; i < count; i++ { 520 go func(idx int) { 521 tc, cf := context.WithTimeout(g.Context, memcachePutTimeout(tasks[idx].size)) 522 errc <- memcache.SetMulti(tc, tasks[idx].items) 523 cf() 524 }(i) 525 } 526 // Wait for all goroutines to finish and log any errors. 527 // Also return a non-deterministic error if there are any. 528 var rerr error 529 for i := 0; i < count; i++ { 530 err := <-errc 531 if err != nil { 532 if appengine.IsTimeoutError(err) { 533 g.timeoutError(err) 534 } else { 535 g.error(err) 536 } 537 rerr = err 538 } 539 } 540 return rerr 541 } 542 543 // Get loads the entity based on dst's key into dst 544 // If there is no such entity for the key, Get returns 545 // datastore.ErrNoSuchEntity. 546 func (g *Goon) Get(dst interface{}) error { 547 v := reflect.ValueOf(dst) 548 if v.Kind() != reflect.Ptr { 549 return fmt.Errorf("goon: expected pointer to a struct, got %#v", dst) 550 } 551 if !v.CanSet() { 552 v = v.Elem() 553 } 554 dsts := []interface{}{dst} 555 if err := g.GetMulti(dsts); err != nil { 556 // Look for an embedded error if it's multi 557 if me, ok := err.(appengine.MultiError); ok { 558 return me[0] 559 } 560 // Not multi, normal error 561 return err 562 } 563 v.Set(reflect.Indirect(reflect.ValueOf(dsts[0]))) 564 return nil 565 } 566 567 // GetMulti is a batch version of Get. 568 // 569 // dst must be a *[]S, *[]*S, *[]I, []S, []*S, or []I, for some struct type S, 570 // or some interface type I. If *[]I or []I, each element must be a struct pointer. 571 func (g *Goon) GetMulti(dst interface{}) error { 572 keys, err := g.extractKeys(dst, false) // don't allow incomplete keys on a Get request 573 if err != nil { 574 return err 575 } 576 577 v := reflect.Indirect(reflect.ValueOf(dst)) 578 579 multiErr, anyErr := make(appengine.MultiError, len(keys)), false 580 var extraErr error 581 582 if g.inTransaction { 583 // todo: support getMultiLimit in transactions 584 if err := datastore.GetMulti(g.Context, keys, v.Interface()); err != nil { 585 if merr, ok := err.(appengine.MultiError); ok { 586 for i := 0; i < len(keys); i++ { 587 if merr[i] != nil && (!IgnoreFieldMismatch || !errFieldMismatch(merr[i])) { 588 anyErr = true // this flag tells GetMulti to return multiErr later 589 multiErr[i] = merr[i] 590 } 591 } 592 } else { 593 g.error(err) 594 anyErr = true // this flag tells GetMulti to return multiErr later 595 for i := 0; i < len(keys); i++ { 596 multiErr[i] = err 597 } 598 } 599 if anyErr { 600 return realError(multiErr) 601 } 602 } 603 return nil 604 } 605 606 var dskeys []*datastore.Key 607 var dsdst []interface{} 608 var dixs []int // dskeys[5] === keys[dixs[5]] 609 610 var mckeys []string 611 var mixs []int // mckeys[3] =~= keys[mixs[3]] 612 613 lckeys := make([]string, 0, len(keys)) 614 for _, key := range keys { 615 // NB! Current implementation has optimizations in place 616 // that expect memcache & local cache keys to match. 617 lckeys = append(lckeys, cacheKey(key)) 618 } 619 620 lcvalues := g.cache.GetMulti(lckeys) 621 622 for i, key := range keys { 623 vi := v.Index(i) 624 if vi.Kind() == reflect.Struct { 625 vi = vi.Addr() 626 } 627 d := vi.Interface() 628 629 if data := lcvalues[i]; data != nil { 630 // Attempt to deserialize the cached value into the struct 631 err := deserializeStruct(d, data) 632 if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) { 633 if err == datastore.ErrNoSuchEntity || errFieldMismatch(err) { 634 anyErr = true // this flag tells GetMulti to return multiErr later 635 multiErr[i] = err 636 } else { 637 g.error(err) 638 return err 639 } 640 } 641 } else { 642 mckeys = append(mckeys, lckeys[i]) 643 mixs = append(mixs, i) 644 dskeys = append(dskeys, key) 645 dsdst = append(dsdst, d) 646 dixs = append(dixs, i) 647 } 648 } 649 650 if len(mckeys) == 0 { 651 if anyErr { 652 return realError(multiErr) 653 } 654 return nil 655 } 656 657 // memcache.GetMulti is limited to memcacheMaxRPCSize for the data returned. 658 // Thus if the returned data is bigger than memcacheMaxRPCSize - memcacheMaxItemSize 659 // then we do another memcache.GetMulti on the missing keys. 660 memvalues := make(map[string]*memcache.Item, len(mckeys)) 661 mcKeysSet := make(map[string]struct{}, len(mckeys)) 662 for _, mk := range mckeys { 663 mcKeysSet[mk] = struct{}{} 664 } 665 for { 666 nextmckeys := make([]string, 0, len(mcKeysSet)) 667 for mk := range mcKeysSet { 668 nextmckeys = append(nextmckeys, mk) 669 } 670 tc, cf := context.WithTimeout(g.Context, memcacheGetTimeout(len(nextmckeys))) 671 mvs, err := memcache.GetMulti(tc, nextmckeys) 672 cf() 673 // timing out or another error from memcache isn't something to fail over, but do log it 674 if appengine.IsTimeoutError(err) { 675 g.timeoutError(err) 676 break 677 } else if err != nil { 678 g.error(err) 679 break 680 } 681 payloadSize := 0 682 for k, v := range mvs { 683 memvalues[k] = v 684 payloadSize += memcacheOverhead + len(v.Key) + len(v.Value) 685 delete(mcKeysSet, k) 686 } 687 if len(mcKeysSet) == 0 || payloadSize < memcacheMaxRPCSize-memcacheMaxItemSize { 688 break 689 } 690 } 691 692 if len(memvalues) > 0 { 693 // since memcache fetch was successful, reset the datastore fetch list and repopulate it 694 dskeys = dskeys[:0] 695 dsdst = dsdst[:0] 696 dixs = dixs[:0] 697 // we only want to check the returned map if there weren't any errors 698 // unlike the datastore, memcache will return a smaller map with no error if some of the keys were missed 699 700 for i, m := range mckeys { 701 d := v.Index(mixs[i]).Interface() 702 if v.Index(mixs[i]).Kind() == reflect.Struct { 703 d = v.Index(mixs[i]).Addr().Interface() 704 } 705 if s, present := memvalues[m]; present { 706 // Mirror any memcache entries in local cache 707 g.cache.Set(&cacheItem{key: m, value: s.Value}) 708 // Attempt to deserialize the cached value into the struct 709 err := deserializeStruct(d, s.Value) 710 if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) { 711 if err == datastore.ErrNoSuchEntity || errFieldMismatch(err) { 712 anyErr = true // this flag tells GetMulti to return multiErr later 713 multiErr[mixs[i]] = err 714 } else { 715 g.error(err) 716 return err 717 } 718 } 719 } else { 720 dskeys = append(dskeys, keys[mixs[i]]) 721 dsdst = append(dsdst, d) 722 dixs = append(dixs, mixs[i]) 723 } 724 } 725 if len(dskeys) == 0 { 726 if anyErr { 727 return realError(multiErr) 728 } 729 return nil 730 } 731 } 732 733 mu := new(sync.Mutex) 734 goroutines := (len(dskeys)-1)/datastoreGetMultiMaxItems + 1 735 var wg sync.WaitGroup 736 wg.Add(goroutines) 737 for i := 0; i < goroutines; i++ { 738 go func(i int) { 739 defer wg.Done() 740 lo := i * datastoreGetMultiMaxItems 741 hi := (i + 1) * datastoreGetMultiMaxItems 742 if hi > len(dskeys) { 743 hi = len(dskeys) 744 } 745 toCache := make([]*cacheItem, 0, hi-lo) 746 propLists := make([]datastore.PropertyList, hi-lo) 747 handleProp := func(i, idx int, exists bool) { 748 // Serialize the properties 749 data, err := serializeProperties(propLists[i], exists) 750 if err != nil { 751 g.error(err) 752 multiErr[idx] = err 753 return 754 } 755 // Prepare the properties for caching 756 toCache = append(toCache, &cacheItem{key: lckeys[idx], value: data}) 757 // Deserialize the properties into a struct 758 if exists { 759 err = deserializeProperties(dsdst[lo+i], propLists[i]) 760 if err != nil && (!IgnoreFieldMismatch || !errFieldMismatch(err)) { 761 multiErr[idx] = err 762 } 763 } 764 } 765 gmerr := datastore.GetMulti(g.Context, dskeys[lo:hi], propLists) 766 if gmerr != nil { 767 mu.Lock() 768 anyErr = true // this flag tells GetMulti to return multiErr later 769 mu.Unlock() 770 merr, ok := gmerr.(appengine.MultiError) 771 if !ok { 772 g.error(gmerr) 773 for j := lo; j < hi; j++ { 774 multiErr[j] = gmerr 775 } 776 return 777 } 778 for i, idx := range dixs[lo:hi] { 779 if merr[i] == nil { 780 handleProp(i, idx, true) 781 } else { 782 if merr[i] == datastore.ErrNoSuchEntity { 783 handleProp(i, idx, false) 784 } 785 multiErr[idx] = merr[i] 786 } 787 } 788 } else { 789 for i, idx := range dixs[lo:hi] { 790 handleProp(i, idx, true) 791 } 792 } 793 if len(toCache) > 0 { 794 // Populate memcache in a goroutine because there's network involved 795 // and we can be doing useful work while waiting for I/O 796 errc := make(chan error) 797 go func() { 798 errc <- g.putMemcache(toCache) 799 }() 800 // Populate local cache 801 g.cache.SetMulti(toCache) 802 // Wait for memcache population to finish 803 err := <-errc 804 // .. but only propagate the memcache error if configured to do so 805 if propagateMemcachePutError && err != nil { 806 mu.Lock() 807 extraErr = err 808 mu.Unlock() 809 } 810 } 811 }(i) 812 } 813 wg.Wait() 814 if anyErr { 815 return realError(multiErr) 816 } else if extraErr != nil { 817 return extraErr 818 } 819 return nil 820 } 821 822 // Delete deletes the provided entity. 823 // Takes either *S or *datastore.Key. 824 func (g *Goon) Delete(src interface{}) error { 825 var srcs interface{} 826 if key, ok := src.(*datastore.Key); ok { 827 srcs = []*datastore.Key{key} 828 } else { 829 v := reflect.ValueOf(src) 830 if v.Kind() != reflect.Ptr { 831 return fmt.Errorf("goon: expected pointer to a struct, got %#v", src) 832 } 833 srcs = []interface{}{src} 834 } 835 err := g.DeleteMulti(srcs) 836 if err != nil { 837 // Look for an embedded error if it's multi 838 if me, ok := err.(appengine.MultiError); ok { 839 return me[0] 840 } 841 } 842 return err 843 } 844 845 // DeleteMulti is a batch version of Delete. 846 // Takes either []*S or []*datastore.Key. 847 func (g *Goon) DeleteMulti(src interface{}) error { 848 keys, ok := src.([]*datastore.Key) 849 if !ok { 850 var err error 851 keys, err = g.extractKeys(src, false) // don't allow incomplete keys on a Delete request 852 if err != nil { 853 return err 854 } 855 } 856 if len(keys) == 0 { 857 return nil 858 // not an error, and it was "successful", so return nil 859 } 860 861 mu := new(sync.Mutex) 862 multiErr, any := make(appengine.MultiError, len(keys)), false 863 goroutines := (len(keys)-1)/datastoreDeleteMultiMaxItems + 1 864 var wg sync.WaitGroup 865 wg.Add(goroutines) 866 for i := 0; i < goroutines; i++ { 867 go func(i int) { 868 defer wg.Done() 869 lo := i * datastoreDeleteMultiMaxItems 870 hi := (i + 1) * datastoreDeleteMultiMaxItems 871 if hi > len(keys) { 872 hi = len(keys) 873 } 874 dmerr := datastore.DeleteMulti(g.Context, keys[lo:hi]) 875 if dmerr != nil { 876 mu.Lock() 877 any = true // this flag tells DeleteMulti to return multiErr later 878 mu.Unlock() 879 merr, ok := dmerr.(appengine.MultiError) 880 if !ok { 881 g.error(dmerr) 882 for j := lo; j < hi; j++ { 883 multiErr[j] = dmerr 884 } 885 return 886 } 887 copy(multiErr[lo:hi], merr) 888 } 889 }(i) 890 } 891 wg.Wait() 892 893 // Caches need to be updated after the datastore to prevent a common race condition, 894 // where a concurrent request will fetch the not-yet-updated data from the datastore 895 // and populate the caches with it. 896 cachekeys := make([]string, 0, len(keys)) 897 for _, key := range keys { 898 cachekeys = append(cachekeys, cacheKey(key)) 899 } 900 if g.inTransaction { 901 g.txnCacheLock.Lock() 902 for _, ck := range cachekeys { 903 g.toDelete[ck] = struct{}{} 904 g.toDeleteMC[ck] = struct{}{} 905 } 906 g.txnCacheLock.Unlock() 907 } else { 908 g.cache.DeleteMulti(cachekeys) 909 g.memcacheDeleteError(memcache.DeleteMulti(g.Context, cachekeys)) 910 } 911 912 if any { 913 return realError(multiErr) 914 } 915 return nil 916 } 917 918 // NotFound returns true if err is an appengine.MultiError and err[idx] is a datastore.ErrNoSuchEntity. 919 func NotFound(err error, idx int) bool { 920 if merr, ok := err.(appengine.MultiError); ok { 921 return idx < len(merr) && merr[idx] == datastore.ErrNoSuchEntity 922 } 923 return false 924 } 925 926 // errFieldMismatch returns true if err is *datastore.ErrFieldMismatch 927 func errFieldMismatch(err error) bool { 928 _, ok := err.(*datastore.ErrFieldMismatch) 929 return ok 930 } 931 932 // Returns a single error if each error in MultiError is the same 933 // otherwise, returns multiError or nil (if multiError is empty) 934 func realError(multiError appengine.MultiError) error { 935 if len(multiError) == 0 { 936 return nil 937 } 938 init := multiError[0] 939 // some errors are *always* returned in MultiError form from the datastore 940 if _, ok := init.(*datastore.ErrFieldMismatch); ok { // returned in GetMulti 941 return multiError 942 } 943 if init == datastore.ErrInvalidEntityType || // returned in GetMulti 944 init == datastore.ErrNoSuchEntity { // returned in GetMulti 945 return multiError 946 } 947 // check if all errors are the same 948 for i := 1; i < len(multiError); i++ { 949 // since type error could hold structs, pointers, etc, 950 // the only way to compare non-nil errors is by their string output 951 if init == nil || multiError[i] == nil { 952 if init != multiError[i] { 953 return multiError 954 } 955 } else if init.Error() != multiError[i].Error() { 956 return multiError 957 } 958 } 959 // datastore.ErrInvalidKey is returned as a single error in PutMulti 960 return init 961 }