diff --git a/internal/route/route.go b/internal/route/route.go index 0098ff38..991a0535 100644 --- a/internal/route/route.go +++ b/internal/route/route.go @@ -497,6 +497,45 @@ func (r *Route) DisplayName() string { return r.Homepage.Name } +// PreferOver implements pool.Preferable to resolve duplicate route keys deterministically. +// Preference policy: +// - Prefer routes with rules over routes without rules. +// - If rules tie, prefer non-docker routes (explicit config) over docker-discovered routes. +// - Otherwise, prefer the new route to preserve existing semantics. +func (r *Route) PreferOver(other any) bool { + // Try to get the underlying *Route of the other value + var or *Route + switch v := other.(type) { + case *Route: + or = v + case *ReveseProxyRoute: + or = v.Route + case *FileServer: + or = v.Route + case *StreamRoute: + or = v.Route + default: + // Unknown type, allow replacement + return true + } + + // Prefer routes that have rules + if len(r.Rules) > 0 && len(or.Rules) == 0 { + return true + } + if len(r.Rules) == 0 && len(or.Rules) > 0 { + return false + } + + // Prefer explicit (non-docker) over docker auto-discovered + if (r.Container == nil) != (or.Container == nil) { + return r.Container == nil + } + + // Default: allow replacement + return true +} + func (r *Route) ContainerInfo() *types.Container { return r.Container } diff --git a/internal/utils/pool/pool.go b/internal/utils/pool/pool.go index aad6ce81..51cbb467 100644 --- a/internal/utils/pool/pool.go +++ b/internal/utils/pool/pool.go @@ -14,6 +14,12 @@ type ( name string disableLog atomic.Bool } + // Preferable allows an object to express deterministic replacement preference + // when multiple objects with the same key are added to the pool. + // If new.PreferOver(old) returns true, the new object replaces the old one. + Preferable interface { + PreferOver(other any) bool + } Object interface { Key() string Name() string @@ -37,12 +43,18 @@ func (p *Pool[T]) Name() string { } func (p *Pool[T]) Add(obj T) { - p.checkExists(obj.Key()) - p.m.Store(obj.Key(), obj) - p.logAction("added", obj) + p.AddKey(obj.Key(), obj) } func (p *Pool[T]) AddKey(key string, obj T) { + if cur, exists := p.m.Load(key); exists { + if newPref, ok := any(obj).(Preferable); ok { + if !newPref.PreferOver(cur) { + // keep existing + return + } + } + } p.checkExists(key) p.m.Store(key, obj) p.logAction("added", obj)