PageRenderTime 21ms CodeModel.GetById 24ms RepoModel.GetById 0ms app.codeStats 0ms

/appengine/memcache/memcache.go

https://code.google.com/p/appengine-go/
Go | 439 lines | 291 code | 37 blank | 111 comment | 59 complexity | 3472a854a11b7a4c93fc64f160717eee MD5 | raw file
Possible License(s): Apache-2.0
  1. // Copyright 2011 Google Inc. All rights reserved.
  2. // Use of this source code is governed by the Apache 2.0
  3. // license that can be found in the LICENSE file.
  4. // Package memcache provides a client for App Engine's distributed in-memory
  5. // key-value store for small chunks of arbitrary data.
  6. //
  7. // The fundamental operations get and set items, keyed by a string.
  8. //
  9. // item0, err := memcache.Get(c, "key")
  10. // if err != nil && err != memcache.ErrCacheMiss {
  11. // return err
  12. // }
  13. // if err == nil {
  14. // fmt.Fprintf(w, "memcache hit: Key=%q Val=[% x]\n", item0.Key, item0.Value)
  15. // } else {
  16. // fmt.Fprintf(w, "memcache miss\n")
  17. // }
  18. //
  19. // and
  20. //
  21. // item1 := &memcache.Item{
  22. // Key: "foo",
  23. // Value: []byte("bar"),
  24. // }
  25. // if err := memcache.Set(c, item1); err != nil {
  26. // return err
  27. // }
  28. package memcache
  29. import (
  30. "bytes"
  31. "encoding/gob"
  32. "encoding/json"
  33. "errors"
  34. "time"
  35. "appengine"
  36. "appengine_internal"
  37. "code.google.com/p/goprotobuf/proto"
  38. pb "appengine_internal/memcache"
  39. )
  40. var (
  41. // ErrCacheMiss means that an operation failed
  42. // because the item wasn't present.
  43. ErrCacheMiss = errors.New("memcache: cache miss")
  44. // ErrCASConflict means that a CompareAndSwap call failed due to the
  45. // cached value being modified between the Get and the CompareAndSwap.
  46. // If the cached value was simply evicted rather than replaced,
  47. // ErrNotStored will be returned instead.
  48. ErrCASConflict = errors.New("memcache: compare-and-swap conflict")
  49. // ErrNoStats means that no statistics were available.
  50. ErrNoStats = errors.New("memcache: no statistics available")
  51. // ErrNotStored means that a conditional write operation (i.e. Add or
  52. // CompareAndSwap) failed because the condition was not satisfied.
  53. ErrNotStored = errors.New("memcache: item not stored")
  54. // ErrServerError means that a server error occurred.
  55. ErrServerError = errors.New("memcache: server error")
  56. )
  57. // Item is the unit of memcache gets and sets.
  58. type Item struct {
  59. // Key is the Item's key (250 bytes maximum).
  60. Key string
  61. // Value is the Item's value.
  62. Value []byte
  63. // Object is the Item's value for use with a Codec.
  64. Object interface{}
  65. // Flags are server-opaque flags whose semantics are entirely up to the
  66. // App Engine app.
  67. Flags uint32
  68. // Expiration is the maximum duration that the item will stay
  69. // in the cache.
  70. // The zero value means the Item has no expiration time.
  71. // Subsecond precision is ignored.
  72. Expiration time.Duration
  73. // casID is a client-opaque value used for compare-and-swap operations.
  74. // Zero means that compare-and-swap is not used.
  75. casID uint64
  76. }
  77. const (
  78. secondsIn30Years = 60 * 60 * 24 * 365 * 30 // from memcache server code
  79. thirtyYears = time.Duration(secondsIn30Years) * time.Second
  80. )
  81. // protoToItem converts a protocol buffer item to a Go struct.
  82. func protoToItem(p *pb.MemcacheGetResponse_Item) *Item {
  83. var expiration time.Duration
  84. sec := proto.GetInt32(p.ExpiresInSeconds)
  85. if sec > 0 && sec < secondsIn30Years {
  86. expiration = time.Duration(sec) * time.Second
  87. }
  88. return &Item{
  89. Key: string(p.Key),
  90. Value: p.Value,
  91. Flags: proto.GetUint32(p.Flags),
  92. Expiration: expiration,
  93. casID: proto.GetUint64(p.CasId),
  94. }
  95. }
  96. // If err is an appengine.MultiError, return its first element. Otherwise, return err.
  97. func singleError(err error) error {
  98. if me, ok := err.(appengine.MultiError); ok {
  99. return me[0]
  100. }
  101. return err
  102. }
  103. // Get gets the item for the given key. ErrCacheMiss is returned for a memcache
  104. // cache miss. The key must be at most 250 bytes in length.
  105. func Get(c appengine.Context, key string) (*Item, error) {
  106. m, err := GetMulti(c, []string{key})
  107. if err != nil {
  108. return nil, err
  109. }
  110. if _, ok := m[key]; !ok {
  111. return nil, ErrCacheMiss
  112. }
  113. return m[key], nil
  114. }
  115. // GetMulti is a batch version of Get. The returned map from keys to items may
  116. // have fewer elements than the input slice, due to memcache cache misses.
  117. // Each key must be at most 250 bytes in length.
  118. func GetMulti(c appengine.Context, key []string) (map[string]*Item, error) {
  119. keyAsBytes := make([][]byte, len(key))
  120. for i, k := range key {
  121. keyAsBytes[i] = []byte(k)
  122. }
  123. req := &pb.MemcacheGetRequest{
  124. Key: keyAsBytes,
  125. ForCas: proto.Bool(true),
  126. }
  127. res := &pb.MemcacheGetResponse{}
  128. if err := c.Call("memcache", "Get", req, res, nil); err != nil {
  129. return nil, err
  130. }
  131. m := make(map[string]*Item, len(res.Item))
  132. for _, p := range res.Item {
  133. t := protoToItem(p)
  134. m[t.Key] = t
  135. }
  136. return m, nil
  137. }
  138. // Delete deletes the item for the given key.
  139. // ErrCacheMiss is returned if the specified item can not be found.
  140. // The key must be at most 250 bytes in length.
  141. func Delete(c appengine.Context, key string) error {
  142. return singleError(DeleteMulti(c, []string{key}))
  143. }
  144. // DeleteMulti is a batch version of Delete.
  145. // If any keys cannot be found, an appengine.MultiError is returned.
  146. // Each key must be at most 250 bytes in length.
  147. func DeleteMulti(c appengine.Context, key []string) error {
  148. req := &pb.MemcacheDeleteRequest{
  149. Item: make([]*pb.MemcacheDeleteRequest_Item, len(key)),
  150. }
  151. for i, k := range key {
  152. req.Item[i] = &pb.MemcacheDeleteRequest_Item{Key: []byte(k)}
  153. }
  154. res := &pb.MemcacheDeleteResponse{}
  155. if err := c.Call("memcache", "Delete", req, res, nil); err != nil {
  156. return err
  157. }
  158. if len(res.DeleteStatus) != len(key) {
  159. return ErrServerError
  160. }
  161. me, any := make(appengine.MultiError, len(key)), false
  162. for i, s := range res.DeleteStatus {
  163. switch s {
  164. case pb.MemcacheDeleteResponse_DELETED:
  165. // OK
  166. case pb.MemcacheDeleteResponse_NOT_FOUND:
  167. me[i] = ErrCacheMiss
  168. any = true
  169. default:
  170. me[i] = ErrServerError
  171. any = true
  172. }
  173. }
  174. if any {
  175. return me
  176. }
  177. return nil
  178. }
  179. // Increment atomically increments the decimal value in the given key
  180. // by delta and returns the new value. The value must fit in a uint64.
  181. // Overflow wraps around, and underflow is capped to zero. The
  182. // provided delta may be negative. If the key doesn't exist in
  183. // memcacheg, the provided initial value is used to atomically
  184. // populate it before the delta is applied.
  185. // The key must be at most 250 bytes in length.
  186. func Increment(c appengine.Context, key string, delta int64, initialValue uint64) (newValue uint64, err error) {
  187. return incr(c, key, delta, &initialValue)
  188. }
  189. // IncrementExisting works like Increment but assumes that the key
  190. // already exists in memcache and doesn't take an initial value.
  191. // IncrementExisting can save work if calculating the initial value is
  192. // expensive.
  193. // ErrCacheMiss is returned if the specified item can not be found.
  194. func IncrementExisting(c appengine.Context, key string, delta int64) (newValue uint64, err error) {
  195. return incr(c, key, delta, nil)
  196. }
  197. func incr(c appengine.Context, key string, delta int64, initialValue *uint64) (newValue uint64, err error) {
  198. req := &pb.MemcacheIncrementRequest{
  199. Key: []byte(key),
  200. InitialValue: initialValue,
  201. }
  202. if delta >= 0 {
  203. req.Delta = proto.Uint64(uint64(delta))
  204. } else {
  205. req.Delta = proto.Uint64(uint64(-delta))
  206. req.Direction = pb.NewMemcacheIncrementRequest_Direction(pb.MemcacheIncrementRequest_DECREMENT)
  207. }
  208. res := &pb.MemcacheIncrementResponse{}
  209. err = c.Call("memcache", "Increment", req, res, nil)
  210. if err != nil {
  211. return
  212. }
  213. if res.NewValue == nil {
  214. return 0, ErrCacheMiss
  215. }
  216. return *res.NewValue, nil
  217. }
  218. // set sets the given items using the given conflict resolution policy.
  219. // appengine.MultiError may be returned.
  220. func set(c appengine.Context, item []*Item, value [][]byte, policy pb.MemcacheSetRequest_SetPolicy) error {
  221. req := &pb.MemcacheSetRequest{
  222. Item: make([]*pb.MemcacheSetRequest_Item, len(item)),
  223. }
  224. for i, t := range item {
  225. p := &pb.MemcacheSetRequest_Item{
  226. Key: []byte(t.Key),
  227. }
  228. if value == nil {
  229. p.Value = t.Value
  230. } else {
  231. p.Value = value[i]
  232. }
  233. if t.Flags != 0 {
  234. p.Flags = proto.Uint32(t.Flags)
  235. }
  236. if t.Expiration != 0 {
  237. // In the .proto file, MemcacheSetRequest_Item uses a fixed32 (i.e. unsigned)
  238. // for expiration time, while MemcacheGetRequest_Item uses int32 (i.e. signed).
  239. // Throughout this .go file, we use int32.
  240. // Also, in the proto, the expiration value is either a duration (in seconds)
  241. // or an absolute Unix timestamp (in seconds), depending on whether the
  242. // value is less than or greater than or equal to 30 years, respectively.
  243. if t.Expiration < time.Second {
  244. // Because an Expiration of 0 means no expiration, we take
  245. // care here to translate an item with an expiration
  246. // Duration between 0-1 seconds as immediately expiring
  247. // (saying it expired a few seconds ago), rather than
  248. // rounding it down to 0 and making it live forever.
  249. p.ExpirationTime = proto.Uint32(uint32(time.Now().Unix()) - 5)
  250. } else if t.Expiration >= thirtyYears {
  251. p.ExpirationTime = proto.Uint32(uint32(time.Now().Unix()) + uint32(t.Expiration/time.Second))
  252. } else {
  253. p.ExpirationTime = proto.Uint32(uint32(t.Expiration / time.Second))
  254. }
  255. }
  256. if t.casID != 0 {
  257. p.CasId = proto.Uint64(t.casID)
  258. p.ForCas = proto.Bool(true)
  259. }
  260. p.SetPolicy = pb.NewMemcacheSetRequest_SetPolicy(policy)
  261. req.Item[i] = p
  262. }
  263. res := &pb.MemcacheSetResponse{}
  264. if err := c.Call("memcache", "Set", req, res, nil); err != nil {
  265. return err
  266. }
  267. if len(res.SetStatus) != len(item) {
  268. return ErrServerError
  269. }
  270. me, any := make(appengine.MultiError, len(item)), false
  271. for i, st := range res.SetStatus {
  272. var err error
  273. switch st {
  274. case pb.MemcacheSetResponse_STORED:
  275. // OK
  276. case pb.MemcacheSetResponse_NOT_STORED:
  277. err = ErrNotStored
  278. case pb.MemcacheSetResponse_EXISTS:
  279. err = ErrCASConflict
  280. default:
  281. err = ErrServerError
  282. }
  283. if err != nil {
  284. me[i] = err
  285. any = true
  286. }
  287. }
  288. if any {
  289. return me
  290. }
  291. return nil
  292. }
  293. // Set writes the given item, unconditionally.
  294. func Set(c appengine.Context, item *Item) error {
  295. return singleError(set(c, []*Item{item}, nil, pb.MemcacheSetRequest_SET))
  296. }
  297. // SetMulti is a batch version of Set.
  298. // appengine.MultiError may be returned.
  299. func SetMulti(c appengine.Context, item []*Item) error {
  300. return set(c, item, nil, pb.MemcacheSetRequest_SET)
  301. }
  302. // Add writes the given item, if no value already exists for its key.
  303. // ErrNotStored is returned if that condition is not met.
  304. func Add(c appengine.Context, item *Item) error {
  305. return singleError(set(c, []*Item{item}, nil, pb.MemcacheSetRequest_ADD))
  306. }
  307. // AddMulti is a batch version of Add.
  308. // appengine.MultiError may be returned.
  309. func AddMulti(c appengine.Context, item []*Item) error {
  310. return set(c, item, nil, pb.MemcacheSetRequest_ADD)
  311. }
  312. // CompareAndSwap writes the given item that was previously returned by Get,
  313. // if the value was neither modified or evicted between the Get and the
  314. // CompareAndSwap calls. The item's Key should not change between calls but
  315. // all other item fields may differ.
  316. // ErrCASConflict is returned if the value was modified in between the calls.
  317. // ErrNotStored is returned if the value was evicted in between the calls.
  318. func CompareAndSwap(c appengine.Context, item *Item) error {
  319. return singleError(set(c, []*Item{item}, nil, pb.MemcacheSetRequest_CAS))
  320. }
  321. // CompareAndSwapMulti is a batch version of CompareAndSwap.
  322. // appengine.MultiError may be returned.
  323. func CompareAndSwapMulti(c appengine.Context, item []*Item) error {
  324. return set(c, item, nil, pb.MemcacheSetRequest_CAS)
  325. }
  326. // Codec represents a symmetric pair of functions that implement a codec.
  327. // Items stored into or retrieved from memcache using a Codec have their
  328. // values marshaled or unmarshaled.
  329. type Codec struct {
  330. Marshal func(interface{}) ([]byte, error)
  331. Unmarshal func([]byte, interface{}) error
  332. }
  333. func (cd Codec) Get(c appengine.Context, key string, v interface{}) (*Item, error) {
  334. i, err := Get(c, key)
  335. if err != nil {
  336. return nil, err
  337. }
  338. if err := cd.Unmarshal(i.Value, v); err != nil {
  339. return nil, err
  340. }
  341. return i, nil
  342. }
  343. func (cd Codec) set(c appengine.Context, item *Item, policy pb.MemcacheSetRequest_SetPolicy) error {
  344. value, err := cd.Marshal(item.Object)
  345. if err != nil {
  346. return err
  347. }
  348. return singleError(set(c, []*Item{item}, [][]byte{value}, policy))
  349. }
  350. func (cd Codec) Set(c appengine.Context, item *Item) error {
  351. return cd.set(c, item, pb.MemcacheSetRequest_SET)
  352. }
  353. func (cd Codec) Add(c appengine.Context, item *Item) error {
  354. return cd.set(c, item, pb.MemcacheSetRequest_ADD)
  355. }
  356. var (
  357. // Gob is a Codec that uses the gob package.
  358. Gob = Codec{gobMarshal, gobUnmarshal}
  359. // JSON is a Codec that uses the json package.
  360. JSON = Codec{json.Marshal, json.Unmarshal}
  361. )
  362. func gobMarshal(v interface{}) ([]byte, error) {
  363. var buf bytes.Buffer
  364. if err := gob.NewEncoder(&buf).Encode(v); err != nil {
  365. return nil, err
  366. }
  367. return buf.Bytes(), nil
  368. }
  369. func gobUnmarshal(data []byte, v interface{}) error {
  370. return gob.NewDecoder(bytes.NewBuffer(data)).Decode(v)
  371. }
  372. // Statistics represents a set of statistics about the memcache cache.
  373. type Statistics struct {
  374. Hits uint64 // Counter of cache hits
  375. Misses uint64 // Counter of cache misses
  376. ByteHits uint64 // Counter of bytes transferred for gets
  377. Items uint64 // Items currently in the cache
  378. Bytes uint64 // Size of all items currently in the cache
  379. Oldest int64 // Age of access of the oldest item, in seconds
  380. }
  381. // Stats retrieves the current memcache statistics.
  382. func Stats(c appengine.Context) (*Statistics, error) {
  383. req := &pb.MemcacheStatsRequest{}
  384. res := &pb.MemcacheStatsResponse{}
  385. if err := c.Call("memcache", "Stats", req, res, nil); err != nil {
  386. return nil, err
  387. }
  388. if res.Stats == nil {
  389. return nil, ErrNoStats
  390. }
  391. return &Statistics{
  392. Hits: *res.Stats.Hits,
  393. Misses: *res.Stats.Misses,
  394. ByteHits: *res.Stats.ByteHits,
  395. Items: *res.Stats.Items,
  396. Bytes: *res.Stats.Bytes,
  397. Oldest: int64(*res.Stats.OldestItemAge),
  398. }, nil
  399. }
  400. func init() {
  401. appengine_internal.RegisterErrorCodeMap("memcache", pb.MemcacheServiceError_ErrorCode_name)
  402. }