Update to math/rand/v2 (#7657)

Replace all of Boulder's usage of the Go stdlib "math/rand" package with
the newer "math/rand/v2" package which first became available in go1.22.
This package has an improved API and faster performance across the
board.

See https://go.dev/blog/randv2 and https://go.dev/blog/chacha8rand for
details.
This commit is contained in:
Aaron Gable 2024-08-12 09:17:09 -07:00 committed by GitHub
parent 8380bb9b92
commit 61b484c13b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
21 changed files with 55 additions and 54 deletions

View File

@ -4,15 +4,16 @@ import (
"context"
"errors"
"fmt"
"math/rand"
"math/rand/v2"
"net"
"strconv"
"sync"
"time"
"github.com/letsencrypt/boulder/cmd"
"github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus"
"github.com/letsencrypt/boulder/cmd"
)
// ServerProvider represents a type which can provide a list of addresses for
@ -306,7 +307,7 @@ func (dp *dynamicProvider) Addrs() ([]string, error) {
var r []string
dp.mu.RLock()
for ip, ports := range dp.addrs {
port := fmt.Sprint(ports[rand.Intn(len(ports))])
port := fmt.Sprint(ports[rand.IntN(len(ports))])
addr := net.JoinHostPort(ip, port)
r = append(r, addr)
}

View File

@ -14,7 +14,7 @@ import (
"errors"
"fmt"
"math/big"
mrand "math/rand"
mrand "math/rand/v2"
"strings"
"time"
@ -535,7 +535,7 @@ func (ca *certificateAuthorityImpl) issuePrecertificateInner(ctx context.Context
if !ok || len(issuerPool) == 0 {
return nil, nil, berrors.InternalServerError("no issuers found for public key algorithm %s", csr.PublicKeyAlgorithm)
}
issuer := issuerPool[mrand.Intn(len(issuerPool))]
issuer := issuerPool[mrand.IntN(len(issuerPool))]
if issuer.Cert.NotAfter.Before(notAfter) {
err = berrors.InternalServerError("cannot issue a certificate that expires after the issuer certificate")

View File

@ -15,7 +15,7 @@ import (
"errors"
"log"
"math/big"
mrand "math/rand"
mrand "math/rand/v2"
"os"
"slices"
"sort"
@ -355,7 +355,7 @@ func TestGetAndProcessCerts(t *testing.T) {
reg := satest.CreateWorkingRegistration(t, isa.SA{Impl: sa})
test.AssertNotError(t, err, "Couldn't create registration")
for range 5 {
rawCert.SerialNumber = big.NewInt(mrand.Int63())
rawCert.SerialNumber = big.NewInt(mrand.Int64())
certDER, err := x509.CreateCertificate(rand.Reader, &rawCert, &rawCert, &testKey.PublicKey, testKey)
test.AssertNotError(t, err, "Couldn't create certificate")
_, err = sa.AddCertificate(context.Background(), &sapb.AddCertificateRequest{

View File

@ -3,7 +3,7 @@ package notmain
import (
"context"
"fmt"
"math/rand"
"math/rand/v2"
"os"
"sync/atomic"
"time"
@ -104,9 +104,9 @@ func (cl *client) loadFromDB(ctx context.Context, speed ProcessingSpeed, startFr
if result.err != nil {
errorCount++
if errorCount < 10 ||
(errorCount < 1000 && rand.Intn(1000) < 100) ||
(errorCount < 100000 && rand.Intn(1000) < 10) ||
(rand.Intn(1000) < 1) {
(errorCount < 1000 && rand.IntN(1000) < 100) ||
(errorCount < 100000 && rand.IntN(1000) < 10) ||
(rand.IntN(1000) < 1) {
cl.logger.Errf("error: %s", result.err)
}
} else {
@ -115,9 +115,9 @@ func (cl *client) loadFromDB(ctx context.Context, speed ProcessingSpeed, startFr
total := successCount + errorCount
if total < 10 ||
(total < 1000 && rand.Intn(1000) < 100) ||
(total < 100000 && rand.Intn(1000) < 10) ||
(rand.Intn(1000) < 1) {
(total < 1000 && rand.IntN(1000) < 100) ||
(total < 100000 && rand.IntN(1000) < 10) ||
(rand.IntN(1000) < 1) {
cl.logger.Infof("stored %d responses, %d errors", successCount, errorCount)
}
}

View File

@ -15,7 +15,7 @@ import (
"fmt"
"io"
"math/big"
mrand "math/rand"
mrand "math/rand/v2"
"os"
"path"
"reflect"

View File

@ -2,7 +2,7 @@ package updater
import (
"context"
"math/rand"
"math/rand/v2"
"sync"
"time"
@ -21,7 +21,7 @@ func (cu *crlUpdater) Run(ctx context.Context) error {
// Wait for a random number of nanoseconds less than the updatePeriod, so
// that process restarts do not skip or delay shards deterministically.
waitTimer := time.NewTimer(time.Duration(rand.Int63n(cu.updatePeriod.Nanoseconds())))
waitTimer := time.NewTimer(time.Duration(rand.Int64N(cu.updatePeriod.Nanoseconds())))
defer waitTimer.Stop()
select {
case <-waitTimer.C:

View File

@ -5,7 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"math/rand/v2"
"os"
"strings"
"time"
@ -314,6 +314,6 @@ func (ll List) PickOne(operator string, expiry time.Time) (string, string, error
return "", "", fmt.Errorf("no log found for group %q and expiry %s", operator, expiry)
}
log := candidates[rand.Intn(len(candidates))]
log := candidates[rand.IntN(len(candidates))]
return log.Url, log.Key, nil
}

View File

@ -21,13 +21,12 @@
package grpcrand
import (
"math/rand"
"math/rand/v2"
"sync"
"time"
)
var (
r = rand.New(rand.NewSource(time.Now().UnixNano()))
r = rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64()))
mu sync.Mutex
)
@ -42,14 +41,14 @@ func Int() int {
func Int63n(n int64) int64 {
mu.Lock()
defer mu.Unlock()
return r.Int63n(n)
return r.Int64N(n)
}
// Intn implements rand.Intn on the grpcrand global source.
func Intn(n int) int {
mu.Lock()
defer mu.Unlock()
return r.Intn(n)
return r.IntN(n)
}
// Float64 implements rand.Float64 on the grpcrand global source.

View File

@ -6,7 +6,7 @@ import (
"crypto/x509"
"errors"
"fmt"
"math/rand"
"math/rand/v2"
"net"
"os"
"time"
@ -353,7 +353,7 @@ func (sa *StorageAuthority) NewOrderAndAuthzs(_ context.Context, req *sapb.NewOr
Names: req.NewOrder.Names,
V2Authorizations: req.NewOrder.V2Authorizations,
// Mock new fields generated by the database transaction.
Id: rand.Int63(),
Id: rand.Int64(),
Created: timestamppb.Now(),
// A new order is never processing because it can't have been finalized yet.
BeganProcessing: false,

View File

@ -40,7 +40,7 @@ import (
"errors"
"fmt"
"io"
"math/rand"
"math/rand/v2"
"net/http"
"net/url"
"time"
@ -153,7 +153,7 @@ var hashToString = map[crypto.Hash]string{
}
func SampledError(log blog.Logger, sampleRate int, format string, a ...interface{}) {
if sampleRate > 0 && rand.Intn(sampleRate) == 0 {
if sampleRate > 0 && rand.IntN(sampleRate) == 0 {
log.Errf(format, a...)
}
}

View File

@ -5,7 +5,7 @@ import (
"encoding/hex"
"errors"
"fmt"
"math/rand"
"math/rand/v2"
"net"
"net/mail"
"os"
@ -46,7 +46,7 @@ func New(challengeTypes map[core.AcmeChallenge]bool, log blog.Logger) (*Authorit
log: log,
enabledChallenges: challengeTypes,
// We don't need real randomness for this.
pseudoRNG: rand.New(rand.NewSource(99)),
pseudoRNG: rand.New(rand.NewPCG(rand.Uint64(), rand.Uint64())),
}
return &pa, nil

View File

@ -15,7 +15,7 @@ import (
"errors"
"fmt"
"math/big"
mrand "math/rand"
mrand "math/rand/v2"
"net"
"os"
"regexp"
@ -2401,7 +2401,7 @@ func (msa *mockSAWithAuthzs) GetAuthorizations2(ctx context.Context, req *sapb.G
func (msa *mockSAWithAuthzs) NewOrderAndAuthzs(ctx context.Context, req *sapb.NewOrderAndAuthzsRequest, _ ...grpc.CallOption) (*corepb.Order, error) {
authzIDs := req.NewOrder.V2Authorizations
for range req.NewAuthzs {
authzIDs = append(authzIDs, mrand.Int63())
authzIDs = append(authzIDs, mrand.Int64())
}
return &corepb.Order{
// Fields from the input new order request.
@ -2411,7 +2411,7 @@ func (msa *mockSAWithAuthzs) NewOrderAndAuthzs(ctx context.Context, req *sapb.Ne
V2Authorizations: authzIDs,
CertificateProfileName: req.NewOrder.CertificateProfileName,
// Mock new fields generated by the database transaction.
Id: mrand.Int63(),
Id: mrand.Int64(),
Created: timestamppb.Now(),
// A new order is never processing because it can't have been finalized yet.
BeganProcessing: false,

View File

@ -2,7 +2,7 @@ package ratelimits
import (
"context"
"math/rand"
"math/rand/v2"
"net"
"testing"
"time"
@ -43,7 +43,7 @@ func setup(t *testing.T) (context.Context, map[string]*Limiter, *TransactionBuil
// runs.
randIP := make(net.IP, 4)
for i := range 4 {
randIP[i] = byte(rand.Intn(256))
randIP[i] = byte(rand.IntN(256))
}
// Construct a limiter for each source.

View File

@ -14,7 +14,7 @@ import (
"fmt"
"math/big"
"math/bits"
mrand "math/rand"
mrand "math/rand/v2"
"net"
"os"
"reflect"
@ -3358,7 +3358,7 @@ func TestSerialsForIncident(t *testing.T) {
"1335": true, "1336": true, "1337": true, "1338": true,
}
for i := range expectedSerials {
randInt := func() int64 { return mrand.Int63() }
randInt := func() int64 { return mrand.Int64() }
_, err := testIncidentsDbMap.ExecContext(ctx,
fmt.Sprintf("INSERT INTO incident_foo (%s) VALUES ('%s', %d, %d, '%s')",
"serial, registrationID, orderID, lastNoticeSent",

View File

@ -6,14 +6,15 @@ package semaphore_test
import (
"context"
"math/rand"
"math/rand/v2"
"runtime"
"sync"
"testing"
"time"
"github.com/letsencrypt/boulder/semaphore"
"golang.org/x/sync/errgroup"
"github.com/letsencrypt/boulder/semaphore"
)
const maxSleep = 1 * time.Millisecond
@ -21,7 +22,7 @@ const maxSleep = 1 * time.Millisecond
func HammerWeighted(sem *semaphore.Weighted, n int64, loops int) {
for i := 0; i < loops; i++ {
_ = sem.Acquire(context.Background(), n)
time.Sleep(time.Duration(rand.Int63n(int64(maxSleep/time.Nanosecond))) * time.Nanosecond)
time.Sleep(time.Duration(rand.Int64N(int64(maxSleep/time.Nanosecond))) * time.Nanosecond)
sem.Release(n)
}
}

View File

@ -13,7 +13,7 @@ import (
"fmt"
"io"
"log"
"math/rand"
"math/rand/v2"
"net/http"
"os"
"strings"
@ -161,7 +161,7 @@ func (is *integrationSrv) addChainOrPre(w http.ResponseWriter, r *http.Request,
is.submissions[hostnames]++
is.Unlock()
if is.flakinessRate != 0 && rand.Intn(100) < is.flakinessRate {
if is.flakinessRate != 0 && rand.IntN(100) < is.flakinessRate {
time.Sleep(10 * time.Second)
}

View File

@ -3,7 +3,7 @@ package acme
import (
"errors"
"fmt"
mrand "math/rand"
mrand "math/rand/v2"
"strings"
"github.com/letsencrypt/boulder/core"
@ -67,7 +67,7 @@ func (strategy randomChallengeStrategy) PickChallenge(authz *core.Authorization)
if len(authz.Challenges) == 0 {
return nil, ErrPickChallengeAuthzMissingChallenges
}
return &authz.Challenges[mrand.Intn(len(authz.Challenges))], nil
return &authz.Challenges[mrand.IntN(len(authz.Challenges))], nil
}
// preferredTypeChallengeStrategy is a ChallengeStrategy implementation that

View File

@ -16,7 +16,7 @@ import (
"errors"
"fmt"
"io"
mrand "math/rand"
mrand "math/rand/v2"
"net/http"
"time"
@ -72,7 +72,7 @@ func getAccount(s *State, c *acmeCache) error {
}
// Select a random account from the state and put it into the context
c.acct = s.accts[mrand.Intn(len(s.accts))]
c.acct = s.accts[mrand.IntN(len(s.accts))]
c.ns = &nonceSource{s: s}
return nil
}
@ -164,7 +164,7 @@ func randDomain(base string) string {
func newOrder(s *State, c *acmeCache) error {
// Pick a random number of names within the constraints of the maxNamesPerCert
// parameter
orderSize := 1 + mrand.Intn(s.maxNamesPerCert-1)
orderSize := 1 + mrand.IntN(s.maxNamesPerCert-1)
// Generate that many random domain names. There may be some duplicates, we
// don't care. The ACME server will collapse those down for us, how handy!
dnsNames := []identifier.ACMEIdentifier{}
@ -231,7 +231,7 @@ func newOrder(s *State, c *acmeCache) error {
// popPendingOrder *removes* a random pendingOrder from the context, returning
// it.
func popPendingOrder(c *acmeCache) *OrderJSON {
orderIndex := mrand.Intn(len(c.pendingOrders))
orderIndex := mrand.IntN(len(c.pendingOrders))
order := c.pendingOrders[orderIndex]
c.pendingOrders = append(c.pendingOrders[:orderIndex], c.pendingOrders[orderIndex+1:]...)
return order
@ -465,7 +465,7 @@ func pollOrderForCert(order *OrderJSON, s *State, c *acmeCache) (*OrderJSON, err
// popFulfilledOrder **removes** a fulfilled order from the context, returning
// it. Fulfilled orders have all of their authorizations satisfied.
func popFulfilledOrder(c *acmeCache) string {
orderIndex := mrand.Intn(len(c.fulfilledOrders))
orderIndex := mrand.IntN(len(c.fulfilledOrders))
order := c.fulfilledOrders[orderIndex]
c.fulfilledOrders = append(c.fulfilledOrders[:orderIndex], c.fulfilledOrders[orderIndex+1:]...)
return order
@ -580,7 +580,7 @@ func postAsGet(s *State, c *acmeCache, url string, latencyTag string) (*http.Res
}
func popCertificate(c *acmeCache) string {
certIndex := mrand.Intn(len(c.certs))
certIndex := mrand.IntN(len(c.certs))
certURL := c.certs[certIndex]
c.certs = append(c.certs[:certIndex], c.certs[certIndex+1:]...)
return certURL

View File

@ -3,7 +3,7 @@ package va
import (
"context"
"fmt"
"math/rand"
"math/rand/v2"
"net/url"
"regexp"
"strings"

View File

@ -6,7 +6,7 @@ import (
"encoding/base64"
"errors"
"fmt"
mrand "math/rand"
mrand "math/rand/v2"
"net"
"net/http"
"net/http/httptest"
@ -1213,7 +1213,7 @@ func TestHTTPBadPort(t *testing.T) {
// Pick a random port between 40000 and 65000 - with great certainty we won't
// have an HTTP server listening on this port and the test will fail as
// intended
badPort := 40000 + mrand.Intn(25000)
badPort := 40000 + mrand.IntN(25000)
va.httpPort = badPort
_, err := va.validateHTTP01(ctx, dnsi("localhost"), expectedToken, expectedKeyAuthorization)

View File

@ -7,7 +7,7 @@ import (
"encoding/json"
"errors"
"fmt"
"math/rand"
"math/rand/v2"
"net"
"net/url"
"os"