fix: add generation tracking to prevent keys surviving a clear operation

Running a Purge and a Get at the same time could allow an old key to be
pulled back into the cache if the request was initiated by a node that
had been cleared and sent to a node that hadn't. This patch tries to
mitigate this, by adding an additional `generation` field, that keeps
track of the number of purges issued. Requests are fulfilled successfully
only if boths ends of a request are on the same generation.
This commit is contained in:
ct16k 2023-01-22 10:58:45 +02:00
parent 95848327b2
commit 7f8db4d8ed
6 changed files with 405 additions and 136 deletions

View File

@ -31,17 +31,24 @@ import (
// A ByteView is meant to be used as a value type, not
// a pointer (like a time.Time).
type ByteView struct {
e time.Time
s string
// If b is non-nil, b is used, else s is used.
b []byte
s string
e time.Time
// generation is an optional field, used only in certain operations
g int64
}
// Returns the expire time associated with this view
// Expire returns the expire time associated with this view
func (v ByteView) Expire() time.Time {
return v.e
}
// Generation returns the generation associated with this cache view
func (v ByteView) Generation() int64 {
return v.g
}
// Len returns the view's length.
func (v ByteView) Len() int {
if v.b != nil {

View File

@ -274,19 +274,20 @@ func (g *Group) Set(ctx context.Context, key string, value []byte, expire time.T
_, err := g.setGroup.Do(key, func() (interface{}, error) {
// If remote peer owns this key
owner, ok := g.peers.PickPeer(key)
generation := g.mainCache.generation()
if ok {
if err := g.setFromPeer(ctx, owner, key, value, expire); err != nil {
if err := g.setFromPeer(ctx, owner, key, value, expire, generation); err != nil {
return nil, err
}
// TODO(thrawn01): Not sure if this is useful outside of tests...
// maybe we should ALWAYS update the local cache?
if hotCache {
g.localSet(key, value, expire, &g.hotCache)
g.localSet(key, value, expire, generation, &g.hotCache)
}
return nil, nil
}
// We own this key
g.localSet(key, value, expire, &g.mainCache)
g.localSet(key, value, expire, generation, &g.mainCache)
return nil, nil
})
return err
@ -495,14 +496,19 @@ func (g *Group) getFromPeer(ctx context.Context, peer ProtoGetter, key string) (
}
}
value := ByteView{b: res.Value, e: expire}
var generation int64
if res.Generation != nil {
generation = *res.Generation
}
value := ByteView{b: res.Value, e: expire, g: generation}
// Always populate the hot cache
g.populateCache(key, value, &g.hotCache)
return value, nil
}
func (g *Group) setFromPeer(ctx context.Context, peer ProtoGetter, k string, v []byte, e time.Time) error {
func (g *Group) setFromPeer(ctx context.Context, peer ProtoGetter, k string, v []byte, e time.Time, gen int64) error {
var expire int64
if !e.IsZero() {
expire = e.UnixNano()
@ -512,6 +518,7 @@ func (g *Group) setFromPeer(ctx context.Context, peer ProtoGetter, k string, v [
Group: &g.name,
Key: &k,
Value: v,
Generation: &gen,
}
return peer.Set(ctx, req)
}
@ -543,7 +550,7 @@ func (g *Group) lookupCache(key string) (value ByteView, ok bool) {
return
}
func (g *Group) localSet(key string, value []byte, expire time.Time, cache *cache) {
func (g *Group) localSet(key string, value []byte, expire time.Time, generation int64, cache *cache) {
if g.cacheBytes <= 0 {
return
}
@ -551,6 +558,7 @@ func (g *Group) localSet(key string, value []byte, expire time.Time, cache *cach
bv := ByteView{
b: value,
e: expire,
g: generation,
}
// Ensure no requests are in flight
@ -646,10 +654,11 @@ var NowFunc lru.NowFunc = time.Now
// values.
type cache struct {
mu sync.RWMutex
nbytes int64 // of all keys and values
lru *lru.Cache
nbytes int64 // of all keys and values
nhit, nget int64
nevict int64 // number of evictions
gen int64
}
func (c *cache) stats() CacheStats {
@ -661,6 +670,7 @@ func (c *cache) stats() CacheStats {
Gets: c.nget,
Hits: c.nhit,
Evictions: c.nevict,
Generation: c.gen,
}
}
@ -677,6 +687,16 @@ func (c *cache) add(key string, value ByteView) {
},
}
}
if c.gen != value.g {
if logger != nil {
logger.Error().WithFields(map[string]interface{}{
"got": value.g,
"have": c.generation,
"key": key,
}).Printf("generation mismatch")
}
return
}
c.lru.Add(key, value, value.Expire())
c.nbytes += int64(len(key)) + int64(value.Len())
}
@ -693,7 +713,10 @@ func (c *cache) get(key string) (value ByteView, ok bool) {
return
}
c.nhit++
return vi.(ByteView), true
bv := vi.(ByteView)
bv.g = c.gen
return bv, true
}
func (c *cache) remove(key string) {
@ -741,6 +764,12 @@ func (c *cache) itemsLocked() int64 {
return int64(c.lru.Len())
}
func (c *cache) generation() int64 {
c.mu.RLock()
defer c.mu.RUnlock()
return c.gen
}
// An AtomicInt is an int64 to be accessed atomically.
type AtomicInt int64
@ -770,4 +799,5 @@ type CacheStats struct {
Gets int64
Hits int64
Evictions int64
Generation int64
}

View File

@ -1,155 +1,377 @@
//
//Copyright 2012 Google Inc.
//
//Licensed under the Apache License, Version 2.0 (the "License");
//you may not use this file except in compliance with the License.
//You may obtain a copy of the License at
//
//http://www.apache.org/licenses/LICENSE-2.0
//
//Unless required by applicable law or agreed to in writing, software
//distributed under the License is distributed on an "AS IS" BASIS,
//WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
//See the License for the specific language governing permissions and
//limitations under the License.
// Code generated by protoc-gen-go. DO NOT EDIT.
// versions:
// protoc-gen-go v1.28.1
// protoc v3.21.12
// source: groupcache.proto
/*
Package groupcachepb is a generated protocol buffer package.
It is generated from these files:
groupcache.proto
It has these top-level messages:
GetRequest
GetResponse
SetRequest
*/
package groupcachepb
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
reflect "reflect"
sync "sync"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
const (
// Verify that this generated code is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
// Verify that runtime/protoimpl is sufficiently up-to-date.
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
)
type GetRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Group *string `protobuf:"bytes,1,req,name=group" json:"group,omitempty"`
Key *string `protobuf:"bytes,2,req,name=key" json:"key,omitempty"`
XXX_unrecognized []byte `json:"-"`
Key *string `protobuf:"bytes,2,req,name=key" json:"key,omitempty"` // not actually required/guaranteed to be UTF-8
Generation *int64 `protobuf:"varint,3,req,name=generation" json:"generation,omitempty"`
}
func (x *GetRequest) Reset() {
*x = GetRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_groupcache_proto_msgTypes[0]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (m *GetRequest) Reset() { *m = GetRequest{} }
func (m *GetRequest) String() string { return proto.CompactTextString(m) }
func (*GetRequest) ProtoMessage() {}
func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *GetRequest) GetGroup() string {
if m != nil && m.Group != nil {
return *m.Group
func (x *GetRequest) ProtoReflect() protoreflect.Message {
mi := &file_groupcache_proto_msgTypes[0]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetRequest.ProtoReflect.Descriptor instead.
func (*GetRequest) Descriptor() ([]byte, []int) {
return file_groupcache_proto_rawDescGZIP(), []int{0}
}
func (x *GetRequest) GetGroup() string {
if x != nil && x.Group != nil {
return *x.Group
}
return ""
}
func (m *GetRequest) GetKey() string {
if m != nil && m.Key != nil {
return *m.Key
func (x *GetRequest) GetKey() string {
if x != nil && x.Key != nil {
return *x.Key
}
return ""
}
type GetResponse struct {
Value []byte `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
MinuteQps *float64 `protobuf:"fixed64,2,opt,name=minute_qps,json=minuteQps" json:"minute_qps,omitempty"`
Expire *int64 `protobuf:"varint,3,opt,name=expire" json:"expire,omitempty"`
XXX_unrecognized []byte `json:"-"`
}
func (m *GetResponse) Reset() { *m = GetResponse{} }
func (m *GetResponse) String() string { return proto.CompactTextString(m) }
func (*GetResponse) ProtoMessage() {}
func (*GetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *GetResponse) GetValue() []byte {
if m != nil {
return m.Value
}
return nil
}
func (m *GetResponse) GetMinuteQps() float64 {
if m != nil && m.MinuteQps != nil {
return *m.MinuteQps
func (x *GetRequest) GetGeneration() int64 {
if x != nil && x.Generation != nil {
return *x.Generation
}
return 0
}
func (m *GetResponse) GetExpire() int64 {
if m != nil && m.Expire != nil {
return *m.Expire
type GetResponse struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Value []byte `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
MinuteQps *float64 `protobuf:"fixed64,2,opt,name=minute_qps,json=minuteQps" json:"minute_qps,omitempty"`
Expire *int64 `protobuf:"varint,3,opt,name=expire" json:"expire,omitempty"`
Generation *int64 `protobuf:"varint,4,opt,name=generation" json:"generation,omitempty"`
}
func (x *GetResponse) Reset() {
*x = GetResponse{}
if protoimpl.UnsafeEnabled {
mi := &file_groupcache_proto_msgTypes[1]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *GetResponse) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (*GetResponse) ProtoMessage() {}
func (x *GetResponse) ProtoReflect() protoreflect.Message {
mi := &file_groupcache_proto_msgTypes[1]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use GetResponse.ProtoReflect.Descriptor instead.
func (*GetResponse) Descriptor() ([]byte, []int) {
return file_groupcache_proto_rawDescGZIP(), []int{1}
}
func (x *GetResponse) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
func (x *GetResponse) GetMinuteQps() float64 {
if x != nil && x.MinuteQps != nil {
return *x.MinuteQps
}
return 0
}
func (x *GetResponse) GetExpire() int64 {
if x != nil && x.Expire != nil {
return *x.Expire
}
return 0
}
func (x *GetResponse) GetGeneration() int64 {
if x != nil && x.Generation != nil {
return *x.Generation
}
return 0
}
type SetRequest struct {
state protoimpl.MessageState
sizeCache protoimpl.SizeCache
unknownFields protoimpl.UnknownFields
Group *string `protobuf:"bytes,1,req,name=group" json:"group,omitempty"`
Key *string `protobuf:"bytes,2,req,name=key" json:"key,omitempty"`
Value []byte `protobuf:"bytes,3,opt,name=value" json:"value,omitempty"`
Expire *int64 `protobuf:"varint,4,opt,name=expire" json:"expire,omitempty"`
XXX_unrecognized []byte `json:"-"`
Generation *int64 `protobuf:"varint,5,opt,name=generation" json:"generation,omitempty"`
}
func (x *SetRequest) Reset() {
*x = SetRequest{}
if protoimpl.UnsafeEnabled {
mi := &file_groupcache_proto_msgTypes[2]
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
ms.StoreMessageInfo(mi)
}
}
func (x *SetRequest) String() string {
return protoimpl.X.MessageStringOf(x)
}
func (m *SetRequest) Reset() { *m = SetRequest{} }
func (m *SetRequest) String() string { return proto.CompactTextString(m) }
func (*SetRequest) ProtoMessage() {}
func (*SetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *SetRequest) GetGroup() string {
if m != nil && m.Group != nil {
return *m.Group
func (x *SetRequest) ProtoReflect() protoreflect.Message {
mi := &file_groupcache_proto_msgTypes[2]
if protoimpl.UnsafeEnabled && x != nil {
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
if ms.LoadMessageInfo() == nil {
ms.StoreMessageInfo(mi)
}
return ms
}
return mi.MessageOf(x)
}
// Deprecated: Use SetRequest.ProtoReflect.Descriptor instead.
func (*SetRequest) Descriptor() ([]byte, []int) {
return file_groupcache_proto_rawDescGZIP(), []int{2}
}
func (x *SetRequest) GetGroup() string {
if x != nil && x.Group != nil {
return *x.Group
}
return ""
}
func (m *SetRequest) GetKey() string {
if m != nil && m.Key != nil {
return *m.Key
func (x *SetRequest) GetKey() string {
if x != nil && x.Key != nil {
return *x.Key
}
return ""
}
func (m *SetRequest) GetValue() []byte {
if m != nil {
return m.Value
func (x *SetRequest) GetValue() []byte {
if x != nil {
return x.Value
}
return nil
}
func (m *SetRequest) GetExpire() int64 {
if m != nil && m.Expire != nil {
return *m.Expire
func (x *SetRequest) GetExpire() int64 {
if x != nil && x.Expire != nil {
return *x.Expire
}
return 0
}
func init() {
proto.RegisterType((*GetRequest)(nil), "groupcachepb.GetRequest")
proto.RegisterType((*GetResponse)(nil), "groupcachepb.GetResponse")
proto.RegisterType((*SetRequest)(nil), "groupcachepb.SetRequest")
func (x *SetRequest) GetGeneration() int64 {
if x != nil && x.Generation != nil {
return *x.Generation
}
return 0
}
func init() { proto.RegisterFile("groupcache.proto", fileDescriptor0) }
var File_groupcache_proto protoreflect.FileDescriptor
var fileDescriptor0 = []byte{
// 215 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x50, 0x31, 0x4b, 0xc5, 0x30,
0x18, 0x34, 0x8d, 0x0a, 0xfd, 0xec, 0x50, 0x82, 0x48, 0x14, 0x84, 0x90, 0x29, 0x53, 0x07, 0x71,
0x74, 0x73, 0x28, 0xb8, 0x19, 0x37, 0x17, 0x69, 0xcb, 0x87, 0x16, 0xb5, 0x49, 0x9b, 0x44, 0x7c,
0xff, 0xfe, 0x91, 0xe6, 0x41, 0x3a, 0xbc, 0xe5, 0x6d, 0xb9, 0x3b, 0x2e, 0x77, 0xdf, 0x41, 0xfd,
0xb9, 0x98, 0x60, 0x87, 0x6e, 0xf8, 0xc2, 0xc6, 0x2e, 0xc6, 0x1b, 0x56, 0x65, 0xc6, 0xf6, 0xf2,
0x11, 0xa0, 0x45, 0xaf, 0x71, 0x0e, 0xe8, 0x3c, 0xbb, 0x86, 0x8b, 0x55, 0xe5, 0x44, 0x14, 0xaa,
0xd4, 0x09, 0xb0, 0x1a, 0xe8, 0x37, 0xee, 0x78, 0xb1, 0x72, 0xf1, 0x29, 0xdf, 0xe1, 0x6a, 0x75,
0x39, 0x6b, 0x26, 0x87, 0xd1, 0xf6, 0xd7, 0xfd, 0x04, 0xe4, 0x44, 0x10, 0x55, 0xe9, 0x04, 0xd8,
0x3d, 0xc0, 0xef, 0x38, 0x05, 0x8f, 0x1f, 0xb3, 0x75, 0xbc, 0x10, 0x44, 0x11, 0x5d, 0x26, 0xe6,
0xd5, 0x3a, 0x76, 0x03, 0x97, 0xf8, 0x6f, 0xc7, 0x05, 0x39, 0x15, 0x44, 0x51, 0x7d, 0x40, 0xb2,
0x07, 0x78, 0x3b, 0xb9, 0x51, 0xae, 0x40, 0xb7, 0x15, 0x72, 0xc6, 0xf9, 0x36, 0xe3, 0xe1, 0x05,
0xa0, 0x8d, 0x1f, 0x3d, 0xc7, 0x15, 0xd8, 0x13, 0xd0, 0x16, 0x3d, 0xe3, 0xcd, 0x76, 0x99, 0x26,
0xcf, 0x72, 0x77, 0x7b, 0x44, 0x49, 0xa7, 0xcb, 0xb3, 0x7d, 0x00, 0x00, 0x00, 0xff, 0xff, 0x02,
0x10, 0x64, 0xec, 0x62, 0x01, 0x00, 0x00,
var file_groupcache_proto_rawDesc = []byte{
0x0a, 0x10, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x63, 0x61, 0x63, 0x68, 0x65, 0x2e, 0x70, 0x72, 0x6f,
0x74, 0x6f, 0x12, 0x0c, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, 0x62,
0x22, 0x54, 0x0a, 0x0a, 0x47, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x14,
0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09, 0x52, 0x05, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02, 0x20, 0x02, 0x28,
0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61,
0x74, 0x69, 0x6f, 0x6e, 0x18, 0x03, 0x20, 0x02, 0x28, 0x03, 0x52, 0x0a, 0x67, 0x65, 0x6e, 0x65,
0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x22, 0x7a, 0x0a, 0x0b, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73,
0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x18, 0x01,
0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12, 0x1d, 0x0a, 0x0a, 0x6d,
0x69, 0x6e, 0x75, 0x74, 0x65, 0x5f, 0x71, 0x70, 0x73, 0x18, 0x02, 0x20, 0x01, 0x28, 0x01, 0x52,
0x09, 0x6d, 0x69, 0x6e, 0x75, 0x74, 0x65, 0x51, 0x70, 0x73, 0x12, 0x16, 0x0a, 0x06, 0x65, 0x78,
0x70, 0x69, 0x72, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x03, 0x52, 0x06, 0x65, 0x78, 0x70, 0x69,
0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e,
0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72, 0x61, 0x74, 0x69,
0x6f, 0x6e, 0x22, 0x82, 0x01, 0x0a, 0x0a, 0x53, 0x65, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
0x74, 0x12, 0x14, 0x0a, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x18, 0x01, 0x20, 0x02, 0x28, 0x09,
0x52, 0x05, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x12, 0x10, 0x0a, 0x03, 0x6b, 0x65, 0x79, 0x18, 0x02,
0x20, 0x02, 0x28, 0x09, 0x52, 0x03, 0x6b, 0x65, 0x79, 0x12, 0x14, 0x0a, 0x05, 0x76, 0x61, 0x6c,
0x75, 0x65, 0x18, 0x03, 0x20, 0x01, 0x28, 0x0c, 0x52, 0x05, 0x76, 0x61, 0x6c, 0x75, 0x65, 0x12,
0x16, 0x0a, 0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x18, 0x04, 0x20, 0x01, 0x28, 0x03, 0x52,
0x06, 0x65, 0x78, 0x70, 0x69, 0x72, 0x65, 0x12, 0x1e, 0x0a, 0x0a, 0x67, 0x65, 0x6e, 0x65, 0x72,
0x61, 0x74, 0x69, 0x6f, 0x6e, 0x18, 0x05, 0x20, 0x01, 0x28, 0x03, 0x52, 0x0a, 0x67, 0x65, 0x6e,
0x65, 0x72, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x32, 0x4a, 0x0a, 0x0a, 0x47, 0x72, 0x6f, 0x75, 0x70,
0x43, 0x61, 0x63, 0x68, 0x65, 0x12, 0x3c, 0x0a, 0x03, 0x47, 0x65, 0x74, 0x12, 0x18, 0x2e, 0x67,
0x72, 0x6f, 0x75, 0x70, 0x63, 0x61, 0x63, 0x68, 0x65, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x52,
0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x19, 0x2e, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x63, 0x61,
0x63, 0x68, 0x65, 0x70, 0x62, 0x2e, 0x47, 0x65, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73,
0x65, 0x22, 0x00, 0x42, 0x2f, 0x5a, 0x2d, 0x67, 0x69, 0x74, 0x68, 0x75, 0x62, 0x2e, 0x63, 0x6f,
0x6d, 0x2f, 0x6d, 0x61, 0x69, 0x6c, 0x67, 0x75, 0x6e, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x63,
0x61, 0x63, 0x68, 0x65, 0x2f, 0x76, 0x32, 0x2f, 0x67, 0x72, 0x6f, 0x75, 0x70, 0x63, 0x61, 0x63,
0x68, 0x65, 0x70, 0x62,
}
var (
file_groupcache_proto_rawDescOnce sync.Once
file_groupcache_proto_rawDescData = file_groupcache_proto_rawDesc
)
func file_groupcache_proto_rawDescGZIP() []byte {
file_groupcache_proto_rawDescOnce.Do(func() {
file_groupcache_proto_rawDescData = protoimpl.X.CompressGZIP(file_groupcache_proto_rawDescData)
})
return file_groupcache_proto_rawDescData
}
var file_groupcache_proto_msgTypes = make([]protoimpl.MessageInfo, 3)
var file_groupcache_proto_goTypes = []interface{}{
(*GetRequest)(nil), // 0: groupcachepb.GetRequest
(*GetResponse)(nil), // 1: groupcachepb.GetResponse
(*SetRequest)(nil), // 2: groupcachepb.SetRequest
}
var file_groupcache_proto_depIdxs = []int32{
0, // 0: groupcachepb.GroupCache.Get:input_type -> groupcachepb.GetRequest
1, // 1: groupcachepb.GroupCache.Get:output_type -> groupcachepb.GetResponse
1, // [1:2] is the sub-list for method output_type
0, // [0:1] is the sub-list for method input_type
0, // [0:0] is the sub-list for extension type_name
0, // [0:0] is the sub-list for extension extendee
0, // [0:0] is the sub-list for field type_name
}
func init() { file_groupcache_proto_init() }
func file_groupcache_proto_init() {
if File_groupcache_proto != nil {
return
}
if !protoimpl.UnsafeEnabled {
file_groupcache_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_groupcache_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*GetResponse); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
file_groupcache_proto_msgTypes[2].Exporter = func(v interface{}, i int) interface{} {
switch v := v.(*SetRequest); i {
case 0:
return &v.state
case 1:
return &v.sizeCache
case 2:
return &v.unknownFields
default:
return nil
}
}
}
type x struct{}
out := protoimpl.TypeBuilder{
File: protoimpl.DescBuilder{
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
RawDescriptor: file_groupcache_proto_rawDesc,
NumEnums: 0,
NumMessages: 3,
NumExtensions: 0,
NumServices: 1,
},
GoTypes: file_groupcache_proto_goTypes,
DependencyIndexes: file_groupcache_proto_depIdxs,
MessageInfos: file_groupcache_proto_msgTypes,
}.Build()
File_groupcache_proto = out.File
file_groupcache_proto_rawDesc = nil
file_groupcache_proto_goTypes = nil
file_groupcache_proto_depIdxs = nil
}

View File

@ -16,17 +16,21 @@ limitations under the License.
syntax = "proto2";
option go_package = "github.com/mailgun/groupcache/v2/groupcachepb";
package groupcachepb;
message GetRequest {
required string group = 1;
required string key = 2; // not actually required/guaranteed to be UTF-8
required int64 generation = 3;
}
message GetResponse {
optional bytes value = 1;
optional double minute_qps = 2;
optional int64 expire = 3;
optional int64 generation = 4;
}
message SetRequest {
@ -34,6 +38,7 @@ message SetRequest {
required string key = 2;
optional bytes value = 3;
optional int64 expire = 4;
optional int64 generation = 5;
}
service GroupCache {

View File

@ -224,7 +224,12 @@ func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
expire = time.Unix(*out.Expire/int64(time.Second), *out.Expire%int64(time.Second))
}
group.localSet(*out.Key, out.Value, expire, &group.mainCache)
var generation int64
if out.Generation != nil {
generation = *out.Generation
}
group.localSet(*out.Key, out.Value, expire, generation, &group.mainCache)
return
}

View File

@ -26,10 +26,6 @@ type NowFunc func() time.Time
// Cache is an LRU cache. It is not safe for concurrent access.
type Cache struct {
// MaxEntries is the maximum number of cache entries before
// an item is evicted. Zero means no limit.
MaxEntries int
// OnEvicted optionally specifies a callback function to be
// executed when an entry is purged from the cache.
OnEvicted func(key Key, value interface{})
@ -41,15 +37,19 @@ type Cache struct {
ll *list.List
cache map[interface{}]*list.Element
// MaxEntries is the maximum number of cache entries before
// an item is evicted. Zero means no limit.
MaxEntries int
}
// A Key may be any value that is comparable. See http://golang.org/ref/spec#Comparison_operators
type Key interface{}
type entry struct {
expire time.Time
key Key
value interface{}
expire time.Time
}
// New creates a new Cache.
@ -80,7 +80,7 @@ func (c *Cache) Add(key Key, value interface{}, expire time.Time) {
eee.value = value
return
}
ele := c.ll.PushFront(&entry{key, value, expire})
ele := c.ll.PushFront(&entry{expire, key, value})
c.cache[key] = ele
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
c.RemoveOldest()