Files
x/config/loader/loader.go
T
ginuerzh ba3e1cbe09 fix(config/loader): close old services before binding new ports on reload (issue #754)
Services are the only component group that binds a port during parsing
(ParseService calls listener.Init). The generic registerGroup "parse-all-
then-swap" sequence bound every new listener while the old services were
still listening, causing EADDRINUSE on SIGHUP reload — a regression from
82e7e50.

Close and unregister all old services first (unregisterAll relies on
registry.Unregister closing io.Closer values, which frees each service's
port), then parse, bind, and register the new ones. This restores the
pre-82e7e50 close-old-before-bind ordering for the services group.
2026-06-14 01:40:50 +08:00

341 lines
10 KiB
Go

// Package loader converts a parsed [config.Config] into running components
// by parsing each config section and registering the results into the
// global registries. Components are registered in dependency order so that
// when a component reads its dependencies from registries during parsing,
// those dependencies are already available.
package loader
import (
"github.com/go-gost/core/admission"
"github.com/go-gost/core/auth"
"github.com/go-gost/core/bypass"
"github.com/go-gost/core/chain"
"github.com/go-gost/core/hop"
"github.com/go-gost/core/hosts"
"github.com/go-gost/core/ingress"
"github.com/go-gost/core/limiter/conn"
"github.com/go-gost/core/limiter/rate"
"github.com/go-gost/core/limiter/traffic"
"github.com/go-gost/core/logger"
"github.com/go-gost/core/observer"
"github.com/go-gost/core/recorder"
reg "github.com/go-gost/core/registry"
"github.com/go-gost/core/resolver"
"github.com/go-gost/core/router"
"github.com/go-gost/core/sd"
"github.com/go-gost/x/config"
"github.com/go-gost/x/config/parsing"
admission_parser "github.com/go-gost/x/config/parsing/admission"
auth_parser "github.com/go-gost/x/config/parsing/auth"
bypass_parser "github.com/go-gost/x/config/parsing/bypass"
chain_parser "github.com/go-gost/x/config/parsing/chain"
hop_parser "github.com/go-gost/x/config/parsing/hop"
hosts_parser "github.com/go-gost/x/config/parsing/hosts"
ingress_parser "github.com/go-gost/x/config/parsing/ingress"
limiter_parser "github.com/go-gost/x/config/parsing/limiter"
logger_parser "github.com/go-gost/x/config/parsing/logger"
observer_parser "github.com/go-gost/x/config/parsing/observer"
recorder_parser "github.com/go-gost/x/config/parsing/recorder"
resolver_parser "github.com/go-gost/x/config/parsing/resolver"
router_parser "github.com/go-gost/x/config/parsing/router"
sd_parser "github.com/go-gost/x/config/parsing/sd"
service_parser "github.com/go-gost/x/config/parsing/service"
"github.com/go-gost/x/registry"
)
// defaultLoader is a singleton loader used by the top-level Load function.
var (
defaultLoader *loader = &loader{}
)
// Load parses all config sections from cfg and registers the resulting
// components into the global registries, then sets up the default logger
// and TLS config.
func Load(cfg *config.Config) error {
return defaultLoader.Load(cfg)
}
// loader holds no state; its Load method is the entry point for converting
// a config into running components.
type loader struct{}
// Load builds the default TLS config, registers all named components from
// cfg into the global registries, and sets the default logger and TLS config.
func (l *loader) Load(cfg *config.Config) error {
if cfg == nil {
return nil
}
logCfg := cfg.Log
if logCfg == nil {
logCfg = &config.LogConfig{}
}
logger.SetDefault(logger_parser.ParseLogger(&config.LoggerConfig{Log: logCfg}))
tlsCfg, err := parsing.BuildDefaultTLSConfig(cfg.TLS)
if err != nil {
return err
}
parsing.SetDefaultTLSConfig(tlsCfg)
if err := register(cfg); err != nil {
return err
}
return nil
}
// named is a generic name+value pair used to buffer parsed components
// before registering them.
type named[T any] struct {
name string
v T
}
// registerGroup replaces all entries in r with the given entries. Old entries
// are unregistered first (see unregisterAll); if any new entry fails to
// register, the group is left partially updated (matching the historical
// reload behavior for intra-group failures).
func registerGroup[T any](entries []named[T], r reg.Registry[T]) error {
unregisterAll(r)
for _, e := range entries {
if err := r.Register(e.name, e.v); err != nil {
return err
}
}
return nil
}
// unregisterAll removes every entry from r. registry.Unregister closes a value
// before deleting it when it implements io.Closer, so for services this frees
// the bound port (a service's Close closes its listener).
func unregisterAll[T any](r reg.Registry[T]) {
for name := range r.GetAll() {
r.Unregister(name)
}
}
// register parses config sections and registers them into the global
// registries. Groups are processed in dependency order: leaf components
// first, then hops, chains, and finally services. This ensures that
// when a component reads its dependencies from registries during
// parsing, those dependencies are already registered.
func register(cfg *config.Config) error {
if cfg == nil {
return nil
}
// --- leaf components (no inter-group registry dependencies) ---
{
var entries []named[logger.Logger]
for _, c := range cfg.Loggers {
entries = append(entries, named[logger.Logger]{c.Name, logger_parser.ParseLogger(c)})
}
if err := registerGroup(entries, registry.LoggerRegistry()); err != nil {
return err
}
}
{
var entries []named[auth.Authenticator]
for _, c := range cfg.Authers {
entries = append(entries, named[auth.Authenticator]{c.Name, auth_parser.ParseAuther(c)})
}
if err := registerGroup(entries, registry.AutherRegistry()); err != nil {
return err
}
}
{
var entries []named[admission.Admission]
for _, c := range cfg.Admissions {
entries = append(entries, named[admission.Admission]{c.Name, admission_parser.ParseAdmission(c)})
}
if err := registerGroup(entries, registry.AdmissionRegistry()); err != nil {
return err
}
}
{
var entries []named[bypass.Bypass]
for _, c := range cfg.Bypasses {
entries = append(entries, named[bypass.Bypass]{c.Name, bypass_parser.ParseBypass(c)})
}
if err := registerGroup(entries, registry.BypassRegistry()); err != nil {
return err
}
}
{
var entries []named[resolver.Resolver]
for _, c := range cfg.Resolvers {
r, err := resolver_parser.ParseResolver(c)
if err != nil {
return err
}
entries = append(entries, named[resolver.Resolver]{c.Name, r})
}
if err := registerGroup(entries, registry.ResolverRegistry()); err != nil {
return err
}
}
{
var entries []named[hosts.HostMapper]
for _, c := range cfg.Hosts {
entries = append(entries, named[hosts.HostMapper]{c.Name, hosts_parser.ParseHostMapper(c)})
}
if err := registerGroup(entries, registry.HostsRegistry()); err != nil {
return err
}
}
{
var entries []named[ingress.Ingress]
for _, c := range cfg.Ingresses {
entries = append(entries, named[ingress.Ingress]{c.Name, ingress_parser.ParseIngress(c)})
}
if err := registerGroup(entries, registry.IngressRegistry()); err != nil {
return err
}
}
{
var entries []named[router.Router]
for _, c := range cfg.Routers {
entries = append(entries, named[router.Router]{c.Name, router_parser.ParseRouter(c)})
}
if err := registerGroup(entries, registry.RouterRegistry()); err != nil {
return err
}
}
{
var entries []named[sd.SD]
for _, c := range cfg.SDs {
entries = append(entries, named[sd.SD]{c.Name, sd_parser.ParseSD(c)})
}
if err := registerGroup(entries, registry.SDRegistry()); err != nil {
return err
}
}
{
var entries []named[observer.Observer]
for _, c := range cfg.Observers {
entries = append(entries, named[observer.Observer]{c.Name, observer_parser.ParseObserver(c)})
}
if err := registerGroup(entries, registry.ObserverRegistry()); err != nil {
return err
}
}
{
var entries []named[recorder.Recorder]
for _, c := range cfg.Recorders {
entries = append(entries, named[recorder.Recorder]{c.Name, recorder_parser.ParseRecorder(c)})
}
if err := registerGroup(entries, registry.RecorderRegistry()); err != nil {
return err
}
}
{
var entries []named[traffic.TrafficLimiter]
for _, c := range cfg.Limiters {
entries = append(entries, named[traffic.TrafficLimiter]{c.Name, limiter_parser.ParseTrafficLimiter(c)})
}
if err := registerGroup(entries, registry.TrafficLimiterRegistry()); err != nil {
return err
}
}
{
var entries []named[conn.ConnLimiter]
for _, c := range cfg.CLimiters {
entries = append(entries, named[conn.ConnLimiter]{c.Name, limiter_parser.ParseConnLimiter(c)})
}
if err := registerGroup(entries, registry.ConnLimiterRegistry()); err != nil {
return err
}
}
{
var entries []named[rate.RateLimiter]
for _, c := range cfg.RLimiters {
entries = append(entries, named[rate.RateLimiter]{c.Name, limiter_parser.ParseRateLimiter(c)})
}
if err := registerGroup(entries, registry.RateLimiterRegistry()); err != nil {
return err
}
}
// --- hops (references bypasses, resolvers, hosts from registries) ---
{
var entries []named[hop.Hop]
for _, c := range cfg.Hops {
h, err := hop_parser.ParseHop(c, logger.Default())
if err != nil {
return err
}
entries = append(entries, named[hop.Hop]{c.Name, h})
}
if err := registerGroup(entries, registry.HopRegistry()); err != nil {
return err
}
}
// --- chains (references hops from registries) ---
{
var entries []named[chain.Chainer]
for _, c := range cfg.Chains {
ch, err := chain_parser.ParseChain(c, logger.Default())
if err != nil {
return err
}
entries = append(entries, named[chain.Chainer]{c.Name, ch})
}
if err := registerGroup(entries, registry.ChainRegistry()); err != nil {
return err
}
}
// --- services (references chains, resolvers, hosts, recorders,
// limiters, observers, hops from registries) ---
//
// Services are the only component group whose construction binds a port:
// service_parser.ParseService calls listener.Init, which binds. The generic
// registerGroup "parse-all-then-swap" sequence used by the other groups
// would therefore bind every new listener while the old services are still
// listening, causing EADDRINUSE on SIGHUP reload (issue #754, regressed by
// 82e7e50). Instead, unregister all old services first — unregisterAll
// closes each one (a service implements io.Closer), freeing its port —
// then parse, bind, and register the new ones.
//
// Trade-off: a parse error in the loop below leaves the registry partially
// updated (the old services are already closed and only the services parsed
// before the failure are registered). The construct/bind split would be
// needed for atomic reload, but this restores the pre-82e7e50 behavior.
{
unregisterAll(registry.ServiceRegistry())
for _, c := range cfg.Services {
svc, err := service_parser.ParseService(c)
if err != nil {
return err
}
if svc != nil {
if err := registry.ServiceRegistry().Register(c.Name, svc); err != nil {
return err
}
}
}
}
return nil
}