mirror of
https://github.com/openlibrecommunity/olcrtc.git
synced 2026-05-30 08:59:43 +00:00
97 lines
2.4 KiB
Go
97 lines
2.4 KiB
Go
// Package supervisor runs ordered session profiles with failover.
|
|
package supervisor
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/openlibrecommunity/olcrtc/internal/app/session"
|
|
)
|
|
|
|
const DefaultRetryDelay = 2 * time.Second
|
|
|
|
var (
|
|
// ErrNoProfiles is returned when the supervisor is started without profiles.
|
|
ErrNoProfiles = errors.New("supervisor: no profiles configured")
|
|
// ErrMaxCyclesExceeded is returned after MaxCycles complete profile-list passes.
|
|
ErrMaxCyclesExceeded = errors.New("supervisor: max failover cycles exceeded")
|
|
)
|
|
|
|
// Profile is one runnable session configuration in an ordered failover list.
|
|
type Profile struct {
|
|
Name string
|
|
Config session.Config
|
|
}
|
|
|
|
// Runner starts one session profile and blocks until it ends or fails.
|
|
type Runner func(ctx context.Context, cfg session.Config) error
|
|
|
|
// Config controls ordered failover behavior.
|
|
type Config struct {
|
|
Profiles []Profile
|
|
RetryDelay time.Duration
|
|
MaxCycles int
|
|
|
|
OnProfileStart func(profile Profile, cycle int)
|
|
OnProfileEnd func(profile Profile, cycle int, err error)
|
|
}
|
|
|
|
// Run starts profiles in order. If a profile exits while ctx is still active,
|
|
// the supervisor waits RetryDelay and advances to the next profile.
|
|
func Run(ctx context.Context, cfg Config, run Runner) error {
|
|
if len(cfg.Profiles) == 0 {
|
|
return ErrNoProfiles
|
|
}
|
|
if cfg.RetryDelay == 0 {
|
|
cfg.RetryDelay = DefaultRetryDelay
|
|
}
|
|
|
|
var lastErr error
|
|
for cycle := 1; ; cycle++ {
|
|
for i, profile := range cfg.Profiles {
|
|
if ctx.Err() != nil {
|
|
return nil
|
|
}
|
|
if cfg.OnProfileStart != nil {
|
|
cfg.OnProfileStart(profile, cycle)
|
|
}
|
|
|
|
err := run(ctx, profile.Config)
|
|
if ctx.Err() != nil {
|
|
return nil
|
|
}
|
|
if err != nil {
|
|
lastErr = fmt.Errorf("profile %q: %w", profile.Name, err)
|
|
} else {
|
|
lastErr = fmt.Errorf("profile %q ended", profile.Name)
|
|
}
|
|
if cfg.OnProfileEnd != nil {
|
|
cfg.OnProfileEnd(profile, cycle, err)
|
|
}
|
|
|
|
if cfg.MaxCycles > 0 && cycle >= cfg.MaxCycles && i == len(cfg.Profiles)-1 {
|
|
return fmt.Errorf("%w after %d cycle(s): %w", ErrMaxCyclesExceeded, cycle, lastErr)
|
|
}
|
|
if err := waitRetryDelay(ctx, cfg.RetryDelay); err != nil {
|
|
return nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
func waitRetryDelay(ctx context.Context, delay time.Duration) error {
|
|
if delay <= 0 {
|
|
return nil
|
|
}
|
|
timer := time.NewTimer(delay)
|
|
defer timer.Stop()
|
|
select {
|
|
case <-ctx.Done():
|
|
return ctx.Err()
|
|
case <-timer.C:
|
|
return nil
|
|
}
|
|
}
|