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 "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 |