mirror of
https://github.com/mailgun/groupcache.git
synced 2024-09-28 23:00:56 +00:00
Compare commits
6 Commits
df5395112d
...
bde4250129
Author | SHA1 | Date | |
---|---|---|---|
|
bde4250129 | ||
|
fdad20ab3a | ||
|
3c50bed6df | ||
|
a139af4144 | ||
|
2bec0534cf | ||
|
81442521fa |
46
.github/workflows/on-pull-request.yaml
vendored
Normal file
46
.github/workflows/on-pull-request.yaml
vendored
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
name: CI
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
- main
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
on-pull-request:
|
||||||
|
name: test
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
go-version:
|
||||||
|
- 1.18.x
|
||||||
|
- 1.19.x
|
||||||
|
os: [ ubuntu-latest ]
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@master
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v2
|
||||||
|
with:
|
||||||
|
go-version: ${{ matrix.go-version }}
|
||||||
|
|
||||||
|
- run: go env
|
||||||
|
|
||||||
|
- name: Cache deps
|
||||||
|
uses: actions/cache@v2
|
||||||
|
with:
|
||||||
|
path: ~/go/pkg/mod
|
||||||
|
key: ${{ runner.os }}-go-${{ hashFiles('**/go.sum') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-go-
|
||||||
|
|
||||||
|
- name: Install deps
|
||||||
|
run: go mod download
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
run: go test ./...
|
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,2 +1,3 @@
|
|||||||
*~
|
*~
|
||||||
.idea/
|
.idea/
|
||||||
|
.DS_Store
|
||||||
|
20
.travis.yml
20
.travis.yml
@ -1,20 +0,0 @@
|
|||||||
language: go
|
|
||||||
go_import_path: github.com/mailgun/groupcache
|
|
||||||
|
|
||||||
os: linux
|
|
||||||
dist: xenial
|
|
||||||
sudo: false
|
|
||||||
|
|
||||||
script:
|
|
||||||
- go test ./...
|
|
||||||
|
|
||||||
go:
|
|
||||||
- 1.13.x
|
|
||||||
- 1.14.x
|
|
||||||
- 1.15.x
|
|
||||||
- 1.17.x
|
|
||||||
- master
|
|
||||||
|
|
||||||
cache:
|
|
||||||
directories:
|
|
||||||
- $GOPATH/pkg
|
|
@ -10,7 +10,7 @@ modifications.
|
|||||||
groupcache is a caching and cache-filling library, intended as a
|
groupcache is a caching and cache-filling library, intended as a
|
||||||
replacement for memcached in many cases.
|
replacement for memcached in many cases.
|
||||||
|
|
||||||
For API docs and examples, see http://godoc.org/github.com/mailgun/groupcache
|
For API docs and examples, see http://godoc.org/github.com/mailgun/groupcache/v2
|
||||||
|
|
||||||
|
|
||||||
### Modifications from original library
|
### Modifications from original library
|
||||||
|
94
cmd/server/main.go
Normal file
94
cmd/server/main.go
Normal file
@ -0,0 +1,94 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"strings"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/mailgun/groupcache/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
var store = map[string]string{}
|
||||||
|
|
||||||
|
var group = groupcache.NewGroup("cache1", 64<<20, groupcache.GetterFunc(
|
||||||
|
func(ctx context.Context, key string, dest groupcache.Sink) error {
|
||||||
|
fmt.Printf("Get Called\n")
|
||||||
|
v, ok := store[key]
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("key not set")
|
||||||
|
} else {
|
||||||
|
if err := dest.SetBytes([]byte(v), time.Now().Add(10*time.Minute)); err != nil {
|
||||||
|
log.Printf("Failed to set cache value for key '%s' - %v\n", key, err)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
))
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
addr := flag.String("addr", ":8080", "server address")
|
||||||
|
addr2 := flag.String("api-addr", ":8081", "api server address")
|
||||||
|
peers := flag.String("pool", "http://localhost:8080", "server pool list")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
p := strings.Split(*peers, ",")
|
||||||
|
pool := groupcache.NewHTTPPoolOpts(fmt.Sprintf("http://%s", *addr), &groupcache.HTTPPoolOptions{})
|
||||||
|
pool.Set(p...)
|
||||||
|
|
||||||
|
http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
r.ParseForm()
|
||||||
|
key := r.FormValue("key")
|
||||||
|
value := r.FormValue("value")
|
||||||
|
fmt.Printf("Set: [%s]%s\n", key, value)
|
||||||
|
store[key] = value
|
||||||
|
})
|
||||||
|
|
||||||
|
http.HandleFunc("/cache", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
key := r.FormValue("key")
|
||||||
|
|
||||||
|
fmt.Printf("Fetching value for key '%s'\n", key)
|
||||||
|
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
var b []byte
|
||||||
|
err := group.Get(ctx, key, groupcache.AllocatingByteSliceSink(&b))
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, err.Error(), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Write(b)
|
||||||
|
w.Write([]byte{'\n'})
|
||||||
|
})
|
||||||
|
|
||||||
|
server := http.Server{
|
||||||
|
Addr: *addr,
|
||||||
|
Handler: pool,
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := server.ListenAndServe(); err != nil {
|
||||||
|
log.Fatalf("Failed to start HTTP server - %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
if err := http.ListenAndServe(*addr2, nil); err != nil {
|
||||||
|
log.Fatalf("Failed to start API HTTP server - %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
fmt.Printf("Running...\n")
|
||||||
|
termChan := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(termChan, syscall.SIGINT, syscall.SIGTERM)
|
||||||
|
<-termChan
|
||||||
|
}
|
17
go.mod
17
go.mod
@ -1,10 +1,19 @@
|
|||||||
module github.com/mailgun/groupcache/v2
|
module github.com/mailgun/groupcache/v2
|
||||||
|
|
||||||
|
go 1.19
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/golang/protobuf v1.3.1
|
github.com/golang/protobuf v1.5.2
|
||||||
github.com/segmentio/fasthash v1.0.3
|
github.com/segmentio/fasthash v1.0.3
|
||||||
github.com/sirupsen/logrus v1.6.0
|
github.com/sirupsen/logrus v1.9.0
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a // indirect
|
github.com/stretchr/testify v1.8.1
|
||||||
)
|
)
|
||||||
|
|
||||||
go 1.15
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 // indirect
|
||||||
|
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f // indirect
|
||||||
|
google.golang.org/protobuf v1.28.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
43
go.sum
43
go.sum
@ -1,18 +1,37 @@
|
|||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
|
github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
|
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
|
||||||
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
|
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
|
||||||
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
|
github.com/segmentio/fasthash v1.0.3 h1:EI9+KE1EwvMLBWwjpRDc+fEM+prwxDYbslddQGtrmhM=
|
||||||
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
github.com/segmentio/fasthash v1.0.3/go.mod h1:waKX8l2N8yckOgmSsXJi7x1ZfdKZ4x7KRMzBtS3oedY=
|
||||||
github.com/sirupsen/logrus v1.6.0 h1:UBcNElsrwanuuMsnGSlYmtmgbb23qDR5dG+6X6Oo89I=
|
github.com/sirupsen/logrus v1.9.0 h1:trlNQbNUG3OdDrDil03MCb1H2o9nJ1x4/5LYw7byDE0=
|
||||||
github.com/sirupsen/logrus v1.6.0/go.mod h1:7uNnSEd1DgxDLC74fIahvMZmmYsHGZGEOFrfsX/uA88=
|
github.com/sirupsen/logrus v1.9.0/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ=
|
||||||
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
|
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
|
||||||
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a h1:dGzPydgVsqGcTRVwiLJ1jVbufYwmzD3LfVPLKsKg+0k=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
|
||||||
|
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
|
||||||
|
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8 h1:h+EGohizhe9XlX18rfpa8k8RAc5XyaeamM+0VHRd4lc=
|
||||||
|
golang.org/x/sys v0.0.0-20220919091848-fb04ddd9f9c8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f h1:uF6paiQQebLeSXkrTqHqz0MXhXXS1KgF41eUdBNvxK0=
|
||||||
|
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
|
||||||
|
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
|
||||||
|
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
|
||||||
|
google.golang.org/protobuf v1.28.1 h1:d0NfwRgPtno5B1Wa6L2DAG+KivqkdutMf1UhdNx175w=
|
||||||
|
google.golang.org/protobuf v1.28.1/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
@ -582,6 +582,11 @@ func (g *Group) CacheStats(which CacheType) CacheStats {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NowFunc returns the current time which is used by the LRU to
|
||||||
|
// determine if the value has expired. This can be overridden by
|
||||||
|
// tests to ensure items are evicted when expired.
|
||||||
|
var NowFunc lru.NowFunc = time.Now
|
||||||
|
|
||||||
// cache is a wrapper around an *lru.Cache that adds synchronization,
|
// cache is a wrapper around an *lru.Cache that adds synchronization,
|
||||||
// makes values always be ByteView, and counts the size of all keys and
|
// makes values always be ByteView, and counts the size of all keys and
|
||||||
// values.
|
// values.
|
||||||
@ -610,6 +615,7 @@ func (c *cache) add(key string, value ByteView) {
|
|||||||
defer c.mu.Unlock()
|
defer c.mu.Unlock()
|
||||||
if c.lru == nil {
|
if c.lru == nil {
|
||||||
c.lru = &lru.Cache{
|
c.lru = &lru.Cache{
|
||||||
|
Now: NowFunc,
|
||||||
OnEvicted: func(key lru.Key, value interface{}) {
|
OnEvicted: func(key lru.Key, value interface{}) {
|
||||||
val := value.(ByteView)
|
val := value.(ByteView)
|
||||||
c.nbytes -= int64(len(key.(string))) + int64(val.Len())
|
c.nbytes -= int64(len(key.(string))) + int64(val.Len())
|
||||||
|
@ -30,9 +30,9 @@ import (
|
|||||||
"unsafe"
|
"unsafe"
|
||||||
|
|
||||||
"github.com/golang/protobuf/proto"
|
"github.com/golang/protobuf/proto"
|
||||||
|
|
||||||
pb "github.com/mailgun/groupcache/v2/groupcachepb"
|
pb "github.com/mailgun/groupcache/v2/groupcachepb"
|
||||||
"github.com/mailgun/groupcache/v2/testpb"
|
"github.com/mailgun/groupcache/v2/testpb"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@ -535,3 +535,106 @@ func TestContextDeadlineOnPeer(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestEnsureSizeReportedCorrectly(t *testing.T) {
|
||||||
|
c := &cache{}
|
||||||
|
|
||||||
|
// Add the first value
|
||||||
|
bv1 := ByteView{s: "first", e: time.Now().Add(100 * time.Second)}
|
||||||
|
c.add("key1", bv1)
|
||||||
|
v, ok := c.get("key1")
|
||||||
|
|
||||||
|
// Should be len("key1" + "first") == 9
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, v.Equal(bv1))
|
||||||
|
assert.Equal(t, int64(9), c.bytes())
|
||||||
|
|
||||||
|
// Add a second value
|
||||||
|
bv2 := ByteView{s: "second", e: time.Now().Add(200 * time.Second)}
|
||||||
|
|
||||||
|
c.add("key2", bv2)
|
||||||
|
v, ok = c.get("key2")
|
||||||
|
|
||||||
|
// Should be len("key2" + "second") == (10 + 9) == 19
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, v.Equal(bv2))
|
||||||
|
assert.Equal(t, int64(19), c.bytes())
|
||||||
|
|
||||||
|
// Replace the first value with a shorter value
|
||||||
|
bv3 := ByteView{s: "3", e: time.Now().Add(200 * time.Second)}
|
||||||
|
|
||||||
|
c.add("key1", bv3)
|
||||||
|
v, ok = c.get("key1")
|
||||||
|
|
||||||
|
// len("key1" + "3") == 5
|
||||||
|
// len("key2" + "second") == 10
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, v.Equal(bv3))
|
||||||
|
assert.Equal(t, int64(15), c.bytes())
|
||||||
|
|
||||||
|
// Replace the second value with a longer value
|
||||||
|
bv4 := ByteView{s: "this-string-is-28-chars-long", e: time.Now().Add(200 * time.Second)}
|
||||||
|
|
||||||
|
c.add("key2", bv4)
|
||||||
|
v, ok = c.get("key2")
|
||||||
|
|
||||||
|
// len("key1" + "3") == 5
|
||||||
|
// len("key2" + "this-string-is-28-chars-long") == 32
|
||||||
|
assert.True(t, ok)
|
||||||
|
assert.True(t, v.Equal(bv4))
|
||||||
|
assert.Equal(t, int64(37), c.bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEnsureUpdateExpiredValue(t *testing.T) {
|
||||||
|
c := &cache{}
|
||||||
|
curTime := time.Now()
|
||||||
|
|
||||||
|
// Override the now function so we control time
|
||||||
|
NowFunc = func() time.Time {
|
||||||
|
return curTime
|
||||||
|
}
|
||||||
|
|
||||||
|
// Expires in 1 second
|
||||||
|
c.add("key1", ByteView{s: "value1", e: curTime.Add(time.Second)})
|
||||||
|
_, ok := c.get("key1")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// Advance 1.1 seconds into the future
|
||||||
|
curTime = curTime.Add(time.Millisecond * 1100)
|
||||||
|
|
||||||
|
// Value should have expired
|
||||||
|
_, ok = c.get("key1")
|
||||||
|
assert.False(t, ok)
|
||||||
|
|
||||||
|
// Add a new key that expires in 1 second
|
||||||
|
c.add("key2", ByteView{s: "value2", e: curTime.Add(time.Second)})
|
||||||
|
_, ok = c.get("key2")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// Advance 0.5 seconds into the future
|
||||||
|
curTime = curTime.Add(time.Millisecond * 500)
|
||||||
|
|
||||||
|
// Value should still exist
|
||||||
|
_, ok = c.get("key2")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// Replace the existing key, this should update the expired time
|
||||||
|
c.add("key2", ByteView{s: "updated value2", e: curTime.Add(time.Second)})
|
||||||
|
_, ok = c.get("key2")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// Advance 0.6 seconds into the future, which puts us past the initial
|
||||||
|
// expired time for key2.
|
||||||
|
curTime = curTime.Add(time.Millisecond * 600)
|
||||||
|
|
||||||
|
// Should still exist
|
||||||
|
_, ok = c.get("key2")
|
||||||
|
assert.True(t, ok)
|
||||||
|
|
||||||
|
// Advance 1.1 seconds into the future
|
||||||
|
curTime = curTime.Add(time.Millisecond * 1100)
|
||||||
|
|
||||||
|
// Should not exist
|
||||||
|
_, ok = c.get("key2")
|
||||||
|
assert.False(t, ok)
|
||||||
|
}
|
||||||
|
6
http.go
6
http.go
@ -271,8 +271,8 @@ func (h *httpGetter) makeRequest(ctx context.Context, m string, in request, b io
|
|||||||
u := fmt.Sprintf(
|
u := fmt.Sprintf(
|
||||||
"%v%v/%v",
|
"%v%v/%v",
|
||||||
h.baseURL,
|
h.baseURL,
|
||||||
url.QueryEscape(in.GetGroup()),
|
url.PathEscape(in.GetGroup()),
|
||||||
url.QueryEscape(in.GetKey()),
|
url.PathEscape(in.GetKey()),
|
||||||
)
|
)
|
||||||
req, err := http.NewRequestWithContext(ctx, m, u, b)
|
req, err := http.NewRequestWithContext(ctx, m, u, b)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -300,7 +300,7 @@ func (h *httpGetter) Get(ctx context.Context, in *pb.GetRequest, out *pb.GetResp
|
|||||||
defer res.Body.Close()
|
defer res.Body.Close()
|
||||||
if res.StatusCode != http.StatusOK {
|
if res.StatusCode != http.StatusOK {
|
||||||
msg, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1024*1024)) // Limit reading the error body to max 1 MiB
|
msg, _ := ioutil.ReadAll(io.LimitReader(res.Body, 1024*1024)) // Limit reading the error body to max 1 MiB
|
||||||
return fmt.Errorf("server returned: %v, %v", res.Status, msg)
|
return fmt.Errorf("server returned: %v, %v", res.Status, string(msg))
|
||||||
}
|
}
|
||||||
b := bufferPool.Get().(*bytes.Buffer)
|
b := bufferPool.Get().(*bytes.Buffer)
|
||||||
b.Reset()
|
b.Reset()
|
||||||
|
@ -174,6 +174,15 @@ func TestHTTPPool(t *testing.T) {
|
|||||||
if !bytes.Equal(setValue, getValue.ByteSlice()) {
|
if !bytes.Equal(setValue, getValue.ByteSlice()) {
|
||||||
t.Fatal(errors.New(fmt.Sprintf("incorrect value retrieved after set: %s", getValue)))
|
t.Fatal(errors.New(fmt.Sprintf("incorrect value retrieved after set: %s", getValue)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Key with non-URL characters to test URL encoding roundtrip
|
||||||
|
key = "a b/c,d"
|
||||||
|
if err := g.Get(ctx, key, StringSink(&value)); err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
if suffix := ":" + key; !strings.HasSuffix(value, suffix) {
|
||||||
|
t.Errorf("Get(%q) = %q, want value ending in %q", key, value, suffix)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testKeys(n int) (keys []string) {
|
func testKeys(n int) (keys []string) {
|
||||||
|
17
lru/lru.go
17
lru/lru.go
@ -22,6 +22,8 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type NowFunc func() time.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 {
|
||||||
// MaxEntries is the maximum number of cache entries before
|
// MaxEntries is the maximum number of cache entries before
|
||||||
@ -32,6 +34,11 @@ type Cache struct {
|
|||||||
// executed when an entry is purged from the cache.
|
// executed when an entry is purged from the cache.
|
||||||
OnEvicted func(key Key, value interface{})
|
OnEvicted func(key Key, value interface{})
|
||||||
|
|
||||||
|
// Now is the Now() function the cache will use to determine
|
||||||
|
// the current time which is used to calculate expired values
|
||||||
|
// Defaults to time.Now()
|
||||||
|
Now NowFunc
|
||||||
|
|
||||||
ll *list.List
|
ll *list.List
|
||||||
cache map[interface{}]*list.Element
|
cache map[interface{}]*list.Element
|
||||||
}
|
}
|
||||||
@ -53,6 +60,7 @@ func New(maxEntries int) *Cache {
|
|||||||
MaxEntries: maxEntries,
|
MaxEntries: maxEntries,
|
||||||
ll: list.New(),
|
ll: list.New(),
|
||||||
cache: make(map[interface{}]*list.Element),
|
cache: make(map[interface{}]*list.Element),
|
||||||
|
Now: time.Now,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,8 +71,13 @@ func (c *Cache) Add(key Key, value interface{}, expire time.Time) {
|
|||||||
c.ll = list.New()
|
c.ll = list.New()
|
||||||
}
|
}
|
||||||
if ee, ok := c.cache[key]; ok {
|
if ee, ok := c.cache[key]; ok {
|
||||||
|
eee := ee.Value.(*entry)
|
||||||
|
if c.OnEvicted != nil {
|
||||||
|
c.OnEvicted(key, eee.value)
|
||||||
|
}
|
||||||
c.ll.MoveToFront(ee)
|
c.ll.MoveToFront(ee)
|
||||||
ee.Value.(*entry).value = value
|
eee.expire = expire
|
||||||
|
eee.value = value
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
ele := c.ll.PushFront(&entry{key, value, expire})
|
ele := c.ll.PushFront(&entry{key, value, expire})
|
||||||
@ -82,7 +95,7 @@ func (c *Cache) Get(key Key) (value interface{}, ok bool) {
|
|||||||
if ele, hit := c.cache[key]; hit {
|
if ele, hit := c.cache[key]; hit {
|
||||||
entry := ele.Value.(*entry)
|
entry := ele.Value.(*entry)
|
||||||
// If the entry has expired, remove it from the cache
|
// If the entry has expired, remove it from the cache
|
||||||
if !entry.expire.IsZero() && entry.expire.Before(time.Now()) {
|
if !entry.expire.IsZero() && entry.expire.Before(c.Now()) {
|
||||||
c.removeElement(ele)
|
c.removeElement(ele)
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
|
@ -46,6 +46,32 @@ var getTests = []struct {
|
|||||||
complexStruct{1, simpleStruct{2, "three"}}, true},
|
complexStruct{1, simpleStruct{2, "three"}}, true},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestAdd_evictsOldAndReplaces(t *testing.T) {
|
||||||
|
var evictedKey Key
|
||||||
|
var evictedValue interface{}
|
||||||
|
lru := New(0)
|
||||||
|
lru.OnEvicted = func(key Key, value interface{}) {
|
||||||
|
evictedKey = key
|
||||||
|
evictedValue = value
|
||||||
|
}
|
||||||
|
lru.Add("myKey", 1234, time.Time{})
|
||||||
|
lru.Add("myKey", 1235, time.Time{})
|
||||||
|
|
||||||
|
newVal, ok := lru.Get("myKey")
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("%s: cache hit = %v; want %v", t.Name(), ok, !ok)
|
||||||
|
}
|
||||||
|
if newVal != 1235 {
|
||||||
|
t.Fatalf("%s: cache hit = %v; want %v", t.Name(), newVal, 1235)
|
||||||
|
}
|
||||||
|
if evictedKey != "myKey" {
|
||||||
|
t.Fatalf("%s: evictedKey = %v; want %v", t.Name(), evictedKey, "myKey")
|
||||||
|
}
|
||||||
|
if evictedValue != 1234 {
|
||||||
|
t.Fatalf("%s: evictedValue = %v; want %v", t.Name(), evictedValue, 1234)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGet(t *testing.T) {
|
func TestGet(t *testing.T) {
|
||||||
for _, tt := range getTests {
|
for _, tt := range getTests {
|
||||||
lru := New(0)
|
lru := New(0)
|
||||||
|
Loading…
Reference in New Issue
Block a user