From de435fceba87b8971dec81bce30bf97c172d90d8 Mon Sep 17 00:00:00 2001 From: Mechiel Lukkien Date: Fri, 29 Nov 2024 13:45:19 +0100 Subject: [PATCH] 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. --- dmarc/dmarc.go | 4 ++-- dmarcdb/eval.go | 2 +- junk.go | 8 ++++---- mox-/rand.go | 20 +++++++++----------- mtastsdb/refresh.go | 7 +++---- queue/hook.go | 2 +- queue/queue.go | 2 +- tlsrptsend/send.go | 2 +- webmail/eventwriter.go | 7 ++----- 9 files changed, 24 insertions(+), 30 deletions(-) diff --git a/dmarc/dmarc.go b/dmarc/dmarc.go index ccfc8b1..3b59ca4 100644 --- a/dmarc/dmarc.go +++ b/dmarc/dmarc.go @@ -15,7 +15,7 @@ import ( "errors" "fmt" "log/slog" - mathrand "math/rand" + mathrand2 "math/rand/v2" "time" "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. // 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" // from reject to quarantine if this message was sampled out. diff --git a/dmarcdb/eval.go b/dmarcdb/eval.go index 9b893e1..1740f31 100644 --- a/dmarcdb/eval.go +++ b/dmarcdb/eval.go @@ -252,7 +252,7 @@ var jitterRand = mox.NewPseudoRand() // Jitter so we don't cause load at exactly whole hours, other processes may // already be doing that. 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 diff --git a/junk.go b/junk.go index 0aabc3d..aa00bfe 100644 --- a/junk.go +++ b/junk.go @@ -214,12 +214,12 @@ messages are shuffled, with optional random seed.` hamFiles := listDir(hamDir) spamFiles := listDir(spamDir) - var rand *mathrand.Rand + var seed int64 if a.seed { - rand = mathrand.New(mathrand.NewSource(time.Now().UnixMilli())) - } else { - rand = mathrand.New(mathrand.NewSource(0)) + seed = time.Now().UnixMilli() } + // 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) { count := len(l) diff --git a/mox-/rand.go b/mox-/rand.go index 3aac6d7..d37153b 100644 --- a/mox-/rand.go +++ b/mox-/rand.go @@ -4,19 +4,23 @@ import ( cryptorand "crypto/rand" "encoding/binary" "fmt" - mathrand "math/rand" + mathrand2 "math/rand/v2" "sync" ) type rand struct { - rand *mathrand.Rand + rand *mathrand2.Rand sync.Mutex } // NewPseudoRand returns a new PRNG seeded with random bytes from crypto/rand. Its // functions can be called concurrently. 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 { @@ -25,16 +29,10 @@ func (r *rand) Float64() float64 { return r.rand.Float64() } -func (r *rand) Intn(n int) int { +func (r *rand) IntN(n int) int { r.Lock() defer r.Unlock() - return r.rand.Intn(n) -} - -func (r *rand) Read(buf []byte) (int, error) { - r.Lock() - defer r.Unlock() - return r.rand.Read(buf) + return r.rand.IntN(n) } // CryptoRandInt returns a cryptographically random number. diff --git a/mtastsdb/refresh.go b/mtastsdb/refresh.go index d4c7950..8783aa9 100644 --- a/mtastsdb/refresh.go +++ b/mtastsdb/refresh.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" "log/slog" - mathrand "math/rand" + mathrand2 "math/rand/v2" "runtime/debug" "time" @@ -71,12 +71,11 @@ func refresh1(ctx context.Context, log mlog.Log, resolver dns.Resolver, sleep fu } // Randomize list. - rand := mathrand.New(mathrand.NewSource(time.Now().UnixNano())) for i := range prs { if i == 0 { continue } - j := rand.Intn(i + 1) + j := mathrand2.IntN(i + 1) 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) if i < 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) d := next.Sub(timeNow()) if d > 0 { diff --git a/queue/hook.go b/queue/hook.go index bc740eb..ec3728b 100644 --- a/queue/hook.go +++ b/queue/hook.go @@ -1119,7 +1119,7 @@ func hookDeliver(log mlog.Log, h Hook) { } else { 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++ now := time.Now() h.NextAttempt = now.Add(backoff) diff --git a/queue/queue.go b/queue/queue.go index 0a987be..a8ce550 100644 --- a/queue/queue.go +++ b/queue/queue.go @@ -1370,7 +1370,7 @@ func deliver(log mlog.Log, resolver dns.Resolver, m0 Msg) { 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++ { backoff *= time.Duration(2) } diff --git a/tlsrptsend/send.go b/tlsrptsend/send.go index 69a05bd..c7c34a6 100644 --- a/tlsrptsend/send.go +++ b/tlsrptsend/send.go @@ -73,7 +73,7 @@ var jitterRand = mox.NewPseudoRand() // Jitter so we don't cause load at exactly midnight, other processes may // already be doing that. 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 diff --git a/webmail/eventwriter.go b/webmail/eventwriter.go index f9a87aa..5d9a7f5 100644 --- a/webmail/eventwriter.go +++ b/webmail/eventwriter.go @@ -7,7 +7,7 @@ import ( "fmt" "io" "log/slog" - mathrand "math/rand" + mathrand2 "math/rand/v2" "net/http" "runtime/debug" "sync" @@ -71,9 +71,6 @@ func (ew *eventWriter) write(name string, v any) error { 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 // function still returns immediately. 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 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) ew.events <- struct { name string