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 "bytes" | |
9 "crypto/sha1" | |
10 "encoding/base64" | |
11 "fmt" | |
12 "sync" | |
13 "time" | |
14 | |
15 "github.com/luci/gae/service/datastore" | |
16 "github.com/luci/gae/service/info" | |
17 "github.com/luci/gae/service/memcache" | |
18 "github.com/luci/luci-go/common/clock" | |
19 "golang.org/x/net/context" | |
20 ) | |
21 | |
22 var ( | |
23 // InstanceEnabledStatic allows you to statically (e.g. in an init() fun ction) | |
24 // bypass this filter by setting it to false. This takes effect when the | |
25 // application calls IsGloballyEnabled. | |
26 InstanceEnabledStatic = true | |
27 | |
28 // LockTimeSeconds is the number of seconds that a "lock" memcache entry will | |
29 // have its expiration set to. It's set to just over half of the fronten d | |
30 // request handler timeout (currently 60 seconds). | |
31 LockTimeSeconds = 31 | |
32 | |
33 // CacheTimeSeconds is the default number of seconds that a positively c ached | |
34 // entity will be retained (memcache contention notwithstanding). A valu e of | |
35 // 0 is infinite. | |
36 CacheTimeSeconds int64 = int64((time.Hour * 24).Seconds()) | |
dnj
2015/08/07 16:30:06
WDYT about just making this a "time.Duration" w/ t
iannucci
2015/08/07 19:41:00
I mentioned this elsewhere, but memcache truncates
| |
37 | |
38 // CompressionThreshold is the number of bytes of entity value after whi ch | |
39 // compression kicks in. | |
40 CompressionThreshold = 860 | |
41 | |
42 // DefaultShards is the default number of key sharding to do. | |
43 DefaultShards int = 1 | |
44 | |
45 // DefaultEnable indicates whether or not caching is globally enabled or | |
46 // disabled by default. Can still be overridden by CacheEnableMeta. | |
47 DefaultEnabled = true | |
48 ) | |
49 | |
50 const ( | |
51 MemcacheVersion = "1" | |
52 | |
53 // KeyFormat is the format string used to generate memcache keys. It's | |
54 // gae:<version>:<shard#>:<base64_std_nopad(sha1(datastore.Key))> | |
55 KeyFormat = "gae:" + MemcacheVersion + ":%x:%s" | |
56 Sha1B64Padding = 1 | |
57 Sha1B64Size = 28 - Sha1B64Padding | |
58 | |
59 MaxShards = 256 | |
60 MaxShardsLen = len("ff") | |
61 InternalGAEPadding = 96 | |
62 ValueSizeLimit = (1000 * 1000) - InternalGAEPadding - MaxShardsLen | |
63 | |
64 CacheEnableMeta = "dscache.enable" | |
65 CacheExpirationMeta = "dscache.expiration" | |
66 | |
67 // NonceUint32s is the number of 32 bit uints to use in the 'lock' nonce . | |
68 NonceUint32s = 2 | |
69 | |
70 // GlobalEnabledCheckInterval is how frequently IsGloballyEnabled should check | |
71 // the globalEnabled datastore entry. | |
72 GlobalEnabledCheckInterval = 5 * time.Minute | |
73 ) | |
74 | |
75 // internalValueSizeLimit is a var for testing purposes. | |
76 var internalValueSizeLimit = ValueSizeLimit | |
77 | |
78 type CompressionType byte | |
79 | |
80 const ( | |
81 NoCompression CompressionType = iota | |
82 ZlibCompression | |
83 ) | |
84 | |
85 func (c CompressionType) String() string { | |
86 switch c { | |
87 case NoCompression: | |
88 return "NoCompression" | |
89 case ZlibCompression: | |
90 return "ZlibCompression" | |
91 default: | |
92 return fmt.Sprintf("UNKNOWN_CompressionType(%d)", c) | |
93 } | |
94 } | |
95 | |
96 // FlagValue is used to indicate if a memcache entry currently contains an | |
97 // item or a lock. | |
98 type FlagValue uint32 | |
99 | |
100 const ( | |
101 ItemUKNONWN FlagValue = iota | |
102 ItemHasData | |
103 ItemHasLock | |
104 ) | |
105 | |
106 func MakeMemcacheKey(shard int, k datastore.Key) string { | |
107 return fmt.Sprintf(KeyFormat, shard, HashKey(k)) | |
108 } | |
109 | |
110 func HashKey(k datastore.Key) string { | |
111 // errs can't happen, since we're using a byte buffer. | |
112 buf := bytes.Buffer{} | |
113 _ = datastore.WriteKey(&buf, datastore.WithoutContext, k) | |
114 dgst := sha1.Sum(buf.Bytes()) | |
115 buf.Reset() | |
116 enc := base64.NewEncoder(base64.StdEncoding, &buf) | |
117 _, _ = enc.Write(dgst[:]) | |
118 enc.Close() | |
119 return buf.String()[:buf.Len()-Sha1B64Padding] | |
120 } | |
121 | |
122 type GlobalConfig struct { | |
dnj
2015/08/07 16:30:06
IMO move to a separate ".go" file.
iannucci
2015/08/07 19:41:00
Done.
| |
123 _id int64 `gae:"$id,1"` | |
124 _kind string `gae:"$kind,dscache"` | |
125 | |
126 Enable bool | |
127 } | |
128 | |
129 var ( | |
130 globalEnabledLock = sync.RWMutex{} | |
131 | |
132 // globalEnabled is whether or not memcache has been globally enabled. I t is | |
133 // populated by IsGloballyEnabled when SetDynamicGlobalEnable has been s et to | |
134 // true. | |
135 globalEnabled = true | |
136 | |
137 // globalEnabledNextCheck is IsGloballyEnabled's last successful check o f the | |
138 // global disable key. | |
139 globalEnabledNextCheck = time.Time{} | |
140 ) | |
141 | |
142 // IsGloballyEnabled checks to see if this filter is enabled globally. | |
143 // | |
144 // This checks InstanceEnabledStatic, as well as polls the datastore entity | |
145 // /dscache,1 (a GlobalConfig instance) | |
146 // Once every GlobalEnabledCheckInterval. | |
147 // | |
148 // For correctness, any error encountered returns true. If this assumed false, | |
149 // then Put operations might incorrectly invalidate the cache. | |
150 func IsGloballyEnabled(c context.Context) bool { | |
151 if !InstanceEnabledStatic { | |
152 return false | |
153 } | |
154 | |
155 now := clock.Now(c) | |
156 | |
157 globalEnabledLock.RLock() | |
dnj
2015/08/07 16:30:07
For peace of mind (and to avoid C-style cleanup pa
iannucci
2015/08/07 19:41:00
derp, dunno what I was thinking. did this.
| |
158 if now.Before(globalEnabledNextCheck) { | |
159 globalEnabledLock.RUnlock() | |
160 return globalEnabled | |
161 } | |
162 globalEnabledLock.RUnlock() | |
163 | |
164 globalEnabledLock.Lock() | |
165 defer globalEnabledLock.Unlock() | |
166 // just in case we raced | |
167 if now.Before(globalEnabledNextCheck) { | |
168 return globalEnabled | |
169 } | |
170 | |
171 // alawys go to the default namespace | |
dnj
2015/08/07 16:30:06
always
iannucci
2015/08/07 19:41:00
Done.
| |
172 c, err := info.Get(c).Namespace("") | |
173 if err != nil { | |
174 return true | |
dnj
2015/08/07 16:30:06
When the memcache failure case can perpetuate stal
iannucci
2015/08/07 19:41:00
Nope, because then you could end up doing Put oper
| |
175 } | |
176 cfg := &GlobalConfig{Enable: true} | |
177 if err := datastore.Get(c).Get(cfg); err != nil && err != datastore.ErrN oSuchEntity { | |
178 return true | |
179 } | |
180 globalEnabled = cfg.Enable | |
181 globalEnabledNextCheck = now.Add(GlobalEnabledCheckInterval) | |
182 return globalEnabled | |
dnj
2015/08/07 16:30:06
Just return "cfg.Enable".
iannucci
2015/08/07 19:41:00
I actually prefer not to. globalEnabled is the val
| |
183 } | |
184 | |
185 func SetDynamicGlobalEnable(c context.Context, memcacheEnabled bool) error { | |
dnj
2015/08/07 16:30:07
Why "Dynamic"? Seems inconsistent with the other v
iannucci
2015/08/07 19:41:00
old naming artifact. fixed.
| |
186 // alawys go to the default namespace | |
187 c, err := info.Get(c).Namespace("") | |
188 if err != nil { | |
189 return err | |
190 } | |
191 return datastore.Get(c).RunInTransaction(func(c context.Context) error { | |
192 ds := datastore.Get(c) | |
193 cfg := &GlobalConfig{Enable: true} | |
194 if err := ds.Get(cfg); err != nil && err != datastore.ErrNoSuchE ntity { | |
195 return err | |
196 } | |
197 if cfg.Enable == memcacheEnabled { | |
198 return nil | |
199 } | |
200 cfg.Enable = memcacheEnabled | |
201 if memcacheEnabled { | |
202 // when going false -> true, wipe memcache. | |
203 if err := memcache.Get(c).Flush(); err != nil { | |
204 return err | |
205 } | |
206 } | |
207 return ds.Put(cfg) | |
208 }, nil) | |
209 } | |
OLD | NEW |