diff --git a/go/service/identify.go b/go/service/identify.go index 58f885e26ff5..d2add663dfa8 100644 --- a/go/service/identify.go +++ b/go/service/identify.go @@ -4,8 +4,10 @@ package service import ( + "fmt" "time" + "github.com/golang/groupcache/singleflight" "golang.org/x/net/context" "stathat.com/c/ramcache" @@ -29,6 +31,7 @@ type IdentifyHandler struct { *BaseHandler libkb.Contextified resultCache *ramcache.Ramcache + callGroup singleflight.Group } func NewIdentifyHandler(xp rpc.Transporter, g *libkb.GlobalContext) *IdentifyHandler { @@ -44,34 +47,47 @@ func NewIdentifyHandler(xp rpc.Transporter, g *libkb.GlobalContext) *IdentifyHan } func (h *IdentifyHandler) Identify(_ context.Context, arg keybase1.IdentifyArg) (keybase1.IdentifyRes, error) { - if arg.Source == keybase1.IdentifySource_KBFS { - h.G().Log.Debug("KBFS Identify: checking result cache for %q", arg.UserAssertion) - x, err := h.resultCache.Get(arg.UserAssertion) - if err == nil { - exp, ok := x.(*keybase1.IdentifyRes) - if ok { - h.G().Log.Debug("KBFS Identify: found cached result for %q", arg.UserAssertion) - return *exp, nil + var do = func() (interface{}, error) { + if arg.Source == keybase1.IdentifySource_KBFS { + h.G().Log.Debug("KBFS Identify: checking result cache for %q", arg.UserAssertion) + x, err := h.resultCache.Get(arg.UserAssertion) + if err == nil { + exp, ok := x.(*keybase1.IdentifyRes) + if ok { + h.G().Log.Debug("KBFS Identify: found cached result for %q", arg.UserAssertion) + return *exp, nil + } } + h.G().Log.Debug("KBFS Identify: no cached result for %q", arg.UserAssertion) } - h.G().Log.Debug("KBFS Identify: no cached result for %q", arg.UserAssertion) + + res, err := h.identify(arg.SessionID, arg) + if err != nil { + return keybase1.IdentifyRes{}, err + } + exp := res.Export() + + if len(arg.UserAssertion) > 0 { + if err := h.resultCache.Set(arg.UserAssertion, exp); err != nil { + h.G().Log.Debug("Identify: result cache set error: %s", err) + } else { + h.G().Log.Debug("Identify: storing result for %q in result cache", arg.UserAssertion) + } + } + + return *exp, nil } - res, err := h.identify(arg.SessionID, arg) + v, err := h.callGroup.Do(arg.UserAssertion, do) if err != nil { return keybase1.IdentifyRes{}, err } - - exp := res.Export() - if len(arg.UserAssertion) > 0 { - if err := h.resultCache.Set(arg.UserAssertion, exp); err != nil { - h.G().Log.Debug("Identify: result cache set error: %s", err) - } else { - h.G().Log.Debug("Identify: storing result for %q in result cache", arg.UserAssertion) - } + res, ok := v.(keybase1.IdentifyRes) + if !ok { + return keybase1.IdentifyRes{}, fmt.Errorf("invalid type returned by do: %T", v) } - return *exp, nil + return res, nil } func (h *IdentifyHandler) makeContext(sessionID int, arg keybase1.IdentifyArg) (ret *engine.Context, err error) { diff --git a/go/vendor/github.com/golang/groupcache/singleflight/singleflight.go b/go/vendor/github.com/golang/groupcache/singleflight/singleflight.go new file mode 100644 index 000000000000..ff2c2ee4f3d6 --- /dev/null +++ b/go/vendor/github.com/golang/groupcache/singleflight/singleflight.go @@ -0,0 +1,64 @@ +/* +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. +*/ + +// Package singleflight provides a duplicate function call suppression +// mechanism. +package singleflight + +import "sync" + +// call is an in-flight or completed Do call +type call struct { + wg sync.WaitGroup + val interface{} + err error +} + +// Group represents a class of work and forms a namespace in which +// units of work can be executed with duplicate suppression. +type Group struct { + mu sync.Mutex // protects m + m map[string]*call // lazily initialized +} + +// Do executes and returns the results of the given function, making +// sure that only one execution is in-flight for a given key at a +// time. If a duplicate comes in, the duplicate caller waits for the +// original to complete and receives the same results. +func (g *Group) Do(key string, fn func() (interface{}, error)) (interface{}, error) { + g.mu.Lock() + if g.m == nil { + g.m = make(map[string]*call) + } + if c, ok := g.m[key]; ok { + g.mu.Unlock() + c.wg.Wait() + return c.val, c.err + } + c := new(call) + c.wg.Add(1) + g.m[key] = c + g.mu.Unlock() + + c.val, c.err = fn() + c.wg.Done() + + g.mu.Lock() + delete(g.m, key) + g.mu.Unlock() + + return c.val, c.err +} diff --git a/go/vendor/vendor.json b/go/vendor/vendor.json index e8f28a8960c3..a0fd16cd1995 100644 --- a/go/vendor/vendor.json +++ b/go/vendor/vendor.json @@ -72,6 +72,11 @@ "revision": "c20a8bde38c8f5ba06f6600edf473705c96829d1", "revisionTime": "2015-08-24T01:38:10-07:00" }, + { + "path": "github.com/golang/groupcache/singleflight", + "revision": "604ed5785183e59ae2789449d89e73f3a2a77987", + "revisionTime": "2015-01-25T10:08:32-08:00" + }, { "path": "github.com/google/go-snappy/snappy", "revision": "eaa750b9bf4dcb7cb20454be850613b66cda3273",