From bd7f82c7b1295ecec7cbec0d4be6c7d1ac470941 Mon Sep 17 00:00:00 2001 From: dfawley Date: Tue, 29 May 2018 09:06:35 -0700 Subject: [PATCH] internal/grpcrand: New package for concurrency-safe randoms (#2106) --- backoff.go | 5 ++-- internal/grpcrand/grpcrand.go | 56 +++++++++++++++++++++++++++++++++++ resolver/dns/dns_resolver.go | 5 ++-- transport/http2_server.go | 6 ++-- vet.sh | 3 +- 5 files changed, 65 insertions(+), 10 deletions(-) create mode 100644 internal/grpcrand/grpcrand.go diff --git a/backoff.go b/backoff.go index c40facce5..2fb322150 100644 --- a/backoff.go +++ b/backoff.go @@ -19,8 +19,9 @@ package grpc import ( - "math/rand" "time" + + "google.golang.org/grpc/internal/grpcrand" ) // DefaultBackoffConfig uses values specified for backoff in @@ -88,7 +89,7 @@ func (bc BackoffConfig) backoff(retries int) time.Duration { } // Randomize backoff delays so that if a cluster of requests start at // the same time, they won't operate in lockstep. - backoff *= 1 + bc.jitter*(rand.Float64()*2-1) + backoff *= 1 + bc.jitter*(grpcrand.Float64()*2-1) if backoff < 0 { return 0 } diff --git a/internal/grpcrand/grpcrand.go b/internal/grpcrand/grpcrand.go new file mode 100644 index 000000000..200b115ca --- /dev/null +++ b/internal/grpcrand/grpcrand.go @@ -0,0 +1,56 @@ +/* + * + * Copyright 2018 gRPC authors. + * + * 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 grpcrand implements math/rand functions in a concurrent-safe way +// with a global random source, independent of math/rand's global source. +package grpcrand + +import ( + "math/rand" + "sync" + "time" +) + +var ( + r = rand.New(rand.NewSource(time.Now().UnixNano())) + mu sync.Mutex +) + +// Int63n implements rand.Int63n on the grpcrand global source. +func Int63n(n int64) int64 { + mu.Lock() + res := r.Int63n(n) + mu.Unlock() + return res +} + +// Intn implements rand.Intn on the grpcrand global source. +func Intn(n int) int { + mu.Lock() + res := r.Intn(n) + mu.Unlock() + return res +} + +// Float64 implements rand.Float64 on the grpcrand global source. +func Float64() float64 { + mu.Lock() + res := r.Float64() + mu.Unlock() + return res +} diff --git a/resolver/dns/dns_resolver.go b/resolver/dns/dns_resolver.go index c1cabfc99..4729ae575 100644 --- a/resolver/dns/dns_resolver.go +++ b/resolver/dns/dns_resolver.go @@ -24,7 +24,6 @@ import ( "encoding/json" "errors" "fmt" - "math/rand" "net" "os" "strconv" @@ -34,6 +33,7 @@ import ( "golang.org/x/net/context" "google.golang.org/grpc/grpclog" + "google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/resolver" ) @@ -52,7 +52,6 @@ const ( var ( errMissingAddr = errors.New("missing address") - randomGen = rand.New(rand.NewSource(time.Now().UnixNano())) ) // NewBuilder creates a dnsBuilder which is used to factory DNS resolvers. @@ -346,7 +345,7 @@ func chosenByPercentage(a *int) bool { if a == nil { return true } - return randomGen.Intn(100)+1 <= *a + return grpcrand.Intn(100)+1 <= *a } func canaryingSC(js string) string { diff --git a/transport/http2_server.go b/transport/http2_server.go index af0095745..8d1fe06bc 100644 --- a/transport/http2_server.go +++ b/transport/http2_server.go @@ -24,7 +24,6 @@ import ( "fmt" "io" "math" - "math/rand" "net" "strconv" "sync" @@ -39,6 +38,7 @@ import ( "google.golang.org/grpc/channelz" "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/internal/grpcrand" "google.golang.org/grpc/keepalive" "google.golang.org/grpc/metadata" "google.golang.org/grpc/peer" @@ -1125,14 +1125,12 @@ func (t *http2Server) getOutFlowWindow() int64 { } } -var rgen = rand.New(rand.NewSource(time.Now().UnixNano())) - func getJitter(v time.Duration) time.Duration { if v == infinity { return 0 } // Generate a jitter between +/- 10% of the value. r := int64(v / 10) - j := rgen.Int63n(2*r) - r + j := grpcrand.Int63n(2*r) - r return time.Duration(j) } diff --git a/vet.sh b/vet.sh index 6126ab64b..079bc2896 100755 --- a/vet.sh +++ b/vet.sh @@ -54,7 +54,8 @@ if git status --porcelain | read; then fi git ls-files "*.go" | xargs grep -L "\(Copyright [0-9]\{4,\} gRPC authors\)\|DO NOT EDIT" 2>&1 | tee /dev/stderr | (! read) -git ls-files "*.go" | xargs grep -l "\"unsafe\"" 2>&1 | (! grep -v '_test.go') | tee /dev/stderr | (! read) +git ls-files "*.go" | xargs grep -l '"unsafe"' 2>&1 | (! grep -v '_test.go') | tee /dev/stderr | (! read) +git ls-files "*.go" | xargs grep -l '"math/rand"' 2>&1 | (! grep -v '^examples\|^stress\|grpcrand') | tee /dev/stderr | (! read) gofmt -s -d -l . 2>&1 | tee /dev/stderr | (! read) goimports -l . 2>&1 | tee /dev/stderr | (! read) golint ./... 2>&1 | (grep -vE "(_mock|\.pb)\.go:" || true) | tee /dev/stderr | (! read)