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" "context"
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand/v2"
"net" "net"
"strconv" "strconv"
"sync" "sync"
"time" "time"
"github.com/letsencrypt/boulder/cmd"
"github.com/miekg/dns" "github.com/miekg/dns"
"github.com/prometheus/client_golang/prometheus" "github.com/prometheus/client_golang/prometheus"
"github.com/letsencrypt/boulder/cmd"
) )
// ServerProvider represents a type which can provide a list of addresses for // 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 var r []string
dp.mu.RLock() dp.mu.RLock()
for ip, ports := range dp.addrs { 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) addr := net.JoinHostPort(ip, port)
r = append(r, addr) r = append(r, addr)
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -2,7 +2,7 @@ package updater
import ( import (
"context" "context"
"math/rand" "math/rand/v2"
"sync" "sync"
"time" "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 // Wait for a random number of nanoseconds less than the updatePeriod, so
// that process restarts do not skip or delay shards deterministically. // 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() defer waitTimer.Stop()
select { select {
case <-waitTimer.C: case <-waitTimer.C:

View File

@ -5,7 +5,7 @@ import (
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
"math/rand" "math/rand/v2"
"os" "os"
"strings" "strings"
"time" "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) 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 return log.Url, log.Key, nil
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,7 @@ import (
"fmt" "fmt"
"io" "io"
"log" "log"
"math/rand" "math/rand/v2"
"net/http" "net/http"
"os" "os"
"strings" "strings"
@ -161,7 +161,7 @@ func (is *integrationSrv) addChainOrPre(w http.ResponseWriter, r *http.Request,
is.submissions[hostnames]++ is.submissions[hostnames]++
is.Unlock() 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) time.Sleep(10 * time.Second)
} }

View File

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

View File

@ -16,7 +16,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
mrand "math/rand" mrand "math/rand/v2"
"net/http" "net/http"
"time" "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 // 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} c.ns = &nonceSource{s: s}
return nil return nil
} }
@ -164,7 +164,7 @@ func randDomain(base string) string {
func newOrder(s *State, c *acmeCache) error { func newOrder(s *State, c *acmeCache) error {
// Pick a random number of names within the constraints of the maxNamesPerCert // Pick a random number of names within the constraints of the maxNamesPerCert
// parameter // 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 // 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! // don't care. The ACME server will collapse those down for us, how handy!
dnsNames := []identifier.ACMEIdentifier{} dnsNames := []identifier.ACMEIdentifier{}
@ -231,7 +231,7 @@ func newOrder(s *State, c *acmeCache) error {
// popPendingOrder *removes* a random pendingOrder from the context, returning // popPendingOrder *removes* a random pendingOrder from the context, returning
// it. // it.
func popPendingOrder(c *acmeCache) *OrderJSON { func popPendingOrder(c *acmeCache) *OrderJSON {
orderIndex := mrand.Intn(len(c.pendingOrders)) orderIndex := mrand.IntN(len(c.pendingOrders))
order := c.pendingOrders[orderIndex] order := c.pendingOrders[orderIndex]
c.pendingOrders = append(c.pendingOrders[:orderIndex], c.pendingOrders[orderIndex+1:]...) c.pendingOrders = append(c.pendingOrders[:orderIndex], c.pendingOrders[orderIndex+1:]...)
return order 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 // popFulfilledOrder **removes** a fulfilled order from the context, returning
// it. Fulfilled orders have all of their authorizations satisfied. // it. Fulfilled orders have all of their authorizations satisfied.
func popFulfilledOrder(c *acmeCache) string { func popFulfilledOrder(c *acmeCache) string {
orderIndex := mrand.Intn(len(c.fulfilledOrders)) orderIndex := mrand.IntN(len(c.fulfilledOrders))
order := c.fulfilledOrders[orderIndex] order := c.fulfilledOrders[orderIndex]
c.fulfilledOrders = append(c.fulfilledOrders[:orderIndex], c.fulfilledOrders[orderIndex+1:]...) c.fulfilledOrders = append(c.fulfilledOrders[:orderIndex], c.fulfilledOrders[orderIndex+1:]...)
return order return order
@ -580,7 +580,7 @@ func postAsGet(s *State, c *acmeCache, url string, latencyTag string) (*http.Res
} }
func popCertificate(c *acmeCache) string { func popCertificate(c *acmeCache) string {
certIndex := mrand.Intn(len(c.certs)) certIndex := mrand.IntN(len(c.certs))
certURL := c.certs[certIndex] certURL := c.certs[certIndex]
c.certs = append(c.certs[:certIndex], c.certs[certIndex+1:]...) c.certs = append(c.certs[:certIndex], c.certs[certIndex+1:]...)
return certURL return certURL

View File

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

View File

@ -6,7 +6,7 @@ import (
"encoding/base64" "encoding/base64"
"errors" "errors"
"fmt" "fmt"
mrand "math/rand" mrand "math/rand/v2"
"net" "net"
"net/http" "net/http"
"net/http/httptest" "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 // 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 // have an HTTP server listening on this port and the test will fail as
// intended // intended
badPort := 40000 + mrand.Intn(25000) badPort := 40000 + mrand.IntN(25000)
va.httpPort = badPort va.httpPort = badPort
_, err := va.validateHTTP01(ctx, dnsi("localhost"), expectedToken, expectedKeyAuthorization) _, err := va.validateHTTP01(ctx, dnsi("localhost"), expectedToken, expectedKeyAuthorization)

View File

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