Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 19 additions & 6 deletions groupcache.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,31 +210,44 @@ func (g *Group) initPeers() {
}

func (g *Group) Get(ctx context.Context, key string, dest Sink) error {
_, err := g.getCached(ctx, key, dest)
return err
}

// GetCached acts like Get but additionally reports whether the
// value was served from the local cache. This is useful for
// callers that want to distinguish cache hits from cache misses
// without relying on the Stats counters.
func (g *Group) GetCached(ctx context.Context, key string, dest Sink) (cached bool, err error) {
return g.getCached(ctx, key, dest)
}

func (g *Group) getCached(ctx context.Context, key string, dest Sink) (cached bool, err error) {
g.peersOnce.Do(g.initPeers)
g.Stats.Gets.Add(1)
if dest == nil {
return errors.New("groupcache: nil dest Sink")
return false, errors.New("groupcache: nil dest Sink")
}
value, cacheHit := g.lookupCache(key)

if cacheHit {
g.Stats.CacheHits.Add(1)
return setSinkView(dest, value)
return true, setSinkView(dest, value)
}

// Optimization to avoid double unmarshalling or copying: keep
// track of whether the dest was already populated. One caller
// (if local) will set this; the losers will not. The common
// case will likely be one caller.
destPopulated := false
value, destPopulated, err := g.load(ctx, key, dest)
value, destPopulated, err = g.load(ctx, key, dest)
if err != nil {
return err
return false, err
}
if destPopulated {
return nil
return false, nil
}
return setSinkView(dest, value)
return false, setSinkView(dest, value)
}

// load loads key either by invoking the getter locally or by sending it to another machine.
Expand Down
42 changes: 42 additions & 0 deletions groupcache_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -453,5 +453,47 @@ func TestGroupStatsAlignment(t *testing.T) {
}
}

// TestGetCached tests that GetCached correctly reports cache hit/miss.
func TestGetCached(t *testing.T) {
once.Do(testSetup)
g := newGroup("TestGetCached-group", 1<<20, GetterFunc(func(_ context.Context, key string, dest Sink) error {
return dest.SetString("val:" + key)
}), nil)

key := "cached-key"
var s string

// First call: cache miss.
cached, err := g.GetCached(dummyCtx, key, StringSink(&s))
if err != nil {
t.Fatal(err)
}
if cached {
t.Error("first GetCached returned cached=true; want false")
}
if s != "val:cached-key" {
t.Errorf("got %q; want %q", s, "val:cached-key")
}

// Second call: should be a cache hit.
s = ""
cached, err = g.GetCached(dummyCtx, key, StringSink(&s))
if err != nil {
t.Fatal(err)
}
if !cached {
t.Error("second GetCached returned cached=false; want true")
}
if s != "val:cached-key" {
t.Errorf("got %q; want %q", s, "val:cached-key")
}

// Nil dest should return error.
_, err = g.GetCached(dummyCtx, "any", nil)
if err == nil {
t.Error("GetCached with nil dest should return error")
}
}

// TODO(bradfitz): port the Google-internal full integration test into here,
// using HTTP requests instead of our RPC system.