diff --git a/rpc/rpcutil.go b/rpc/rpcutil.go index ede10f061..3588ca5a5 100644 --- a/rpc/rpcutil.go +++ b/rpc/rpcutil.go @@ -182,7 +182,9 @@ func recordRPCCost(startTime time.Time, method string, err error) { // Optimistically, val will not be nil except the first Call of method // expvar uses sync.Map // So, we try it first without lock - if val = expvar.Get(name); val == nil { + val = expvar.Get(name) + valC = expvar.Get(nameC) + if val == nil || valC == nil { callRPCExpvarLock.Lock() val = expvar.Get(name) if val == nil { @@ -191,9 +193,9 @@ func recordRPCCost(startTime time.Time, method string, err error) { } callRPCExpvarLock.Unlock() val = expvar.Get(name) + valC = expvar.Get(nameC) } val.(mw.Metric).Add(costTime.Seconds()) - valC = expvar.Get(nameC) valC.(mw.Metric).Add(1) return } diff --git a/rpc/rpcutil_test.go b/rpc/rpcutil_test.go index 90a432095..ebde85893 100644 --- a/rpc/rpcutil_test.go +++ b/rpc/rpcutil_test.go @@ -18,6 +18,7 @@ package rpc import ( "context" + "fmt" "os" "path/filepath" "runtime" @@ -443,3 +444,28 @@ func BenchmarkPersistentCaller_Call(b *testing.B) { server.Stop() } + +func TestRecordRPCCost(t *testing.T) { + Convey("Bug: bad critical section for multiple values", t, func(c C) { + var ( + start = time.Now() + rounds = 1000 + concurrent = 10 + wg = &sync.WaitGroup{} + body = func(i int) { + defer func() { + c.So(recover(), ShouldBeNil) + wg.Done() + }() + recordRPCCost(start, fmt.Sprintf("M%d", i), nil) + } + ) + defer wg.Wait() + for i := 0; i < rounds; i++ { + for j := 0; j < concurrent; j++ { + wg.Add(1) + go body(i) + } + } + }) +}