switch to math/rand/v2 in most places

this allows removing some ugly instantiations of an rng based on the current
time.

Intn is now IntN for our concurrency-safe prng wrapper to match the randv2 api.

v2 exists since go1.22, which we already require.
This commit is contained in:
Mechiel Lukkien 2024-11-29 13:45:19 +01:00
parent 96a3ecd52c
commit de435fceba
No known key found for this signature in database
9 changed files with 24 additions and 30 deletions

View file

@ -15,7 +15,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
mathrand "math/rand" mathrand2 "math/rand/v2"
"time" "time"
"github.com/mjl-/mox/dkim" "github.com/mjl-/mox/dkim"
@ -257,7 +257,7 @@ func Verify(ctx context.Context, elog *slog.Logger, resolver dns.Resolver, msgFr
// Record can request sampling of messages to apply policy. // Record can request sampling of messages to apply policy.
// See ../rfc/7489:1432 // See ../rfc/7489:1432
useResult = !applyRandomPercentage || record.Percentage == 100 || mathrand.Intn(100) < record.Percentage useResult = !applyRandomPercentage || record.Percentage == 100 || mathrand2.IntN(100) < record.Percentage
// We treat "quarantine" and "reject" the same. Thus, we also don't "downgrade" // We treat "quarantine" and "reject" the same. Thus, we also don't "downgrade"
// from reject to quarantine if this message was sampled out. // from reject to quarantine if this message was sampled out.

View file

@ -252,7 +252,7 @@ var jitterRand = mox.NewPseudoRand()
// Jitter so we don't cause load at exactly whole hours, other processes may // Jitter so we don't cause load at exactly whole hours, other processes may
// already be doing that. // already be doing that.
var jitteredTimeUntil = func(t time.Time) time.Duration { var jitteredTimeUntil = func(t time.Time) time.Duration {
return time.Until(t.Add(time.Duration(30+jitterRand.Intn(60)) * time.Second)) return time.Until(t.Add(time.Duration(30+jitterRand.IntN(60)) * time.Second))
} }
// Start launches a goroutine that wakes up at each whole hour (plus jitter) and // Start launches a goroutine that wakes up at each whole hour (plus jitter) and

View file

@ -214,12 +214,12 @@ messages are shuffled, with optional random seed.`
hamFiles := listDir(hamDir) hamFiles := listDir(hamDir)
spamFiles := listDir(spamDir) spamFiles := listDir(spamDir)
var rand *mathrand.Rand var seed int64
if a.seed { if a.seed {
rand = mathrand.New(mathrand.NewSource(time.Now().UnixMilli())) seed = time.Now().UnixMilli()
} else {
rand = mathrand.New(mathrand.NewSource(0))
} }
// Still at math/rand (v1 instead of v2) for potential comparison to earlier test results.
rand := mathrand.New(mathrand.NewSource(seed))
shuffle := func(l []string) { shuffle := func(l []string) {
count := len(l) count := len(l)

View file

@ -4,19 +4,23 @@ import (
cryptorand "crypto/rand" cryptorand "crypto/rand"
"encoding/binary" "encoding/binary"
"fmt" "fmt"
mathrand "math/rand" mathrand2 "math/rand/v2"
"sync" "sync"
) )
type rand struct { type rand struct {
rand *mathrand.Rand rand *mathrand2.Rand
sync.Mutex sync.Mutex
} }
// NewPseudoRand returns a new PRNG seeded with random bytes from crypto/rand. Its // NewPseudoRand returns a new PRNG seeded with random bytes from crypto/rand. Its
// functions can be called concurrently. // functions can be called concurrently.
func NewPseudoRand() *rand { func NewPseudoRand() *rand {
return &rand{rand: mathrand.New(mathrand.NewSource(CryptoRandInt()))} var seed [32]byte
if _, err := cryptorand.Read(seed[:]); err != nil {
panic(err)
}
return &rand{rand: mathrand2.New(mathrand2.NewChaCha8(seed))}
} }
func (r *rand) Float64() float64 { func (r *rand) Float64() float64 {
@ -25,16 +29,10 @@ func (r *rand) Float64() float64 {
return r.rand.Float64() return r.rand.Float64()
} }
func (r *rand) Intn(n int) int { func (r *rand) IntN(n int) int {
r.Lock() r.Lock()
defer r.Unlock() defer r.Unlock()
return r.rand.Intn(n) return r.rand.IntN(n)
}
func (r *rand) Read(buf []byte) (int, error) {
r.Lock()
defer r.Unlock()
return r.rand.Read(buf)
} }
// CryptoRandInt returns a cryptographically random number. // CryptoRandInt returns a cryptographically random number.

View file

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
"log/slog" "log/slog"
mathrand "math/rand" mathrand2 "math/rand/v2"
"runtime/debug" "runtime/debug"
"time" "time"
@ -71,12 +71,11 @@ func refresh1(ctx context.Context, log mlog.Log, resolver dns.Resolver, sleep fu
} }
// Randomize list. // Randomize list.
rand := mathrand.New(mathrand.NewSource(time.Now().UnixNano()))
for i := range prs { for i := range prs {
if i == 0 { if i == 0 {
continue continue
} }
j := rand.Intn(i + 1) j := mathrand2.IntN(i + 1)
prs[i], prs[j] = prs[j], prs[i] prs[i], prs[j] = prs[j], prs[i]
} }
@ -87,7 +86,7 @@ func refresh1(ctx context.Context, log mlog.Log, resolver dns.Resolver, sleep fu
go refreshDomain(ctx, log, DB, resolver, pr) go refreshDomain(ctx, log, DB, resolver, pr)
if i < len(prs)-1 { if i < len(prs)-1 {
interval := 3 * int64(time.Hour) / int64(len(prs)-1) interval := 3 * int64(time.Hour) / int64(len(prs)-1)
extra := time.Duration(rand.Int63n(interval) - interval/2) extra := time.Duration(mathrand2.Int64N(interval) - interval/2)
next := start.Add(time.Duration(int64(i+1)*interval) + extra) next := start.Add(time.Duration(int64(i+1)*interval) + extra)
d := next.Sub(timeNow()) d := next.Sub(timeNow())
if d > 0 { if d > 0 {

View file

@ -1119,7 +1119,7 @@ func hookDeliver(log mlog.Log, h Hook) {
} else { } else {
backoff = hookIntervals[len(hookIntervals)-1] * time.Duration(2) backoff = hookIntervals[len(hookIntervals)-1] * time.Duration(2)
} }
backoff += time.Duration(jitter.Intn(200)-100) * backoff / 10000 backoff += time.Duration(jitter.IntN(200)-100) * backoff / 10000
h.Attempts++ h.Attempts++
now := time.Now() now := time.Now()
h.NextAttempt = now.Add(backoff) h.NextAttempt = now.Add(backoff)

View file

@ -1370,7 +1370,7 @@ func deliver(log mlog.Log, resolver dns.Resolver, m0 Msg) {
return fmt.Errorf("get message to be delivered: %v", err) return fmt.Errorf("get message to be delivered: %v", err)
} }
backoff = time.Duration(7*60+30+jitter.Intn(10)-5) * time.Second backoff = time.Duration(7*60+30+jitter.IntN(10)-5) * time.Second
for i := 0; i < m0.Attempts; i++ { for i := 0; i < m0.Attempts; i++ {
backoff *= time.Duration(2) backoff *= time.Duration(2)
} }

View file

@ -73,7 +73,7 @@ var jitterRand = mox.NewPseudoRand()
// Jitter so we don't cause load at exactly midnight, other processes may // Jitter so we don't cause load at exactly midnight, other processes may
// already be doing that. // already be doing that.
var jitteredTimeUntil = func(t time.Time) time.Duration { var jitteredTimeUntil = func(t time.Time) time.Duration {
return time.Until(t.Add(time.Duration(240+jitterRand.Intn(120)) * time.Second)) return time.Until(t.Add(time.Duration(240+jitterRand.IntN(120)) * time.Second))
} }
// Start launches a goroutine that wakes up just after 00:00 UTC to send TLSRPT // Start launches a goroutine that wakes up just after 00:00 UTC to send TLSRPT

View file

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"io" "io"
"log/slog" "log/slog"
mathrand "math/rand" mathrand2 "math/rand/v2"
"net/http" "net/http"
"runtime/debug" "runtime/debug"
"sync" "sync"
@ -71,9 +71,6 @@ func (ew *eventWriter) write(name string, v any) error {
return ew.out.Flush() return ew.out.Flush()
} }
// For random wait between min and max delay.
var waitGen = mathrand.New(mathrand.NewSource(time.Now().UnixNano()))
// Schedule an event for writing to the connection. If events get a delay, this // Schedule an event for writing to the connection. If events get a delay, this
// function still returns immediately. // function still returns immediately.
func (ew *eventWriter) xsendEvent(ctx context.Context, log mlog.Log, name string, v any) { func (ew *eventWriter) xsendEvent(ctx context.Context, log mlog.Log, name string, v any) {
@ -136,7 +133,7 @@ func (ew *eventWriter) xsendEvent(ctx context.Context, log mlog.Log, name string
} }
// If we have an events channel, we have a goroutine that write the events, delayed. // If we have an events channel, we have a goroutine that write the events, delayed.
if ew.events != nil { if ew.events != nil {
wait := ew.waitMin + time.Duration(waitGen.Intn(1000))*(ew.waitMax-ew.waitMin)/1000 wait := ew.waitMin + time.Duration(mathrand2.IntN(1000))*(ew.waitMax-ew.waitMin)/1000
when := time.Now().Add(wait) when := time.Now().Add(wait)
ew.events <- struct { ew.events <- struct {
name string name string