Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: optimize jitter factor calculation #3629

Merged
merged 5 commits into from
Jan 29, 2025
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 46 additions & 41 deletions tm2/pkg/p2p/switch.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package p2p

import (
"bytes"
"context"
"crypto/rand"
"encoding/binary"
"fmt"
"math"
"math/big"
"sync"
"time"

Expand Down Expand Up @@ -482,65 +483,69 @@
}
}

// calculateBackoff calculates a backoff time,
// based on the number of attempts and range limits
// calculateBackoff calculates the backoff interval by exponentiating the base interval
// by the number of attempts. The returned interval is capped at maxInterval and has a
// jitter factor applied to it (+/- 10% of interval, max 10 sec).
func calculateBackoff(
attempts int,
minTimeout time.Duration,
maxTimeout time.Duration,
baseInterval time.Duration,
maxInterval time.Duration,
) time.Duration {
var (
minTime = time.Second * 1
maxTime = time.Second * 60
multiplier = float64(2) // exponential
const (
defaultBaseInterval = time.Second * 1
defaultMaxInterval = time.Second * 60

Check warning on line 496 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L494-L496

Added lines #L494 - L496 were not covered by tests
)

// Check the min limit
if minTimeout > 0 {
minTime = minTimeout
// Sanitize base interval parameter.
if baseInterval <= 0 {
baseInterval = defaultBaseInterval

Check warning on line 501 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L499-L501

Added lines #L499 - L501 were not covered by tests
}

// Check the max limit
if maxTimeout > 0 {
maxTime = maxTimeout
// Sanitize max interval parameter.
if maxInterval <= 0 {
maxInterval = defaultMaxInterval

Check warning on line 506 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L505-L506

Added lines #L505 - L506 were not covered by tests
}

// Sanity check the range
if minTime >= maxTime {
return maxTime
// Calculate the interval by exponentiating the base interval by the number of attempts.
interval := baseInterval << attempts

// Cap the interval to the maximum interval.
if interval > maxInterval {
interval = maxInterval

Check warning on line 514 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L510-L514

Added lines #L510 - L514 were not covered by tests
}

// Calculate the backoff duration
var (
base = float64(minTime)
calculated = base * math.Pow(multiplier, float64(attempts))
)
// Below is the code to add a jitter factor to the interval.
// Read random bytes into an 8 bytes buffer (size of an int64).
var randBytes [8]byte
_, err := rand.Read(randBytes[:])
if err != nil {
return interval
}

Check warning on line 523 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L519-L523

Added lines #L519 - L523 were not covered by tests

// Attempt to calculate the jitter factor
n, err := rand.Int(rand.Reader, big.NewInt(math.MaxInt64))
if err == nil {
jitterFactor := float64(n.Int64()) / float64(math.MaxInt64) // range [0, 1]
// Convert the random bytes to an int64.
var randInt64 int64
_ = binary.Read(bytes.NewReader(randBytes[:]), binary.NativeEndian, &randInt64)

Check warning on line 527 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L526-L527

Added lines #L526 - L527 were not covered by tests

calculated = jitterFactor*(calculated-base) + base
}
// Calculate the random jitter multiplier (float between -1 and 1).
jitterMultiplier := float64(randInt64) / float64(math.MaxInt64)

Check warning on line 530 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L529-L530

Added lines #L529 - L530 were not covered by tests

// Prevent overflow for int64 (duration) cast
if calculated > float64(math.MaxInt64) {
return maxTime
}
const (
maxJitterDuration = 10 * time.Second
maxJitterPercentage = 10 // 10%
)

Check warning on line 535 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L532-L535

Added lines #L532 - L535 were not covered by tests

duration := time.Duration(calculated)
// Calculate the maximum jitter based on interval percentage.
maxJitter := interval * maxJitterPercentage / 100

Check warning on line 538 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L537-L538

Added lines #L537 - L538 were not covered by tests

// Clamp the duration within bounds
if duration < minTime {
return minTime
// Cap the maximum jitter to the maximum duration.
if maxJitter > maxJitterDuration {
maxJitter = maxJitterDuration

Check warning on line 542 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L540-L542

Added lines #L540 - L542 were not covered by tests
}

if duration > maxTime {
return maxTime
}
// Calculate the jitter.
jitter := time.Duration(float64(maxJitter) * jitterMultiplier)

Check warning on line 546 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L546

Added line #L546 was not covered by tests

return duration
return interval + jitter

Check warning on line 548 in tm2/pkg/p2p/switch.go

View check run for this annotation

Codecov / codecov/patch

tm2/pkg/p2p/switch.go#L548

Added line #L548 was not covered by tests
}

// DialPeers adds the peers to the dial queue for async dialing.
Expand Down
Loading