mirror of
https://github.com/mailgun/groupcache.git
synced 2024-10-01 20:32:05 +00:00
Merge pull request #1 from mailgun/add-ttl
PIP-405: Sinks can now accept an expire time
This commit is contained in:
commit
b9520c6403
1
.gitignore
vendored
1
.gitignore
vendored
@ -1 +1,2 @@
|
|||||||
*~
|
*~
|
||||||
|
.idea/
|
||||||
|
@ -21,6 +21,7 @@ import (
|
|||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// A ByteView holds an immutable view of bytes.
|
// A ByteView holds an immutable view of bytes.
|
||||||
@ -33,6 +34,12 @@ type ByteView struct {
|
|||||||
// If b is non-nil, b is used, else s is used.
|
// If b is non-nil, b is used, else s is used.
|
||||||
b []byte
|
b []byte
|
||||||
s string
|
s string
|
||||||
|
e time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// Returns the expire time associated with this view
|
||||||
|
func (v ByteView) Expire() time.Time {
|
||||||
|
return v.e
|
||||||
}
|
}
|
||||||
|
|
||||||
// Len returns the view's length.
|
// Len returns the view's length.
|
||||||
|
3
go.mod
Normal file
3
go.mod
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
module github.com/golang/groupcache
|
||||||
|
|
||||||
|
require github.com/golang/protobuf v1.3.1
|
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
||||||
|
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
@ -30,6 +30,7 @@ import (
|
|||||||
"strconv"
|
"strconv"
|
||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
pb "github.com/golang/groupcache/groupcachepb"
|
pb "github.com/golang/groupcache/groupcachepb"
|
||||||
"github.com/golang/groupcache/lru"
|
"github.com/golang/groupcache/lru"
|
||||||
@ -310,7 +311,16 @@ func (g *Group) getFromPeer(ctx Context, peer ProtoGetter, key string) (ByteView
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return ByteView{}, err
|
return ByteView{}, err
|
||||||
}
|
}
|
||||||
value := ByteView{b: res.Value}
|
|
||||||
|
var expire time.Time
|
||||||
|
if res.Expire != nil && *res.Expire != 0 {
|
||||||
|
expire = time.Unix(*res.Expire/int64(time.Second), *res.Expire%int64(time.Second))
|
||||||
|
if time.Now().After(expire) {
|
||||||
|
return ByteView{}, errors.New("peer returned expired value")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
value := ByteView{b: res.Value, e: expire}
|
||||||
// TODO(bradfitz): use res.MinuteQps or something smart to
|
// TODO(bradfitz): use res.MinuteQps or something smart to
|
||||||
// conditionally populate hotCache. For now just do it some
|
// conditionally populate hotCache. For now just do it some
|
||||||
// percentage of the time.
|
// percentage of the time.
|
||||||
@ -418,7 +428,7 @@ func (c *cache) add(key string, value ByteView) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
c.lru.Add(key, value)
|
c.lru.Add(key, value, value.Expire())
|
||||||
c.nbytes += int64(len(key)) + int64(value.Len())
|
c.nbytes += int64(len(key)) + int64(value.Len())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,8 +36,8 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
once sync.Once
|
once sync.Once
|
||||||
stringGroup, protoGroup Getter
|
stringGroup, protoGroup, expireGroup Getter
|
||||||
|
|
||||||
stringc = make(chan string)
|
stringc = make(chan string)
|
||||||
|
|
||||||
@ -52,6 +52,7 @@ var (
|
|||||||
const (
|
const (
|
||||||
stringGroupName = "string-group"
|
stringGroupName = "string-group"
|
||||||
protoGroupName = "proto-group"
|
protoGroupName = "proto-group"
|
||||||
|
expireGroupName = "expire-group"
|
||||||
testMessageType = "google3/net/groupcache/go/test_proto.TestMessage"
|
testMessageType = "google3/net/groupcache/go/test_proto.TestMessage"
|
||||||
fromChan = "from-chan"
|
fromChan = "from-chan"
|
||||||
cacheSize = 1 << 20
|
cacheSize = 1 << 20
|
||||||
@ -63,7 +64,7 @@ func testSetup() {
|
|||||||
key = <-stringc
|
key = <-stringc
|
||||||
}
|
}
|
||||||
cacheFills.Add(1)
|
cacheFills.Add(1)
|
||||||
return dest.SetString("ECHO:" + key)
|
return dest.SetString("ECHO:"+key, time.Time{})
|
||||||
}))
|
}))
|
||||||
|
|
||||||
protoGroup = NewGroup(protoGroupName, cacheSize, GetterFunc(func(_ Context, key string, dest Sink) error {
|
protoGroup = NewGroup(protoGroupName, cacheSize, GetterFunc(func(_ Context, key string, dest Sink) error {
|
||||||
@ -74,7 +75,12 @@ func testSetup() {
|
|||||||
return dest.SetProto(&testpb.TestMessage{
|
return dest.SetProto(&testpb.TestMessage{
|
||||||
Name: proto.String("ECHO:" + key),
|
Name: proto.String("ECHO:" + key),
|
||||||
City: proto.String("SOME-CITY"),
|
City: proto.String("SOME-CITY"),
|
||||||
})
|
}, time.Time{})
|
||||||
|
}))
|
||||||
|
|
||||||
|
expireGroup = NewGroup(expireGroupName, cacheSize, GetterFunc(func(_ Context, key string, dest Sink) error {
|
||||||
|
cacheFills.Add(1)
|
||||||
|
return dest.SetString("ECHO:"+key, time.Now().Add(time.Millisecond*100))
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,6 +191,24 @@ func TestCaching(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestCachingExpire(t *testing.T) {
|
||||||
|
once.Do(testSetup)
|
||||||
|
fills := countFills(func() {
|
||||||
|
for i := 0; i < 3; i++ {
|
||||||
|
var s string
|
||||||
|
if err := expireGroup.Get(dummyCtx, "TestCachingExpire-key", StringSink(&s)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if i == 1 {
|
||||||
|
time.Sleep(time.Millisecond * 150)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if fills != 2 {
|
||||||
|
t.Errorf("expected 2 cache fill; got %d", fills)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestCacheEviction(t *testing.T) {
|
func TestCacheEviction(t *testing.T) {
|
||||||
once.Do(testSetup)
|
once.Do(testSetup)
|
||||||
testKey := "TestCacheEviction-key"
|
testKey := "TestCacheEviction-key"
|
||||||
@ -261,7 +285,7 @@ func TestPeers(t *testing.T) {
|
|||||||
localHits := 0
|
localHits := 0
|
||||||
getter := func(_ Context, key string, dest Sink) error {
|
getter := func(_ Context, key string, dest Sink) error {
|
||||||
localHits++
|
localHits++
|
||||||
return dest.SetString("got:" + key)
|
return dest.SetString("got:"+key, time.Time{})
|
||||||
}
|
}
|
||||||
testGroup := newGroup("TestPeers-group", cacheSize, GetterFunc(getter), peerList)
|
testGroup := newGroup("TestPeers-group", cacheSize, GetterFunc(getter), peerList)
|
||||||
run := func(name string, n int, wantSummary string) {
|
run := func(name string, n int, wantSummary string) {
|
||||||
@ -345,7 +369,7 @@ func TestAllocatingByteSliceTarget(t *testing.T) {
|
|||||||
sink := AllocatingByteSliceSink(&dst)
|
sink := AllocatingByteSliceSink(&dst)
|
||||||
|
|
||||||
inBytes := []byte("some bytes")
|
inBytes := []byte("some bytes")
|
||||||
sink.SetBytes(inBytes)
|
sink.SetBytes(inBytes, time.Time{})
|
||||||
if want := "some bytes"; string(dst) != want {
|
if want := "some bytes"; string(dst) != want {
|
||||||
t.Errorf("SetBytes resulted in %q; want %q", dst, want)
|
t.Errorf("SetBytes resulted in %q; want %q", dst, want)
|
||||||
}
|
}
|
||||||
@ -388,7 +412,7 @@ func TestNoDedup(t *testing.T) {
|
|||||||
const testkey = "testkey"
|
const testkey = "testkey"
|
||||||
const testval = "testval"
|
const testval = "testval"
|
||||||
g := newGroup("testgroup", 1024, GetterFunc(func(_ Context, key string, dest Sink) error {
|
g := newGroup("testgroup", 1024, GetterFunc(func(_ Context, key string, dest Sink) error {
|
||||||
return dest.SetString(testval)
|
return dest.SetString(testval, time.Time{})
|
||||||
}), nil)
|
}), nil)
|
||||||
|
|
||||||
orderedGroup := &orderedFlightGroup{
|
orderedGroup := &orderedFlightGroup{
|
||||||
|
@ -1,27 +1,43 @@
|
|||||||
// Code generated by protoc-gen-go.
|
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||||
// source: groupcache.proto
|
// source: groupcache.proto
|
||||||
// DO NOT EDIT!
|
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package groupcachepb is a generated protocol buffer package.
|
||||||
|
|
||||||
|
It is generated from these files:
|
||||||
|
groupcache.proto
|
||||||
|
|
||||||
|
It has these top-level messages:
|
||||||
|
GetRequest
|
||||||
|
GetResponse
|
||||||
|
*/
|
||||||
package groupcachepb
|
package groupcachepb
|
||||||
|
|
||||||
import proto "github.com/golang/protobuf/proto"
|
import proto "github.com/golang/protobuf/proto"
|
||||||
import json "encoding/json"
|
import fmt "fmt"
|
||||||
import math "math"
|
import math "math"
|
||||||
|
|
||||||
// Reference proto, json, and math imports to suppress error if they are not otherwise used.
|
// Reference imports to suppress errors if they are not otherwise used.
|
||||||
var _ = proto.Marshal
|
var _ = proto.Marshal
|
||||||
var _ = &json.SyntaxError{}
|
var _ = fmt.Errorf
|
||||||
var _ = math.Inf
|
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
|
||||||
|
|
||||||
type GetRequest struct {
|
type GetRequest struct {
|
||||||
Group *string `protobuf:"bytes,1,req,name=group" json:"group,omitempty"`
|
Group *string `protobuf:"bytes,1,req,name=group" json:"group,omitempty"`
|
||||||
Key *string `protobuf:"bytes,2,req,name=key" json:"key,omitempty"`
|
Key *string `protobuf:"bytes,2,req,name=key" json:"key,omitempty"`
|
||||||
XXX_unrecognized []byte `json:"-"`
|
XXX_unrecognized []byte `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GetRequest) Reset() { *m = GetRequest{} }
|
func (m *GetRequest) Reset() { *m = GetRequest{} }
|
||||||
func (m *GetRequest) String() string { return proto.CompactTextString(m) }
|
func (m *GetRequest) String() string { return proto.CompactTextString(m) }
|
||||||
func (*GetRequest) ProtoMessage() {}
|
func (*GetRequest) ProtoMessage() {}
|
||||||
|
func (*GetRequest) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
|
||||||
|
|
||||||
func (m *GetRequest) GetGroup() string {
|
func (m *GetRequest) GetGroup() string {
|
||||||
if m != nil && m.Group != nil {
|
if m != nil && m.Group != nil {
|
||||||
@ -39,13 +55,15 @@ func (m *GetRequest) GetKey() string {
|
|||||||
|
|
||||||
type GetResponse struct {
|
type GetResponse struct {
|
||||||
Value []byte `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
|
Value []byte `protobuf:"bytes,1,opt,name=value" json:"value,omitempty"`
|
||||||
MinuteQps *float64 `protobuf:"fixed64,2,opt,name=minute_qps" json:"minute_qps,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:"-"`
|
XXX_unrecognized []byte `json:"-"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *GetResponse) Reset() { *m = GetResponse{} }
|
func (m *GetResponse) Reset() { *m = GetResponse{} }
|
||||||
func (m *GetResponse) String() string { return proto.CompactTextString(m) }
|
func (m *GetResponse) String() string { return proto.CompactTextString(m) }
|
||||||
func (*GetResponse) ProtoMessage() {}
|
func (*GetResponse) ProtoMessage() {}
|
||||||
|
func (*GetResponse) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
|
||||||
|
|
||||||
func (m *GetResponse) GetValue() []byte {
|
func (m *GetResponse) GetValue() []byte {
|
||||||
if m != nil {
|
if m != nil {
|
||||||
@ -61,5 +79,33 @@ func (m *GetResponse) GetMinuteQps() float64 {
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func (m *GetResponse) GetExpire() int64 {
|
||||||
|
if m != nil && m.Expire != nil {
|
||||||
|
return *m.Expire
|
||||||
|
}
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
proto.RegisterType((*GetRequest)(nil), "groupcachepb.GetRequest")
|
||||||
|
proto.RegisterType((*GetResponse)(nil), "groupcachepb.GetResponse")
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() { proto.RegisterFile("groupcache.proto", fileDescriptor0) }
|
||||||
|
|
||||||
|
var fileDescriptor0 = []byte{
|
||||||
|
// 197 bytes of a gzipped FileDescriptorProto
|
||||||
|
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0x12, 0x48, 0x2f, 0xca, 0x2f,
|
||||||
|
0x2d, 0x48, 0x4e, 0x4c, 0xce, 0x48, 0xd5, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0xe2, 0x41, 0x88,
|
||||||
|
0x14, 0x24, 0x29, 0x99, 0x70, 0x71, 0xb9, 0xa7, 0x96, 0x04, 0xa5, 0x16, 0x96, 0xa6, 0x16, 0x97,
|
||||||
|
0x08, 0x89, 0x70, 0xb1, 0x82, 0x65, 0x25, 0x18, 0x15, 0x98, 0x34, 0x38, 0x83, 0x20, 0x1c, 0x21,
|
||||||
|
0x01, 0x2e, 0xe6, 0xec, 0xd4, 0x4a, 0x09, 0x26, 0xb0, 0x18, 0x88, 0xa9, 0x14, 0xc5, 0xc5, 0x0d,
|
||||||
|
0xd6, 0x55, 0x5c, 0x90, 0x9f, 0x57, 0x9c, 0x0a, 0xd2, 0x56, 0x96, 0x98, 0x53, 0x9a, 0x2a, 0xc1,
|
||||||
|
0xa8, 0xc0, 0xa8, 0xc1, 0x13, 0x04, 0xe1, 0x08, 0xc9, 0x72, 0x71, 0xe5, 0x66, 0xe6, 0x95, 0x96,
|
||||||
|
0xa4, 0xc6, 0x17, 0x16, 0x14, 0x4b, 0x30, 0x29, 0x30, 0x6a, 0x30, 0x06, 0x71, 0x42, 0x44, 0x02,
|
||||||
|
0x0b, 0x8a, 0x85, 0xc4, 0xb8, 0xd8, 0x52, 0x2b, 0x0a, 0x32, 0x8b, 0x52, 0x25, 0x98, 0x15, 0x18,
|
||||||
|
0x35, 0x98, 0x83, 0xa0, 0x3c, 0x23, 0x2f, 0x2e, 0x2e, 0x77, 0x90, 0xb5, 0xce, 0x20, 0x17, 0x0a,
|
||||||
|
0xd9, 0x70, 0x31, 0xbb, 0xa7, 0x96, 0x08, 0x49, 0xe8, 0x21, 0xbb, 0x5a, 0x0f, 0xe1, 0x64, 0x29,
|
||||||
|
0x49, 0x2c, 0x32, 0x10, 0x67, 0x29, 0x31, 0x00, 0x02, 0x00, 0x00, 0xff, 0xff, 0xd4, 0x73, 0xe1,
|
||||||
|
0xb8, 0xfe, 0x00, 0x00, 0x00,
|
||||||
}
|
}
|
||||||
|
@ -26,6 +26,7 @@ message GetRequest {
|
|||||||
message GetResponse {
|
message GetResponse {
|
||||||
optional bytes value = 1;
|
optional bytes value = 1;
|
||||||
optional double minute_qps = 2;
|
optional double minute_qps = 2;
|
||||||
|
optional int64 expire = 3;
|
||||||
}
|
}
|
||||||
|
|
||||||
service GroupCache {
|
service GroupCache {
|
||||||
|
18
http.go
18
http.go
@ -163,15 +163,27 @@ func (p *HTTPPool) ServeHTTP(w http.ResponseWriter, r *http.Request) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group.Stats.ServerRequests.Add(1)
|
group.Stats.ServerRequests.Add(1)
|
||||||
var value []byte
|
var b []byte
|
||||||
err := group.Get(ctx, key, AllocatingByteSliceSink(&value))
|
|
||||||
|
value := AllocatingByteSliceSink(&b)
|
||||||
|
err := group.Get(ctx, key, value)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
view, err := value.view()
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var expireNano int64
|
||||||
|
if !view.e.IsZero() {
|
||||||
|
expireNano = view.Expire().UnixNano()
|
||||||
|
}
|
||||||
|
|
||||||
// Write the value to the response body as a proto message.
|
// Write the value to the response body as a proto message.
|
||||||
body, err := proto.Marshal(&pb.GetResponse{Value: value})
|
body, err := proto.Marshal(&pb.GetResponse{Value: b, Expire: &expireNano})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
|
@ -117,7 +117,7 @@ func beChildForTestHTTPPool() {
|
|||||||
p.Set(addrToURL(addrs)...)
|
p.Set(addrToURL(addrs)...)
|
||||||
|
|
||||||
getter := GetterFunc(func(ctx Context, key string, dest Sink) error {
|
getter := GetterFunc(func(ctx Context, key string, dest Sink) error {
|
||||||
dest.SetString(strconv.Itoa(*peerIndex) + ":" + key)
|
dest.SetString(strconv.Itoa(*peerIndex)+":"+key, time.Time{})
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
NewGroup("httpPoolTest", 1<<20, getter)
|
NewGroup("httpPoolTest", 1<<20, getter)
|
||||||
|
23
lru/lru.go
23
lru/lru.go
@ -17,7 +17,10 @@ limitations under the License.
|
|||||||
// Package lru implements an LRU cache.
|
// Package lru implements an LRU cache.
|
||||||
package lru
|
package lru
|
||||||
|
|
||||||
import "container/list"
|
import (
|
||||||
|
"container/list"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
// Cache is an LRU cache. It is not safe for concurrent access.
|
// Cache is an LRU cache. It is not safe for concurrent access.
|
||||||
type Cache struct {
|
type Cache struct {
|
||||||
@ -37,8 +40,9 @@ type Cache struct {
|
|||||||
type Key interface{}
|
type Key interface{}
|
||||||
|
|
||||||
type entry struct {
|
type entry struct {
|
||||||
key Key
|
key Key
|
||||||
value interface{}
|
value interface{}
|
||||||
|
expire time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
// New creates a new Cache.
|
// New creates a new Cache.
|
||||||
@ -53,7 +57,7 @@ func New(maxEntries int) *Cache {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Add adds a value to the cache.
|
// Add adds a value to the cache.
|
||||||
func (c *Cache) Add(key Key, value interface{}) {
|
func (c *Cache) Add(key Key, value interface{}, expire time.Time) {
|
||||||
if c.cache == nil {
|
if c.cache == nil {
|
||||||
c.cache = make(map[interface{}]*list.Element)
|
c.cache = make(map[interface{}]*list.Element)
|
||||||
c.ll = list.New()
|
c.ll = list.New()
|
||||||
@ -63,7 +67,7 @@ func (c *Cache) Add(key Key, value interface{}) {
|
|||||||
ee.Value.(*entry).value = value
|
ee.Value.(*entry).value = value
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ele := c.ll.PushFront(&entry{key, value})
|
ele := c.ll.PushFront(&entry{key, value, expire})
|
||||||
c.cache[key] = ele
|
c.cache[key] = ele
|
||||||
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
|
if c.MaxEntries != 0 && c.ll.Len() > c.MaxEntries {
|
||||||
c.RemoveOldest()
|
c.RemoveOldest()
|
||||||
@ -76,8 +80,15 @@ func (c *Cache) Get(key Key) (value interface{}, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if ele, hit := c.cache[key]; hit {
|
if ele, hit := c.cache[key]; hit {
|
||||||
|
entry := ele.Value.(*entry)
|
||||||
|
// If the entry has expired, remove it from the cache
|
||||||
|
if !entry.expire.IsZero() && entry.expire.Before(time.Now()) {
|
||||||
|
c.removeElement(ele)
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
c.ll.MoveToFront(ele)
|
c.ll.MoveToFront(ele)
|
||||||
return ele.Value.(*entry).value, true
|
return entry.value, true
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ package lru
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type simpleStruct struct {
|
type simpleStruct struct {
|
||||||
@ -48,7 +49,7 @@ var getTests = []struct {
|
|||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
for _, tt := range getTests {
|
for _, tt := range getTests {
|
||||||
lru := New(0)
|
lru := New(0)
|
||||||
lru.Add(tt.keyToAdd, 1234)
|
lru.Add(tt.keyToAdd, 1234, time.Time{})
|
||||||
val, ok := lru.Get(tt.keyToGet)
|
val, ok := lru.Get(tt.keyToGet)
|
||||||
if ok != tt.expectedOk {
|
if ok != tt.expectedOk {
|
||||||
t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok)
|
t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok)
|
||||||
@ -60,7 +61,7 @@ func TestGet(t *testing.T) {
|
|||||||
|
|
||||||
func TestRemove(t *testing.T) {
|
func TestRemove(t *testing.T) {
|
||||||
lru := New(0)
|
lru := New(0)
|
||||||
lru.Add("myKey", 1234)
|
lru.Add("myKey", 1234, time.Time{})
|
||||||
if val, ok := lru.Get("myKey"); !ok {
|
if val, ok := lru.Get("myKey"); !ok {
|
||||||
t.Fatal("TestRemove returned no match")
|
t.Fatal("TestRemove returned no match")
|
||||||
} else if val != 1234 {
|
} else if val != 1234 {
|
||||||
@ -82,7 +83,7 @@ func TestEvict(t *testing.T) {
|
|||||||
lru := New(20)
|
lru := New(20)
|
||||||
lru.OnEvicted = onEvictedFun
|
lru.OnEvicted = onEvictedFun
|
||||||
for i := 0; i < 22; i++ {
|
for i := 0; i < 22; i++ {
|
||||||
lru.Add(fmt.Sprintf("myKey%d", i), 1234)
|
lru.Add(fmt.Sprintf("myKey%d", i), 1234, time.Time{})
|
||||||
}
|
}
|
||||||
|
|
||||||
if len(evictedKeys) != 2 {
|
if len(evictedKeys) != 2 {
|
||||||
@ -95,3 +96,28 @@ func TestEvict(t *testing.T) {
|
|||||||
t.Fatalf("got %v in second evicted key; want %s", evictedKeys[1], "myKey1")
|
t.Fatalf("got %v in second evicted key; want %s", evictedKeys[1], "myKey1")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestExpire(t *testing.T) {
|
||||||
|
var tests = []struct {
|
||||||
|
name string
|
||||||
|
key interface{}
|
||||||
|
expectedOk bool
|
||||||
|
expire time.Duration
|
||||||
|
wait time.Duration
|
||||||
|
}{
|
||||||
|
{"not-expired", "myKey", true, time.Second * 1, time.Duration(0)},
|
||||||
|
{"expired", "expiredKey", false, time.Millisecond * 100, time.Millisecond * 150},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
lru := New(0)
|
||||||
|
lru.Add(tt.key, 1234, time.Now().Add(tt.expire))
|
||||||
|
time.Sleep(tt.wait)
|
||||||
|
val, ok := lru.Get(tt.key)
|
||||||
|
if ok != tt.expectedOk {
|
||||||
|
t.Fatalf("%s: cache hit = %v; want %v", tt.name, ok, !ok)
|
||||||
|
} else if ok && val != 1234 {
|
||||||
|
t.Fatalf("%s expected get to return 1234 but got %v", tt.name, val)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
12
proto.sh
Executable file
12
proto.sh
Executable file
@ -0,0 +1,12 @@
|
|||||||
|
#! /bin/sh
|
||||||
|
|
||||||
|
# Make sure the script fails fast.
|
||||||
|
set -e
|
||||||
|
set -u
|
||||||
|
set -x
|
||||||
|
|
||||||
|
PROTO_DIR=groupcachepb
|
||||||
|
|
||||||
|
protoc -I=$PROTO_DIR \
|
||||||
|
--go_out=$PROTO_DIR \
|
||||||
|
$PROTO_DIR/groupcache.proto
|
77
sinks.go
77
sinks.go
@ -18,25 +18,32 @@ package groupcache
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var _ Sink = &stringSink{}
|
||||||
|
var _ Sink = &allocBytesSink{}
|
||||||
|
var _ Sink = &protoSink{}
|
||||||
|
var _ Sink = &truncBytesSink{}
|
||||||
|
var _ Sink = &byteViewSink{}
|
||||||
|
|
||||||
// A Sink receives data from a Get call.
|
// A Sink receives data from a Get call.
|
||||||
//
|
//
|
||||||
// Implementation of Getter must call exactly one of the Set methods
|
// Implementation of Getter must call exactly one of the Set methods
|
||||||
// on success.
|
// on success.
|
||||||
type Sink interface {
|
type Sink interface {
|
||||||
// SetString sets the value to s.
|
// SetString sets the value to s.
|
||||||
SetString(s string) error
|
SetString(s string, e time.Time) error
|
||||||
|
|
||||||
// SetBytes sets the value to the contents of v.
|
// SetBytes sets the value to the contents of v.
|
||||||
// The caller retains ownership of v.
|
// The caller retains ownership of v.
|
||||||
SetBytes(v []byte) error
|
SetBytes(v []byte, e time.Time) error
|
||||||
|
|
||||||
// SetProto sets the value to the encoded version of m.
|
// SetProto sets the value to the encoded version of m.
|
||||||
// The caller retains ownership of m.
|
// The caller retains ownership of m.
|
||||||
SetProto(m proto.Message) error
|
SetProto(m proto.Message, e time.Time) error
|
||||||
|
|
||||||
// view returns a frozen view of the bytes for caching.
|
// view returns a frozen view of the bytes for caching.
|
||||||
view() (ByteView, error)
|
view() (ByteView, error)
|
||||||
@ -60,9 +67,9 @@ func setSinkView(s Sink, v ByteView) error {
|
|||||||
return vs.setView(v)
|
return vs.setView(v)
|
||||||
}
|
}
|
||||||
if v.b != nil {
|
if v.b != nil {
|
||||||
return s.SetBytes(v.b)
|
return s.SetBytes(v.b, v.Expire())
|
||||||
}
|
}
|
||||||
return s.SetString(v.s)
|
return s.SetString(v.s, v.Expire())
|
||||||
}
|
}
|
||||||
|
|
||||||
// StringSink returns a Sink that populates the provided string pointer.
|
// StringSink returns a Sink that populates the provided string pointer.
|
||||||
@ -81,24 +88,26 @@ func (s *stringSink) view() (ByteView, error) {
|
|||||||
return s.v, nil
|
return s.v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stringSink) SetString(v string) error {
|
func (s *stringSink) SetString(v string, e time.Time) error {
|
||||||
s.v.b = nil
|
s.v.b = nil
|
||||||
s.v.s = v
|
s.v.s = v
|
||||||
*s.sp = v
|
*s.sp = v
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stringSink) SetBytes(v []byte) error {
|
func (s *stringSink) SetBytes(v []byte, e time.Time) error {
|
||||||
return s.SetString(string(v))
|
return s.SetString(string(v), e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *stringSink) SetProto(m proto.Message) error {
|
func (s *stringSink) SetProto(m proto.Message, e time.Time) error {
|
||||||
b, err := proto.Marshal(m)
|
b, err := proto.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.v.b = b
|
s.v.b = b
|
||||||
*s.sp = string(b)
|
*s.sp = string(b)
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,22 +141,22 @@ func (s *byteViewSink) view() (ByteView, error) {
|
|||||||
return *s.dst, nil
|
return *s.dst, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *byteViewSink) SetProto(m proto.Message) error {
|
func (s *byteViewSink) SetProto(m proto.Message, e time.Time) error {
|
||||||
b, err := proto.Marshal(m)
|
b, err := proto.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
*s.dst = ByteView{b: b}
|
*s.dst = ByteView{b: b, e: e}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *byteViewSink) SetBytes(b []byte) error {
|
func (s *byteViewSink) SetBytes(b []byte, e time.Time) error {
|
||||||
*s.dst = ByteView{b: cloneBytes(b)}
|
*s.dst = ByteView{b: cloneBytes(b), e: e}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *byteViewSink) SetString(v string) error {
|
func (s *byteViewSink) SetString(v string, e time.Time) error {
|
||||||
*s.dst = ByteView{s: v}
|
*s.dst = ByteView{s: v, e: e}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -161,6 +170,7 @@ func ProtoSink(m proto.Message) Sink {
|
|||||||
type protoSink struct {
|
type protoSink struct {
|
||||||
dst proto.Message // authoritative value
|
dst proto.Message // authoritative value
|
||||||
typ string
|
typ string
|
||||||
|
ttl time.Duration
|
||||||
|
|
||||||
v ByteView // encoded
|
v ByteView // encoded
|
||||||
}
|
}
|
||||||
@ -169,17 +179,18 @@ func (s *protoSink) view() (ByteView, error) {
|
|||||||
return s.v, nil
|
return s.v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *protoSink) SetBytes(b []byte) error {
|
func (s *protoSink) SetBytes(b []byte, e time.Time) error {
|
||||||
err := proto.Unmarshal(b, s.dst)
|
err := proto.Unmarshal(b, s.dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
s.v.b = cloneBytes(b)
|
s.v.b = cloneBytes(b)
|
||||||
s.v.s = ""
|
s.v.s = ""
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *protoSink) SetString(v string) error {
|
func (s *protoSink) SetString(v string, e time.Time) error {
|
||||||
b := []byte(v)
|
b := []byte(v)
|
||||||
err := proto.Unmarshal(b, s.dst)
|
err := proto.Unmarshal(b, s.dst)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -187,10 +198,11 @@ func (s *protoSink) SetString(v string) error {
|
|||||||
}
|
}
|
||||||
s.v.b = b
|
s.v.b = b
|
||||||
s.v.s = ""
|
s.v.s = ""
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *protoSink) SetProto(m proto.Message) error {
|
func (s *protoSink) SetProto(m proto.Message, e time.Time) error {
|
||||||
b, err := proto.Marshal(m)
|
b, err := proto.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
@ -205,6 +217,7 @@ func (s *protoSink) SetProto(m proto.Message) error {
|
|||||||
}
|
}
|
||||||
s.v.b = b
|
s.v.b = b
|
||||||
s.v.s = ""
|
s.v.s = ""
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,35 +247,37 @@ func (s *allocBytesSink) setView(v ByteView) error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *allocBytesSink) SetProto(m proto.Message) error {
|
func (s *allocBytesSink) SetProto(m proto.Message, e time.Time) error {
|
||||||
b, err := proto.Marshal(m)
|
b, err := proto.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.setBytesOwned(b)
|
return s.setBytesOwned(b, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *allocBytesSink) SetBytes(b []byte) error {
|
func (s *allocBytesSink) SetBytes(b []byte, e time.Time) error {
|
||||||
return s.setBytesOwned(cloneBytes(b))
|
return s.setBytesOwned(cloneBytes(b), e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *allocBytesSink) setBytesOwned(b []byte) error {
|
func (s *allocBytesSink) setBytesOwned(b []byte, e time.Time) error {
|
||||||
if s.dst == nil {
|
if s.dst == nil {
|
||||||
return errors.New("nil AllocatingByteSliceSink *[]byte dst")
|
return errors.New("nil AllocatingByteSliceSink *[]byte dst")
|
||||||
}
|
}
|
||||||
*s.dst = cloneBytes(b) // another copy, protecting the read-only s.v.b view
|
*s.dst = cloneBytes(b) // another copy, protecting the read-only s.v.b view
|
||||||
s.v.b = b
|
s.v.b = b
|
||||||
s.v.s = ""
|
s.v.s = ""
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *allocBytesSink) SetString(v string) error {
|
func (s *allocBytesSink) SetString(v string, e time.Time) error {
|
||||||
if s.dst == nil {
|
if s.dst == nil {
|
||||||
return errors.New("nil AllocatingByteSliceSink *[]byte dst")
|
return errors.New("nil AllocatingByteSliceSink *[]byte dst")
|
||||||
}
|
}
|
||||||
*s.dst = []byte(v)
|
*s.dst = []byte(v)
|
||||||
s.v.b = nil
|
s.v.b = nil
|
||||||
s.v.s = v
|
s.v.s = v
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,19 +298,19 @@ func (s *truncBytesSink) view() (ByteView, error) {
|
|||||||
return s.v, nil
|
return s.v, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *truncBytesSink) SetProto(m proto.Message) error {
|
func (s *truncBytesSink) SetProto(m proto.Message, e time.Time) error {
|
||||||
b, err := proto.Marshal(m)
|
b, err := proto.Marshal(m)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return s.setBytesOwned(b)
|
return s.setBytesOwned(b, e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *truncBytesSink) SetBytes(b []byte) error {
|
func (s *truncBytesSink) SetBytes(b []byte, e time.Time) error {
|
||||||
return s.setBytesOwned(cloneBytes(b))
|
return s.setBytesOwned(cloneBytes(b), e)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *truncBytesSink) setBytesOwned(b []byte) error {
|
func (s *truncBytesSink) setBytesOwned(b []byte, e time.Time) error {
|
||||||
if s.dst == nil {
|
if s.dst == nil {
|
||||||
return errors.New("nil TruncatingByteSliceSink *[]byte dst")
|
return errors.New("nil TruncatingByteSliceSink *[]byte dst")
|
||||||
}
|
}
|
||||||
@ -305,10 +320,11 @@ func (s *truncBytesSink) setBytesOwned(b []byte) error {
|
|||||||
}
|
}
|
||||||
s.v.b = b
|
s.v.b = b
|
||||||
s.v.s = ""
|
s.v.s = ""
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *truncBytesSink) SetString(v string) error {
|
func (s *truncBytesSink) SetString(v string, e time.Time) error {
|
||||||
if s.dst == nil {
|
if s.dst == nil {
|
||||||
return errors.New("nil TruncatingByteSliceSink *[]byte dst")
|
return errors.New("nil TruncatingByteSliceSink *[]byte dst")
|
||||||
}
|
}
|
||||||
@ -318,5 +334,6 @@ func (s *truncBytesSink) SetString(v string) error {
|
|||||||
}
|
}
|
||||||
s.v.b = nil
|
s.v.b = nil
|
||||||
s.v.s = v
|
s.v.s = v
|
||||||
|
s.v.e = e
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user