first
This commit is contained in:
@@ -0,0 +1,100 @@
|
||||
package scheduler
|
||||
|
||||
// This file demonstrates how to add a custom scheduler.
|
||||
// To create your own:
|
||||
// 1. Copy this file and rename it (e.g., weighted_rtt.go)
|
||||
// 2. Implement the three PathScheduler methods
|
||||
// 3. Update the init() to register with your scheduler name
|
||||
//
|
||||
// The scheduler will automatically appear in --list-schedulers.
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
quic "github.com/AeonDave/mp-quic-go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
Register("weighted", func() quic.PathScheduler {
|
||||
return NewWeightedScheduler()
|
||||
})
|
||||
}
|
||||
|
||||
// WeightedScheduler is an example custom scheduler that weighs paths
|
||||
// by a combination of RTT and available congestion window.
|
||||
type WeightedScheduler struct {
|
||||
mu sync.Mutex
|
||||
quotas map[quic.PathID]uint64
|
||||
}
|
||||
|
||||
// NewWeightedScheduler creates a new weighted scheduler.
|
||||
func NewWeightedScheduler() *WeightedScheduler {
|
||||
return &WeightedScheduler{
|
||||
quotas: make(map[quic.PathID]uint64),
|
||||
}
|
||||
}
|
||||
|
||||
// SelectPath picks the path with the best weighted score.
|
||||
// Score = (CongestionWindow - BytesInFlight) / (1 + SmoothedRTT_ms)
|
||||
// Higher score = more capacity available per unit latency.
|
||||
func (s *WeightedScheduler) SelectPath(paths []quic.SchedulerPathInfo, hasRetransmission bool) *quic.SchedulerPathInfo {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
|
||||
if len(paths) == 0 {
|
||||
return nil
|
||||
}
|
||||
if len(paths) == 1 {
|
||||
if !hasRetransmission && !paths[0].SendingAllowed {
|
||||
return nil
|
||||
}
|
||||
return &paths[0]
|
||||
}
|
||||
|
||||
var best *quic.SchedulerPathInfo
|
||||
var bestScore float64 = -1
|
||||
|
||||
for i := range paths {
|
||||
p := &paths[i]
|
||||
if !hasRetransmission && !p.SendingAllowed {
|
||||
continue
|
||||
}
|
||||
if p.PotentiallyFailed {
|
||||
continue
|
||||
}
|
||||
|
||||
// Available window
|
||||
available := float64(0)
|
||||
if p.CongestionWindow > p.BytesInFlight {
|
||||
available = float64(p.CongestionWindow - p.BytesInFlight)
|
||||
}
|
||||
|
||||
// RTT factor (ms, minimum 1 to avoid division by zero)
|
||||
rttMs := float64(1)
|
||||
if p.SmoothedRTT.Milliseconds() > 0 {
|
||||
rttMs = float64(p.SmoothedRTT.Milliseconds())
|
||||
}
|
||||
|
||||
score := available / rttMs
|
||||
if score > bestScore {
|
||||
bestScore = score
|
||||
best = p
|
||||
}
|
||||
}
|
||||
|
||||
return best
|
||||
}
|
||||
|
||||
// UpdateQuota tracks per-path send counts.
|
||||
func (s *WeightedScheduler) UpdateQuota(pathID quic.PathID, packetSize quic.ByteCount) {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.quotas[pathID]++
|
||||
}
|
||||
|
||||
// Reset clears all state.
|
||||
func (s *WeightedScheduler) Reset() {
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
s.quotas = make(map[quic.PathID]uint64)
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package scheduler
|
||||
|
||||
import quic "github.com/AeonDave/mp-quic-go"
|
||||
|
||||
func init() {
|
||||
Register("lowlatency", func() quic.PathScheduler {
|
||||
return quic.NewLowLatencyScheduler()
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
package scheduler
|
||||
|
||||
import quic "github.com/AeonDave/mp-quic-go"
|
||||
|
||||
func init() {
|
||||
Register("minrtt", func() quic.PathScheduler {
|
||||
// rttBias=0.8 favors lower RTT paths while still considering load balance.
|
||||
// Adjust this value: 1.0=pure RTT, 0.0=pure load balancing.
|
||||
return quic.NewMinRTTScheduler(0.8)
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Package scheduler provides a modular registry for MP-QUIC path schedulers.
|
||||
//
|
||||
// Adding a new scheduler:
|
||||
// 1. Create a new file in this package (e.g., myscheduler.go)
|
||||
// 2. Implement quic.PathScheduler (SelectPath, UpdateQuota, Reset)
|
||||
// 3. In an init() function, call Register("myscheduler", factory)
|
||||
//
|
||||
// See custom_example.go for a complete example.
|
||||
package scheduler
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
quic "github.com/AeonDave/mp-quic-go"
|
||||
)
|
||||
|
||||
// Factory is a function that creates a new PathScheduler instance.
|
||||
type Factory func() quic.PathScheduler
|
||||
|
||||
var (
|
||||
mu sync.RWMutex
|
||||
factories = make(map[string]Factory)
|
||||
)
|
||||
|
||||
// Register adds a scheduler factory to the registry.
|
||||
// Call this from init() in each scheduler file.
|
||||
func Register(name string, factory Factory) {
|
||||
mu.Lock()
|
||||
defer mu.Unlock()
|
||||
if _, exists := factories[name]; exists {
|
||||
panic(fmt.Sprintf("scheduler: duplicate registration for %q", name))
|
||||
}
|
||||
factories[name] = factory
|
||||
}
|
||||
|
||||
// Get creates a new instance of the named scheduler.
|
||||
// Returns an error if the name is not registered.
|
||||
func Get(name string) (quic.PathScheduler, error) {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
factory, exists := factories[name]
|
||||
if !exists {
|
||||
return nil, fmt.Errorf("scheduler: unknown scheduler %q (available: %v)", name, List())
|
||||
}
|
||||
return factory(), nil
|
||||
}
|
||||
|
||||
// List returns sorted names of all registered schedulers.
|
||||
func List() []string {
|
||||
mu.RLock()
|
||||
defer mu.RUnlock()
|
||||
names := make([]string, 0, len(factories))
|
||||
for name := range factories {
|
||||
names = append(names, name)
|
||||
}
|
||||
sort.Strings(names)
|
||||
return names
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package scheduler
|
||||
|
||||
import quic "github.com/AeonDave/mp-quic-go"
|
||||
|
||||
func init() {
|
||||
Register("roundrobin", func() quic.PathScheduler {
|
||||
return quic.NewRoundRobinScheduler()
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user