Repository: grpc/grpc-go

Issue:
Upgrade to using math/rand/v2 to get perf enhancements 
Follow up from: https://github.com/grpc/grpc-go/pull/7283#issuecomment-2142662503

Go1.22 introduced a new rand package with a lot of improvements. See: [go.dev/blog/randv2](https://go.dev/blog/randv2). 

We would love to update our usages of math/rand once gRPC-go requires Go 1.22 or later.

Discussion:
@silaselisha: @arvindbr8 I would like to be assigned this task.@purnesh42H: @silaselisha assigned it to you@easwars: This is blocked until we bump the minimum Go version supported to `1.22` in our go.mod.@marcoferrer: Is switching to randv2 still blocked?@easwars: I see that we bumped up our minimum supported Go version to `1.22.7` in https://github.com/grpc/grpc-go/pull/7624. So, this should be unblocked.

@marcoferrer : Would you be interested in making this change? We'll be happy to review. Thanks.@marcoferrer: @easwars Sounds good. PR opened. 

Solution patch:
diff --git a/balancer/endpointsharding/endpointsharding.go b/balancer/endpointsharding/endpointsharding.go
index 00189b1d..df5b5abe 100644
--- a/balancer/endpointsharding/endpointsharding.go
+++ b/balancer/endpointsharding/endpointsharding.go
@@ -29,6 +29,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"math/rand"
 	"sync"
 	"sync/atomic"
 
@@ -36,7 +37,6 @@ import (
 	"google.golang.org/grpc/balancer/base"
 	"google.golang.org/grpc/connectivity"
 	"google.golang.org/grpc/internal/balancer/gracefulswitch"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
 )
@@ -234,7 +234,7 @@ func (es *endpointSharding) updateState() {
 	p := &pickerWithChildStates{
 		pickers:     pickers,
 		childStates: childStates,
-		next:        uint32(grpcrand.Intn(len(pickers))),
+		next:        uint32(rand.Intn(len(pickers))),
 	}
 	es.cc.UpdateState(balancer.State{
 		ConnectivityState: aggState,
diff --git a/balancer/grpclb/grpclb_picker.go b/balancer/grpclb/grpclb_picker.go
index 20c5f2ec..671bc663 100644
--- a/balancer/grpclb/grpclb_picker.go
+++ b/balancer/grpclb/grpclb_picker.go
@@ -19,13 +19,13 @@
 package grpclb
 
 import (
+	"math/rand"
 	"sync"
 	"sync/atomic"
 
 	"google.golang.org/grpc/balancer"
 	lbpb "google.golang.org/grpc/balancer/grpclb/grpc_lb_v1"
 	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/status"
 )
 
@@ -112,7 +112,7 @@ type rrPicker struct {
 func newRRPicker(readySCs []balancer.SubConn) *rrPicker {
 	return &rrPicker{
 		subConns:     readySCs,
-		subConnsNext: grpcrand.Intn(len(readySCs)),
+		subConnsNext: rand.Intn(len(readySCs)),
 	}
 }
 
@@ -147,7 +147,7 @@ func newLBPicker(serverList []*lbpb.Server, readySCs []balancer.SubConn, stats *
 	return &lbPicker{
 		serverList:   serverList,
 		subConns:     readySCs,
-		subConnsNext: grpcrand.Intn(len(readySCs)),
+		subConnsNext: rand.Intn(len(readySCs)),
 		stats:        stats,
 	}
 }
diff --git a/balancer/leastrequest/leastrequest.go b/balancer/leastrequest/leastrequest.go
index 3289f286..c248a3a8 100644
--- a/balancer/leastrequest/leastrequest.go
+++ b/balancer/leastrequest/leastrequest.go
@@ -22,17 +22,17 @@ package leastrequest
 import (
 	"encoding/json"
 	"fmt"
+	"math/rand"
 	"sync/atomic"
 
 	"google.golang.org/grpc/balancer"
 	"google.golang.org/grpc/balancer/base"
 	"google.golang.org/grpc/grpclog"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/serviceconfig"
 )
 
-// grpcranduint32 is a global to stub out in tests.
-var grpcranduint32 = grpcrand.Uint32
+// randuint32 is a global to stub out in tests.
+var randuint32 = rand.Uint32
 
 // Name is the name of the least request balancer.
 const Name = "least_request_experimental"
@@ -157,7 +157,7 @@ func (p *picker) Pick(balancer.PickInfo) (balancer.PickResult, error) {
 	var pickedSC *scWithRPCCount
 	var pickedSCNumRPCs int32
 	for i := 0; i < int(p.choiceCount); i++ {
-		index := grpcranduint32() % uint32(len(p.subConns))
+		index := randuint32() % uint32(len(p.subConns))
 		sc := p.subConns[index]
 		n := sc.numRPCs.Load()
 		if pickedSC == nil || n < pickedSCNumRPCs {
diff --git a/balancer/pickfirst/pickfirst.go b/balancer/pickfirst/pickfirst.go
index 853f3885..07527603 100644
--- a/balancer/pickfirst/pickfirst.go
+++ b/balancer/pickfirst/pickfirst.go
@@ -23,12 +23,13 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"math/rand"
 
 	"google.golang.org/grpc/balancer"
 	"google.golang.org/grpc/connectivity"
 	"google.golang.org/grpc/grpclog"
+	"google.golang.org/grpc/internal"
 	internalgrpclog "google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/pretty"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
@@ -36,6 +37,7 @@ import (
 
 func init() {
 	balancer.Register(pickfirstBuilder{})
+	internal.ShuffleAddressListForTesting = func(n int, swap func(i, j int)) { rand.Shuffle(n, swap) }
 }
 
 var logger = grpclog.Component("pick-first-lb")
@@ -101,6 +103,12 @@ func (b *pickfirstBalancer) ResolverError(err error) {
 	})
 }
 
+type Shuffler interface {
+	ShuffleAddressListForTesting(n int, swap func(i, j int))
+}
+
+func ShuffleAddressListForTesting(n int, swap func(i, j int)) { rand.Shuffle(n, swap) }
+
 func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState) error {
 	if len(state.ResolverState.Addresses) == 0 && len(state.ResolverState.Endpoints) == 0 {
 		// The resolver reported an empty address list. Treat it like an error by
@@ -132,7 +140,7 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState
 		// within each endpoint. - A61
 		if cfg.ShuffleAddressList {
 			endpoints = append([]resolver.Endpoint{}, endpoints...)
-			grpcrand.Shuffle(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] })
+			internal.ShuffleAddressListForTesting.(func(int, func(int, int)))(len(endpoints), func(i, j int) { endpoints[i], endpoints[j] = endpoints[j], endpoints[i] })
 		}
 
 		// "Flatten the list by concatenating the ordered list of addresses for each
@@ -153,7 +161,7 @@ func (b *pickfirstBalancer) UpdateClientConnState(state balancer.ClientConnState
 		addrs = state.ResolverState.Addresses
 		if cfg.ShuffleAddressList {
 			addrs = append([]resolver.Address{}, addrs...)
-			grpcrand.Shuffle(len(addrs), func(i, j int) { addrs[i], addrs[j] = addrs[j], addrs[i] })
+			rand.Shuffle(len(addrs), func(i, j int) { addrs[i], addrs[j] = addrs[j], addrs[i] })
 		}
 	}
 
diff --git a/balancer/rls/internal/adaptive/adaptive.go b/balancer/rls/internal/adaptive/adaptive.go
index a3b0931b..8b178604 100644
--- a/balancer/rls/internal/adaptive/adaptive.go
+++ b/balancer/rls/internal/adaptive/adaptive.go
@@ -20,16 +20,15 @@
 package adaptive
 
 import (
+	"math/rand"
 	"sync"
 	"time"
-
-	"google.golang.org/grpc/internal/grpcrand"
 )
 
 // For overriding in unittests.
 var (
 	timeNowFunc = func() time.Time { return time.Now() }
-	randFunc    = func() float64 { return grpcrand.Float64() }
+	randFunc    = func() float64 { return rand.Float64() }
 )
 
 const (
diff --git a/balancer/roundrobin/roundrobin.go b/balancer/roundrobin/roundrobin.go
index f7031ad2..260255d3 100644
--- a/balancer/roundrobin/roundrobin.go
+++ b/balancer/roundrobin/roundrobin.go
@@ -22,12 +22,12 @@
 package roundrobin
 
 import (
+	"math/rand"
 	"sync/atomic"
 
 	"google.golang.org/grpc/balancer"
 	"google.golang.org/grpc/balancer/base"
 	"google.golang.org/grpc/grpclog"
-	"google.golang.org/grpc/internal/grpcrand"
 )
 
 // Name is the name of round_robin balancer.
@@ -60,7 +60,7 @@ func (*rrPickerBuilder) Build(info base.PickerBuildInfo) balancer.Picker {
 		// Start at a random index, as the same RR balancer rebuilds a new
 		// picker when SubConn states change, and we don't want to apply excess
 		// load to the first server in the list.
-		next: uint32(grpcrand.Intn(len(scs))),
+		next: uint32(rand.Intn(len(scs))),
 	}
 }
 
diff --git a/balancer/weightedroundrobin/balancer.go b/balancer/weightedroundrobin/balancer.go
index 7e751722..36606e79 100644
--- a/balancer/weightedroundrobin/balancer.go
+++ b/balancer/weightedroundrobin/balancer.go
@@ -23,6 +23,7 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	"math/rand"
 	"sync"
 	"sync/atomic"
 	"time"
@@ -33,7 +34,6 @@ import (
 	"google.golang.org/grpc/balancer/weightedroundrobin/internal"
 	"google.golang.org/grpc/connectivity"
 	"google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/internal/grpcrand"
 	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/orca"
 	"google.golang.org/grpc/resolver"
@@ -318,7 +318,7 @@ func (b *wrrBalancer) regeneratePicker() {
 	}
 
 	p := &picker{
-		v:        grpcrand.Uint32(), // start the scheduler at a random point
+		v:        rand.Uint32(), // start the scheduler at a random point
 		cfg:      b.cfg,
 		subConns: b.readySubConns(),
 	}
diff --git a/benchmark/worker/benchmark_client.go b/benchmark/worker/benchmark_client.go
index bd8a6e9e..d3d04cd0 100644
--- a/benchmark/worker/benchmark_client.go
+++ b/benchmark/worker/benchmark_client.go
@@ -22,6 +22,7 @@ import (
 	"context"
 	"flag"
 	"math"
+	"math/rand"
 	"runtime"
 	"sync"
 	"time"
@@ -32,7 +33,6 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/syscall"
 	"google.golang.org/grpc/status"
 	"google.golang.org/grpc/testdata"
@@ -298,7 +298,7 @@ func (bc *benchmarkClient) unaryLoop(conns []*grpc.ClientConn, rpcCountPerConn i
 						}
 					}
 				} else { // Open loop.
-					timeBetweenRPCs := time.Duration((grpcrand.ExpFloat64() / *poissonLambda) * float64(time.Second))
+					timeBetweenRPCs := time.Duration((rand.ExpFloat64() / *poissonLambda) * float64(time.Second))
 					time.AfterFunc(timeBetweenRPCs, func() {
 						bc.poissonUnary(client, idx, reqSize, respSize, *poissonLambda)
 					})
@@ -348,7 +348,7 @@ func (bc *benchmarkClient) streamingLoop(conns []*grpc.ClientConn, rpcCountPerCo
 					}
 				}(idx)
 			} else { // Open loop.
-				timeBetweenRPCs := time.Duration((grpcrand.ExpFloat64() / *poissonLambda) * float64(time.Second))
+				timeBetweenRPCs := time.Duration((rand.ExpFloat64() / *poissonLambda) * float64(time.Second))
 				time.AfterFunc(timeBetweenRPCs, func() {
 					bc.poissonStreaming(stream, idx, reqSize, respSize, *poissonLambda, doRPC)
 				})
@@ -366,7 +366,7 @@ func (bc *benchmarkClient) poissonUnary(client testgrpc.BenchmarkServiceClient,
 		elapse := time.Since(start)
 		bc.lockingHistograms[idx].add(int64(elapse))
 	}()
-	timeBetweenRPCs := time.Duration((grpcrand.ExpFloat64() / lambda) * float64(time.Second))
+	timeBetweenRPCs := time.Duration((rand.ExpFloat64() / lambda) * float64(time.Second))
 	time.AfterFunc(timeBetweenRPCs, func() {
 		bc.poissonUnary(client, idx, reqSize, respSize, lambda)
 	})
@@ -381,7 +381,7 @@ func (bc *benchmarkClient) poissonStreaming(stream testgrpc.BenchmarkService_Str
 		elapse := time.Since(start)
 		bc.lockingHistograms[idx].add(int64(elapse))
 	}()
-	timeBetweenRPCs := time.Duration((grpcrand.ExpFloat64() / lambda) * float64(time.Second))
+	timeBetweenRPCs := time.Duration((rand.ExpFloat64() / lambda) * float64(time.Second))
 	time.AfterFunc(timeBetweenRPCs, func() {
 		bc.poissonStreaming(stream, idx, reqSize, respSize, lambda, doRPC)
 	})
diff --git a/examples/features/debugging/server/main.go b/examples/features/debugging/server/main.go
index 397cb0c7..e8ccab16 100644
--- a/examples/features/debugging/server/main.go
+++ b/examples/features/debugging/server/main.go
@@ -22,12 +22,12 @@ package main
 import (
 	"context"
 	"log"
+	"math/rand"
 	"net"
 	"time"
 
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/channelz/service"
-	"google.golang.org/grpc/internal/grpcrand"
 
 	pb "google.golang.org/grpc/examples/helloworld/helloworld"
 )
@@ -54,7 +54,7 @@ type slowServer struct {
 // SayHello implements helloworld.GreeterServer
 func (s *slowServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
 	// Delay 100ms ~ 200ms before replying
-	time.Sleep(time.Duration(100+grpcrand.Intn(100)) * time.Millisecond)
+	time.Sleep(time.Duration(100+rand.Intn(100)) * time.Millisecond)
 	return &pb.HelloReply{Message: "Hello " + in.Name}, nil
 }
 
diff --git a/internal/backoff/backoff.go b/internal/backoff/backoff.go
index fed1c011..b15cf482 100644
--- a/internal/backoff/backoff.go
+++ b/internal/backoff/backoff.go
@@ -25,10 +25,10 @@ package backoff
 import (
 	"context"
 	"errors"
+	"math/rand"
 	"time"
 
 	grpcbackoff "google.golang.org/grpc/backoff"
-	"google.golang.org/grpc/internal/grpcrand"
 )
 
 // Strategy defines the methodology for backing off after a grpc connection
@@ -67,7 +67,7 @@ func (bc Exponential) 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.Config.Jitter*(grpcrand.Float64()*2-1)
+	backoff *= 1 + bc.Config.Jitter*(rand.Float64()*2-1)
 	if backoff < 0 {
 		return 0
 	}
diff --git a/internal/grpcrand/grpcrand.go b/internal/grpcrand/grpcrand.go
deleted file mode 100644
index 0126d6b5..00000000
--- a/internal/grpcrand/grpcrand.go
+++ /dev/null
@@ -1,100 +0,0 @@
-//go:build !go1.21
-
-// TODO: when this file is deleted (after Go 1.20 support is dropped), delete
-// all of grpcrand and call the rand package directly.
-
-/*
- *
- * 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
-)
-
-// Int implements rand.Int on the grpcrand global source.
-func Int() int {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.Int()
-}
-
-// Int63n implements rand.Int63n on the grpcrand global source.
-func Int63n(n int64) int64 {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.Int63n(n)
-}
-
-// Intn implements rand.Intn on the grpcrand global source.
-func Intn(n int) int {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.Intn(n)
-}
-
-// Int31n implements rand.Int31n on the grpcrand global source.
-func Int31n(n int32) int32 {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.Int31n(n)
-}
-
-// Float64 implements rand.Float64 on the grpcrand global source.
-func Float64() float64 {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.Float64()
-}
-
-// Uint64 implements rand.Uint64 on the grpcrand global source.
-func Uint64() uint64 {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.Uint64()
-}
-
-// Uint32 implements rand.Uint32 on the grpcrand global source.
-func Uint32() uint32 {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.Uint32()
-}
-
-// ExpFloat64 implements rand.ExpFloat64 on the grpcrand global source.
-func ExpFloat64() float64 {
-	mu.Lock()
-	defer mu.Unlock()
-	return r.ExpFloat64()
-}
-
-// Shuffle implements rand.Shuffle on the grpcrand global source.
-var Shuffle = func(n int, f func(int, int)) {
-	mu.Lock()
-	defer mu.Unlock()
-	r.Shuffle(n, f)
-}
diff --git a/internal/grpcrand/grpcrand_go1.21.go b/internal/grpcrand/grpcrand_go1.21.go
deleted file mode 100644
index c37299af..00000000
--- a/internal/grpcrand/grpcrand_go1.21.go
+++ /dev/null
@@ -1,73 +0,0 @@
-//go:build go1.21
-
-/*
- *
- * Copyright 2024 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"
-
-// This implementation will be used for Go version 1.21 or newer.
-// For older versions, the original implementation with mutex will be used.
-
-// Int implements rand.Int on the grpcrand global source.
-func Int() int {
-	return rand.Int()
-}
-
-// Int63n implements rand.Int63n on the grpcrand global source.
-func Int63n(n int64) int64 {
-	return rand.Int63n(n)
-}
-
-// Intn implements rand.Intn on the grpcrand global source.
-func Intn(n int) int {
-	return rand.Intn(n)
-}
-
-// Int31n implements rand.Int31n on the grpcrand global source.
-func Int31n(n int32) int32 {
-	return rand.Int31n(n)
-}
-
-// Float64 implements rand.Float64 on the grpcrand global source.
-func Float64() float64 {
-	return rand.Float64()
-}
-
-// Uint64 implements rand.Uint64 on the grpcrand global source.
-func Uint64() uint64 {
-	return rand.Uint64()
-}
-
-// Uint32 implements rand.Uint32 on the grpcrand global source.
-func Uint32() uint32 {
-	return rand.Uint32()
-}
-
-// ExpFloat64 implements rand.ExpFloat64 on the grpcrand global source.
-func ExpFloat64() float64 {
-	return rand.ExpFloat64()
-}
-
-// Shuffle implements rand.Shuffle on the grpcrand global source.
-var Shuffle = func(n int, f func(int, int)) {
-	rand.Shuffle(n, f)
-}
diff --git a/internal/internal.go b/internal/internal.go
index 146e5feb..46843a3e 100644
--- a/internal/internal.go
+++ b/internal/internal.go
@@ -211,6 +211,10 @@ var (
 	// UserSetDefaultScheme is set to true if the user has overridden the
 	// default resolver scheme.
 	UserSetDefaultScheme bool = false
+
+	// ShuffleAddressListForTesting pseudo-randomizes the order of addresses.  n
+	// is the number of elements.  swap swaps the elements with indexes i and j.
+	ShuffleAddressListForTesting any // func(n int, swap func(i, j int))
 )
 
 // HealthChecker defines the signature of the client-side LB channel health
diff --git a/internal/resolver/dns/dns_resolver.go b/internal/resolver/dns/dns_resolver.go
index f3f52a59..50afcadc 100644
--- a/internal/resolver/dns/dns_resolver.go
+++ b/internal/resolver/dns/dns_resolver.go
@@ -24,6 +24,7 @@ import (
 	"context"
 	"encoding/json"
 	"fmt"
+	"math/rand"
 	"net"
 	"os"
 	"strconv"
@@ -35,7 +36,6 @@ import (
 	"google.golang.org/grpc/grpclog"
 	"google.golang.org/grpc/internal/backoff"
 	"google.golang.org/grpc/internal/envconfig"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/resolver/dns/internal"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/serviceconfig"
@@ -423,7 +423,7 @@ func chosenByPercentage(a *int) bool {
 	if a == nil {
 		return true
 	}
-	return grpcrand.Intn(100)+1 <= *a
+	return rand.Intn(100)+1 <= *a
 }
 
 func canaryingSC(js string) string {
diff --git a/internal/transport/http2_server.go b/internal/transport/http2_server.go
index cab0e2d3..b7091165 100644
--- a/internal/transport/http2_server.go
+++ b/internal/transport/http2_server.go
@@ -25,6 +25,7 @@ import (
 	"fmt"
 	"io"
 	"math"
+	"math/rand"
 	"net"
 	"net/http"
 	"strconv"
@@ -43,7 +44,6 @@ import (
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/credentials"
 	"google.golang.org/grpc/internal/channelz"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpcsync"
 	"google.golang.org/grpc/keepalive"
 	"google.golang.org/grpc/metadata"
@@ -1440,7 +1440,7 @@ func getJitter(v time.Duration) time.Duration {
 	}
 	// Generate a jitter between +/- 10% of the value.
 	r := int64(v / 10)
-	j := grpcrand.Int63n(2*r) - r
+	j := rand.Int63n(2*r) - r
 	return time.Duration(j)
 }
 
diff --git a/internal/wrr/random.go b/internal/wrr/random.go
index 4d91fc6f..3f611a35 100644
--- a/internal/wrr/random.go
+++ b/internal/wrr/random.go
@@ -19,9 +19,8 @@ package wrr
 
 import (
 	"fmt"
+	"math/rand"
 	"sort"
-
-	"google.golang.org/grpc/internal/grpcrand"
 )
 
 // weightedItem is a wrapped weighted item that is used to implement weighted random algorithm.
@@ -47,19 +46,19 @@ func NewRandom() WRR {
 	return &randomWRR{}
 }
 
-var grpcrandInt63n = grpcrand.Int63n
+var randInt63n = rand.Int63n
 
 func (rw *randomWRR) Next() (item any) {
 	if len(rw.items) == 0 {
 		return nil
 	}
 	if rw.equalWeights {
-		return rw.items[grpcrandInt63n(int64(len(rw.items)))].item
+		return rw.items[randInt63n(int64(len(rw.items)))].item
 	}
 
 	sumOfWeights := rw.items[len(rw.items)-1].accumulatedWeight
 	// Random number in [0, sumOfWeights).
-	randomWeight := grpcrandInt63n(sumOfWeights)
+	randomWeight := randInt63n(sumOfWeights)
 	// Item's accumulated weights are in ascending order, because item's weight >= 0.
 	// Binary search rw.items to find first item whose accumulatedWeight > randomWeight
 	// The return i is guaranteed to be in range [0, len(rw.items)) because randomWeight < last item's accumulatedWeight
diff --git a/scripts/vet.sh b/scripts/vet.sh
index f5806f1c..3e4e3863 100755
--- a/scripts/vet.sh
+++ b/scripts/vet.sh
@@ -54,10 +54,6 @@ git grep --quiet 'func [A-Z]' -- "*_test.go" | not grep -v 'func Test\|Benchmark
 #   timer since there is no way to stop it early.
 git grep --quiet -l 'time.After(' -- "*.go" | not grep -v '_test.go\|test_utils\|testutils'
 
-# - Do not import math/rand for real library code.  Use internal/grpcrand for
-#   thread safety.
-git grep --quiet -l '"math/rand"' -- "*.go" 2>&1 | not grep -v '^examples\|^interop/stress\|grpcrand\|^benchmark\|wrr_test'
-
 # - Do not use "interface{}"; use "any" instead.
 git grep --quiet -l 'interface{}' -- "*.go" 2>&1 | not grep -v '\.pb\.go\|protoc-gen-go-grpc\|grpc_testing_not_regenerate'
 
diff --git a/stream.go b/stream.go
index b54563e8..8051ef5b 100644
--- a/stream.go
+++ b/stream.go
@@ -23,6 +23,7 @@ import (
 	"errors"
 	"io"
 	"math"
+	"math/rand"
 	"strconv"
 	"sync"
 	"time"
@@ -34,7 +35,6 @@ import (
 	"google.golang.org/grpc/internal/balancerload"
 	"google.golang.org/grpc/internal/binarylog"
 	"google.golang.org/grpc/internal/channelz"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpcutil"
 	imetadata "google.golang.org/grpc/internal/metadata"
 	iresolver "google.golang.org/grpc/internal/resolver"
@@ -699,7 +699,7 @@ func (a *csAttempt) shouldRetry(err error) (bool, error) {
 		if max := float64(rp.MaxBackoff); cur > max {
 			cur = max
 		}
-		dur = time.Duration(grpcrand.Int63n(int64(cur)))
+		dur = time.Duration(rand.Int63n(int64(cur)))
 		cs.numRetriesSincePushback++
 	}
 
diff --git a/xds/googledirectpath/googlec2p.go b/xds/googledirectpath/googlec2p.go
index 37b38799..6ab7fb03 100644
--- a/xds/googledirectpath/googlec2p.go
+++ b/xds/googledirectpath/googlec2p.go
@@ -27,6 +27,7 @@ package googledirectpath
 
 import (
 	"fmt"
+	"math/rand"
 	"net/url"
 	"time"
 
@@ -34,7 +35,6 @@ import (
 	"google.golang.org/grpc/internal/envconfig"
 	"google.golang.org/grpc/internal/googlecloud"
 	internalgrpclog "google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/xds/bootstrap"
 	"google.golang.org/grpc/resolver"
 	"google.golang.org/grpc/xds/internal/xdsclient"
@@ -159,7 +159,7 @@ func (r *c2pResolver) Close() {
 	r.clientCloseFunc()
 }
 
-var id = fmt.Sprintf("C2P-%d", grpcrand.Int())
+var id = fmt.Sprintf("C2P-%d", rand.Int())
 
 func newNodeConfig(zone string, ipv6Capable bool) string {
 	metadata := ""
diff --git a/xds/internal/balancer/outlierdetection/balancer.go b/xds/internal/balancer/outlierdetection/balancer.go
index 34604318..80d3d444 100644
--- a/xds/internal/balancer/outlierdetection/balancer.go
+++ b/xds/internal/balancer/outlierdetection/balancer.go
@@ -25,6 +25,7 @@ import (
 	"encoding/json"
 	"fmt"
 	"math"
+	"math/rand"
 	"strings"
 	"sync"
 	"sync/atomic"
@@ -37,7 +38,6 @@ import (
 	"google.golang.org/grpc/internal/buffer"
 	"google.golang.org/grpc/internal/channelz"
 	"google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpcsync"
 	iserviceconfig "google.golang.org/grpc/internal/serviceconfig"
 	"google.golang.org/grpc/resolver"
@@ -838,7 +838,7 @@ func (b *outlierDetectionBalancer) successRateAlgorithm() {
 		requiredSuccessRate := mean - stddev*(float64(ejectionCfg.StdevFactor)/1000)
 		if successRate < requiredSuccessRate {
 			channelz.Infof(logger, b.channelzParent, "SuccessRate algorithm detected outlier: %s. Parameters: successRate=%f, mean=%f, stddev=%f, requiredSuccessRate=%f", addrInfo, successRate, mean, stddev, requiredSuccessRate)
-			if uint32(grpcrand.Int31n(100)) < ejectionCfg.EnforcementPercentage {
+			if uint32(rand.Int31n(100)) < ejectionCfg.EnforcementPercentage {
 				b.ejectAddress(addrInfo)
 			}
 		}
@@ -865,7 +865,7 @@ func (b *outlierDetectionBalancer) failurePercentageAlgorithm() {
 		failurePercentage := (float64(bucket.numFailures) / float64(bucket.numSuccesses+bucket.numFailures)) * 100
 		if failurePercentage > float64(b.cfg.FailurePercentageEjection.Threshold) {
 			channelz.Infof(logger, b.channelzParent, "FailurePercentage algorithm detected outlier: %s, failurePercentage=%f", addrInfo, failurePercentage)
-			if uint32(grpcrand.Int31n(100)) < ejectionCfg.EnforcementPercentage {
+			if uint32(rand.Int31n(100)) < ejectionCfg.EnforcementPercentage {
 				b.ejectAddress(addrInfo)
 			}
 		}
diff --git a/xds/internal/httpfilter/fault/fault.go b/xds/internal/httpfilter/fault/fault.go
index 3ad93e27..90155d80 100644
--- a/xds/internal/httpfilter/fault/fault.go
+++ b/xds/internal/httpfilter/fault/fault.go
@@ -24,12 +24,12 @@ import (
 	"errors"
 	"fmt"
 	"io"
+	"math/rand"
 	"strconv"
 	"sync/atomic"
 	"time"
 
 	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/internal/grpcrand"
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/metadata"
 	"google.golang.org/grpc/status"
@@ -162,7 +162,7 @@ func (i *interceptor) NewStream(ctx context.Context, ri iresolver.RPCInfo, done
 }
 
 // For overriding in tests
-var randIntn = grpcrand.Intn
+var randIntn = rand.Intn
 var newTimer = time.NewTimer
 
 func injectDelay(ctx context.Context, delayCfg *cpb.FaultDelay) error {
diff --git a/xds/internal/resolver/serviceconfig.go b/xds/internal/resolver/serviceconfig.go
index 88cb1d2a..f5bfc500 100644
--- a/xds/internal/resolver/serviceconfig.go
+++ b/xds/internal/resolver/serviceconfig.go
@@ -23,13 +23,13 @@ import (
 	"encoding/json"
 	"fmt"
 	"math/bits"
+	"math/rand"
 	"strings"
 	"sync/atomic"
 	"time"
 
 	xxhash "github.com/cespare/xxhash/v2"
 	"google.golang.org/grpc/codes"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpcutil"
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/serviceconfig"
@@ -274,7 +274,7 @@ func (cs *configSelector) generateHash(rpcInfo iresolver.RPCInfo, hashPolicies [
 	}
 	// If no generated hash return a random long. In the grand scheme of things
 	// this logically will map to choosing a random backend to route request to.
-	return grpcrand.Uint64()
+	return rand.Uint64()
 }
 
 func (cs *configSelector) newInterceptor(rt *route, cluster *routeCluster) (iresolver.ClientInterceptor, error) {
diff --git a/xds/internal/resolver/xds_resolver.go b/xds/internal/resolver/xds_resolver.go
index 928decfa..19061261 100644
--- a/xds/internal/resolver/xds_resolver.go
+++ b/xds/internal/resolver/xds_resolver.go
@@ -22,11 +22,11 @@ package resolver
 import (
 	"context"
 	"fmt"
+	"math/rand"
 	"sync/atomic"
 
 	"google.golang.org/grpc/internal"
 	"google.golang.org/grpc/internal/grpclog"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpcsync"
 	"google.golang.org/grpc/internal/pretty"
 	iresolver "google.golang.org/grpc/internal/resolver"
@@ -75,7 +75,7 @@ func (b *xdsResolverBuilder) Build(target resolver.Target, cc resolver.ClientCon
 	r := &xdsResolver{
 		cc:             cc,
 		activeClusters: make(map[string]*clusterInfo),
-		channelID:      grpcrand.Uint64(),
+		channelID:      rand.Uint64(),
 	}
 	defer func() {
 		if retErr != nil {
diff --git a/xds/internal/xdsclient/xdsresource/matcher.go b/xds/internal/xdsclient/xdsresource/matcher.go
index 77aa85b6..796e9e30 100644
--- a/xds/internal/xdsclient/xdsresource/matcher.go
+++ b/xds/internal/xdsclient/xdsresource/matcher.go
@@ -19,9 +19,9 @@ package xdsresource
 
 import (
 	"fmt"
+	"math/rand"
 	"strings"
 
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpcutil"
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/xds/matcher"
@@ -142,8 +142,8 @@ func newFractionMatcher(fraction uint32) *fractionMatcher {
 	return &fractionMatcher{fraction: int64(fraction)}
 }
 
-// RandInt63n overwrites grpcrand for control in tests.
-var RandInt63n = grpcrand.Int63n
+// RandInt63n overwrites rand for control in tests.
+var RandInt63n = rand.Int63n
 
 func (fm *fractionMatcher) match() bool {
 	t := RandInt63n(1000000)



Test patch:
diff --git a/balancer/leastrequest/balancer_test.go b/balancer/leastrequest/balancer_test.go
index 4c308b58..ee66b24f 100644
--- a/balancer/leastrequest/balancer_test.go
+++ b/balancer/leastrequest/balancer_test.go
@@ -194,13 +194,13 @@ func checkRoundRobinRPCs(ctx context.Context, client testgrpc.TestServiceClient,
 // deterministic, allowing the test to make assertions on the distribution.
 func (s) TestLeastRequestE2E(t *testing.T) {
 	defer func(u func() uint32) {
-		grpcranduint32 = u
-	}(grpcranduint32)
+		randuint32 = u
+	}(randuint32)
 	var index int
 	indexes := []uint32{
 		0, 0, 1, 1, 2, 2, // Triggers a round robin distribution.
 	}
-	grpcranduint32 = func() uint32 {
+	randuint32 = func() uint32 {
 		ret := indexes[index%len(indexes)]
 		index++
 		return ret
@@ -310,13 +310,13 @@ func (s) TestLeastRequestE2E(t *testing.T) {
 // previous. Any created streams should then be started on the new backend.
 func (s) TestLeastRequestPersistsCounts(t *testing.T) {
 	defer func(u func() uint32) {
-		grpcranduint32 = u
-	}(grpcranduint32)
+		randuint32 = u
+	}(randuint32)
 	var index int
 	indexes := []uint32{
 		0, 0, 1, 1,
 	}
-	grpcranduint32 = func() uint32 {
+	randuint32 = func() uint32 {
 		ret := indexes[index%len(indexes)]
 		index++
 		return ret
diff --git a/internal/serviceconfig/duration_test.go b/internal/serviceconfig/duration_test.go
index 5696541a..b03a4508 100644
--- a/internal/serviceconfig/duration_test.go
+++ b/internal/serviceconfig/duration_test.go
@@ -21,11 +21,10 @@ package serviceconfig
 import (
 	"fmt"
 	"math"
+	"math/rand"
 	"strings"
 	"testing"
 	"time"
-
-	"google.golang.org/grpc/internal/grpcrand"
 )
 
 // Tests both marshalling and unmarshalling of Durations.
@@ -69,7 +68,7 @@ func TestDuration_MarshalUnmarshal(t *testing.T) {
 	for _, tc := range testCases {
 		// Seed `got` with a random value to ensure we properly reset it in all
 		// non-error cases.
-		got := Duration(grpcrand.Uint64())
+		got := Duration(rand.Uint64())
 		err := got.UnmarshalJSON([]byte(tc.json))
 		if (err == nil && time.Duration(got) != tc.td) ||
 			(err != nil) != (tc.unmarshalErr != nil) || !strings.Contains(fmt.Sprint(err), fmt.Sprint(tc.unmarshalErr)) {
diff --git a/internal/wrr/wrr_test.go b/internal/wrr/wrr_test.go
index ce4f5e50..7ede1fff 100644
--- a/internal/wrr/wrr_test.go
+++ b/internal/wrr/wrr_test.go
@@ -189,5 +189,5 @@ func BenchmarkRandomWRRNext(b *testing.B) {
 
 func init() {
 	r := rand.New(rand.NewSource(0))
-	grpcrandInt63n = r.Int63n
+	randInt63n = r.Int63n
 }
diff --git a/test/pickfirst_test.go b/test/pickfirst_test.go
index 8bcd8011..750c2305 100644
--- a/test/pickfirst_test.go
+++ b/test/pickfirst_test.go
@@ -33,7 +33,6 @@ import (
 	"google.golang.org/grpc/credentials/insecure"
 	"google.golang.org/grpc/internal"
 	"google.golang.org/grpc/internal/channelz"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/stubserver"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/pickfirst"
@@ -378,9 +377,9 @@ func (s) TestPickFirst_ShuffleAddressList(t *testing.T) {
 	const serviceConfig = `{"loadBalancingConfig": [{"pick_first":{ "shuffleAddressList": true }}]}`
 
 	// Install a shuffler that always reverses two entries.
-	origShuf := grpcrand.Shuffle
-	defer func() { grpcrand.Shuffle = origShuf }()
-	grpcrand.Shuffle = func(n int, f func(int, int)) {
+	origShuf := internal.ShuffleAddressListForTesting
+	defer func() { internal.ShuffleAddressListForTesting = origShuf }()
+	internal.ShuffleAddressListForTesting = func(n int, f func(int, int)) {
 		if n != 2 {
 			t.Errorf("Shuffle called with n=%v; want 2", n)
 			return
@@ -435,9 +434,9 @@ func (s) TestPickFirst_ShuffleAddressList(t *testing.T) {
 // Test config parsing with the env var turned on and off for various scenarios.
 func (s) TestPickFirst_ParseConfig_Success(t *testing.T) {
 	// Install a shuffler that always reverses two entries.
-	origShuf := grpcrand.Shuffle
-	defer func() { grpcrand.Shuffle = origShuf }()
-	grpcrand.Shuffle = func(n int, f func(int, int)) {
+	origShuf := internal.ShuffleAddressListForTesting
+	defer func() { internal.ShuffleAddressListForTesting = origShuf }()
+	internal.ShuffleAddressListForTesting = func(n int, f func(int, int)) {
 		if n != 2 {
 			t.Errorf("Shuffle called with n=%v; want 2", n)
 			return
diff --git a/xds/internal/httpfilter/fault/fault_test.go b/xds/internal/httpfilter/fault/fault_test.go
index fade7bc4..8660daec 100644
--- a/xds/internal/httpfilter/fault/fault_test.go
+++ b/xds/internal/httpfilter/fault/fault_test.go
@@ -26,6 +26,7 @@ import (
 	"context"
 	"fmt"
 	"io"
+	"math/rand"
 	"net"
 	"reflect"
 	"testing"
@@ -35,7 +36,6 @@ import (
 	"google.golang.org/grpc"
 	"google.golang.org/grpc/codes"
 	"google.golang.org/grpc/credentials/insecure"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpctest"
 	"google.golang.org/grpc/internal/testutils"
 	"google.golang.org/grpc/internal/testutils/xds/bootstrap"
@@ -483,7 +483,7 @@ func (s) TestFaultInjection_Unary(t *testing.T) {
 
 	for tcNum, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			defer func() { randIntn = grpcrand.Intn; newTimer = time.NewTimer }()
+			defer func() { randIntn = rand.Intn; newTimer = time.NewTimer }()
 			var intnCalls []int
 			var newTimerCalls []time.Duration
 			randOut := 0
diff --git a/xds/internal/xdsclient/xdsresource/matcher_test.go b/xds/internal/xdsclient/xdsresource/matcher_test.go
index 9931a63b..6b376db2 100644
--- a/xds/internal/xdsclient/xdsresource/matcher_test.go
+++ b/xds/internal/xdsclient/xdsresource/matcher_test.go
@@ -19,10 +19,10 @@ package xdsresource
 
 import (
 	"context"
+	"math/rand"
 	"testing"
 
 	"github.com/google/go-cmp/cmp"
-	"google.golang.org/grpc/internal/grpcrand"
 	"google.golang.org/grpc/internal/grpcutil"
 	iresolver "google.golang.org/grpc/internal/resolver"
 	"google.golang.org/grpc/internal/xds/matcher"
@@ -119,7 +119,7 @@ func (s) TestFractionMatcherMatch(t *testing.T) {
 	const fraction = 500000
 	fm := newFractionMatcher(fraction)
 	defer func() {
-		RandInt63n = grpcrand.Int63n
+		RandInt63n = rand.Int63n
 	}()
 
 	// rand > fraction, should return false.