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 |