Chromium Code Reviews| OLD | NEW |
|---|---|
| (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 dscache | |
| 6 | |
| 7 import ( | |
| 8 "fmt" | |
| 9 "math/rand" | |
| 10 "time" | |
| 11 | |
| 12 ds "github.com/luci/gae/service/datastore" | |
| 13 "github.com/luci/gae/service/memcache" | |
| 14 "github.com/luci/luci-go/common/logging" | |
| 15 ) | |
| 16 | |
| 17 type supportContext struct { | |
| 18 aid string | |
| 19 ns string | |
| 20 | |
| 21 log logging.Logger | |
| 22 mc memcache.Interface | |
| 23 mr *rand.Rand | |
| 24 shardsForKey func(ds.Key) int | |
| 25 } | |
| 26 | |
| 27 func (s *supportContext) numShards(k ds.Key) int { | |
| 28 ret := DefaultShards | |
| 29 if s.shardsForKey != nil { | |
| 30 ret = s.shardsForKey(k) | |
| 31 } | |
| 32 if ret < 1 { | |
| 33 return 0 // disable caching entirely | |
| 34 } | |
| 35 if ret > MaxShards { | |
| 36 ret = MaxShards | |
| 37 } | |
| 38 return ret | |
| 39 } | |
| 40 | |
| 41 func (s *supportContext) mkRandKeys(keys []ds.Key, metas ds.MultiMetaGetter) []s tring { | |
| 42 ret := []string(nil) | |
| 43 for i, key := range keys { | |
| 44 if !metas.GetMetaDefault(i, CacheEnableMeta, true).(bool) { | |
| 45 continue | |
| 46 } | |
| 47 shards := s.numShards(key) | |
| 48 if shards == 0 { | |
| 49 continue | |
| 50 } | |
| 51 if ret == nil { | |
| 52 ret = make([]string, len(keys)) | |
| 53 } | |
| 54 ret[i] = MakeMemcacheKey(s.mr.Intn(shards), key) | |
|
Vadim Sh.
2015/08/06 01:23:34
so shards reduce contention on reads by making wri
iannucci
2015/08/06 01:54:01
Right
| |
| 55 } | |
| 56 if len(ret) == 0 { | |
|
dnj
2015/08/05 18:32:18
Is this necessary? Either it is allocated and gets
iannucci
2015/08/06 01:54:02
o rite
| |
| 57 ret = nil | |
| 58 } | |
| 59 return ret | |
| 60 } | |
| 61 | |
| 62 func (s *supportContext) mkAllKeys(keys []ds.Key) []string { | |
| 63 size := 0 | |
| 64 nums := make([]int, len(keys)) | |
| 65 for i, k := range keys { | |
| 66 shards := s.numShards(k) | |
| 67 nums[i] = shards | |
| 68 size += shards | |
| 69 } | |
| 70 if size == 0 { | |
| 71 return nil | |
| 72 } | |
| 73 ret := make([]string, 0, size) | |
| 74 for i, key := range keys { | |
| 75 keySuffix := HashKey(key) | |
| 76 for shard := 0; shard < nums[i]; shard++ { | |
| 77 ret = append(ret, fmt.Sprintf(KeyFormat, shard, keySuffi x)) | |
| 78 } | |
| 79 } | |
| 80 return ret | |
| 81 } | |
| 82 | |
| 83 // crappyNonce creates a really crappy nonce using math/rand. This is generally | |
|
Vadim Sh.
2015/08/06 01:23:34
lol
| |
| 84 // unacceptable for cryptographic purposes, but since mathrand is the only | |
| 85 // mocked randomness source, we use that. | |
|
dnj
2015/08/05 18:32:18
Worth mentioning that we're using it for sharding,
iannucci
2015/08/06 01:54:01
ya, did
| |
| 86 // | |
| 87 // Do not use this function for anything other than mkRandLockItems or your hair | |
| 88 // will fall out. You've been warned. | |
| 89 // | |
| 90 // TODO(riannucci): make this use crypto/rand instead | |
|
Vadim Sh.
2015/08/06 01:23:34
do you see any exploitation possibilities? Not all
iannucci
2015/08/06 01:54:01
Yeah it's actually fine... just felt dirty extract
| |
| 91 func (s *supportContext) crappyNonce() []byte { | |
| 92 ret := make([]byte, NonceUint32s*4) | |
| 93 for w := uint(0); w < NonceUint32s; w++ { | |
| 94 word := s.mr.Uint32() | |
| 95 for i := uint(0); i < 4; i++ { | |
| 96 ret[(w*4)+i] = byte(word >> (8 * i)) | |
| 97 } | |
| 98 } | |
| 99 return ret | |
| 100 } | |
| 101 | |
| 102 func (s *supportContext) mutation(keys []ds.Key, f func() error) error { | |
| 103 lockItems, lockKeys := s.mkAllLockItems(keys) | |
| 104 if lockItems == nil { | |
| 105 return f() | |
| 106 } | |
| 107 if err := s.mc.SetMulti(lockItems); err != nil { | |
| 108 s.log.Errorf("dscache: mc.SetMulti: %s", err) | |
|
dnj
2015/08/05 18:32:18
This could be a lot of logging if memcache is not
iannucci
2015/08/06 01:54:02
changed to a hard failz
| |
| 109 } | |
| 110 err := f() | |
|
dnj
2015/08/05 18:32:18
if err := f(); err == nil {
...
}
iannucci
2015/08/06 01:54:01
Nah, I return it in the else case.
| |
| 111 if err == nil { | |
| 112 if err := s.mc.DeleteMulti(lockKeys); err != nil { | |
| 113 s.log.Errorf("dscache: mc.DeleteMulti: %s", err) | |
|
dnj
2015/08/05 18:32:18
(same)
iannucci
2015/08/06 01:54:02
a warning now
| |
| 114 } | |
| 115 } | |
| 116 return err | |
| 117 } | |
| 118 | |
| 119 func (s *supportContext) mkRandLockItems(keys []ds.Key, metas ds.MultiMetaGetter ) ([]memcache.Item, []byte) { | |
| 120 mcKeys := s.mkRandKeys(keys, metas) | |
| 121 if len(mcKeys) == 0 { | |
| 122 return nil, nil | |
| 123 } | |
| 124 nonce := s.crappyNonce() | |
| 125 ret := make([]memcache.Item, len(mcKeys)) | |
| 126 for i, k := range mcKeys { | |
| 127 if k == "" { | |
|
Vadim Sh.
2015/08/06 01:23:34
is it possible?
iannucci
2015/08/06 01:54:01
Yeah. This is done if mkRandKeys detects that some
| |
| 128 continue | |
| 129 } | |
| 130 ret[i] = (s.mc.NewItem(k). | |
| 131 SetFlags(uint32(ItemHasLock)). | |
|
Vadim Sh.
2015/08/06 01:23:34
ah, I didn't know about flags. Cool.
| |
| 132 SetExpiration(time.Second * time.Duration(LockTimeSecond s)). | |
|
Vadim Sh.
2015/08/06 01:23:34
why not define LockTimeSeconds to be of type time.
iannucci
2015/08/06 01:54:01
Because SetExpiration truncates anything shorter t
| |
| 133 SetValue(nonce)) | |
| 134 } | |
| 135 return ret, nonce | |
| 136 } | |
| 137 | |
| 138 func (s *supportContext) mkAllLockItems(keys []ds.Key) ([]memcache.Item, []strin g) { | |
| 139 mcKeys := s.mkAllKeys(keys) | |
| 140 if mcKeys == nil { | |
| 141 return nil, nil | |
| 142 } | |
| 143 ret := make([]memcache.Item, len(mcKeys)) | |
| 144 for i := range ret { | |
| 145 ret[i] = (s.mc.NewItem(mcKeys[i]). | |
| 146 SetFlags(uint32(ItemHasLock)). | |
| 147 SetExpiration(time.Second * time.Duration(LockTimeSecond s))) | |
| 148 } | |
| 149 return ret, mcKeys | |
| 150 } | |
| OLD | NEW |