Files
livekit/pkg/sfu/ccutils/probe_regulator.go
T
Leo Camus 1e539510c0 ccutils: use millisecond resolution for probe interval backoff (#4891)
* ccutils: use millisecond resolution for probe interval backoff

ProbeSignal grew probeInterval via Seconds(), which truncates the float
to the nearest nanosecond before scaling by time.Second:

    time.Duration(probeInterval.Seconds() * BackoffFactor) * time.Second

With BackoffFactor=1.5 and the default BaseInterval of 3 s, the first
congestion signal produced 4 s instead of 4.5 s, and the error compounded
on every subsequent signal (4 s → 6 s → 9 s → 13 s ... instead of
4.5 s → 6.75 s → 10.125 s ...).  The interval converges to MaxInterval
later than intended, so the server probes more frequently during sustained
congestion than the configuration requests.

probeDuration in the same function already uses the correct idiom:

    time.Duration(float64(probeDuration.Milliseconds()) * Factor) * time.Millisecond

Apply the same pattern to probeInterval so both fields are computed
consistently with millisecond precision.

Found by a defect-hunting pipeline I build and run ([Dev-next-gen](https://github.com/Dev-next-gen)), using Claude Code with Anthropic's Claude Opus 5.

* ccutils: remove probe interval backoff test

Requested by reviewer.
2026-09-22 11:04:53 +05:30

107 lines
3.3 KiB
Go

// Copyright 2023 LiveKit, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package ccutils
import (
"time"
"github.com/livekit/protocol/logger"
"github.com/livekit/protocol/utils/mono"
)
// ------------------------------------------------
type ProbeRegulatorConfig struct {
BaseInterval time.Duration `yaml:"base_interval,omitempty"`
BackoffFactor float64 `yaml:"backoff_factor,omitempty"`
MaxInterval time.Duration `yaml:"max_interval,omitempty"`
MinDuration time.Duration `yaml:"min_duration,omitempty"`
MaxDuration time.Duration `yaml:"max_duration,omitempty"`
DurationIncreaseFactor float64 `yaml:"duration_increase_factor,omitempty"`
}
var (
DefaultProbeRegulatorConfig = ProbeRegulatorConfig{
BaseInterval: 3 * time.Second,
BackoffFactor: 1.5,
MaxInterval: 2 * time.Minute,
MinDuration: 200 * time.Millisecond,
MaxDuration: 20 * time.Second,
DurationIncreaseFactor: 1.5,
}
)
// ---------------------------------------------------------------------------
type ProbeRegulatorParams struct {
Config ProbeRegulatorConfig
Logger logger.Logger
}
type ProbeRegulator struct {
params ProbeRegulatorParams
probeInterval time.Duration
probeDuration time.Duration
nextProbeEarliestAt time.Time
}
func NewProbeRegulator(params ProbeRegulatorParams) *ProbeRegulator {
return &ProbeRegulator{
params: params,
probeInterval: params.Config.BaseInterval,
probeDuration: params.Config.MinDuration,
nextProbeEarliestAt: mono.Now(),
}
}
func (p *ProbeRegulator) CanProbe() bool {
return mono.Now().After(p.nextProbeEarliestAt)
}
func (p *ProbeRegulator) ProbeDuration() time.Duration {
return p.probeDuration
}
func (p *ProbeRegulator) ProbeSignal(probeSignal ProbeSignal, baseTime time.Time) {
if probeSignal == ProbeSignalCongesting {
// wait longer till next probe
p.probeInterval = time.Duration(float64(p.probeInterval.Milliseconds())*p.params.Config.BackoffFactor) * time.Millisecond
if p.probeInterval > p.params.Config.MaxInterval {
p.probeInterval = p.params.Config.MaxInterval
}
// revert back to starting with shortest probe
p.probeDuration = p.params.Config.MinDuration
} else {
// probe can be started again after minimal interval as previous congestion signal indicated congestion clearing
p.probeInterval = p.params.Config.BaseInterval
// can do longer probe after a good probe
p.probeDuration = time.Duration(float64(p.probeDuration.Milliseconds())*p.params.Config.DurationIncreaseFactor) * time.Millisecond
if p.probeDuration > p.params.Config.MaxDuration {
p.probeDuration = p.params.Config.MaxDuration
}
}
if baseTime.IsZero() {
p.nextProbeEarliestAt = mono.Now().Add(p.probeInterval)
} else {
p.nextProbeEarliestAt = baseTime.Add(p.probeInterval)
}
}