This commit is contained in:
Dongho Kim
2026-05-17 16:33:43 +02:00
commit a221870b70
20 changed files with 1285 additions and 0 deletions
+100
View File
@@ -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)
}
+9
View File
@@ -0,0 +1,9 @@
package scheduler
import quic "github.com/AeonDave/mp-quic-go"
func init() {
Register("lowlatency", func() quic.PathScheduler {
return quic.NewLowLatencyScheduler()
})
}
+11
View File
@@ -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)
})
}
+60
View File
@@ -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
}
+9
View File
@@ -0,0 +1,9 @@
package scheduler
import quic "github.com/AeonDave/mp-quic-go"
func init() {
Register("roundrobin", func() quic.PathScheduler {
return quic.NewRoundRobinScheduler()
})
}