Files
Archive/echo/internal/relay/server.go
2025-03-22 19:35:00 +01:00

160 lines
3.4 KiB
Go

package relay
import (
"context"
"errors"
"net"
"net/http"
"os"
"os/signal"
"sync"
"syscall"
"time"
"github.com/Ehco1996/ehco/internal/cmgr"
"github.com/Ehco1996/ehco/internal/config"
"go.uber.org/zap"
)
type Server struct {
relayM *sync.Map
cfg *config.Config
l *zap.SugaredLogger
errCH chan error // once error happen, server will exit
reloadCH chan struct{} // reload config
Cmgr cmgr.Cmgr
}
func NewServer(cfg *config.Config) (*Server, error) {
l := zap.S().Named("relay-server")
cmgrCfg := &cmgr.Config{
SyncURL: cfg.RelaySyncURL,
SyncInterval: cfg.RelaySyncInterval,
MetricsURL: cfg.GetMetricURL(),
}
cmgrCfg.Adjust()
cmgr, err := cmgr.NewCmgr(cmgrCfg)
if err != nil {
return nil, err
}
s := &Server{
cfg: cfg,
l: l,
relayM: &sync.Map{},
errCH: make(chan error, 1),
reloadCH: make(chan struct{}, 1),
Cmgr: cmgr,
}
return s, nil
}
func (s *Server) startOneRelay(ctx context.Context, r *Relay) {
s.relayM.Store(r.UniqueID(), r)
// mute closed network error for tcp server and mute http.ErrServerClosed for http server when config reload
if err := r.ListenAndServe(ctx); err != nil &&
!errors.Is(err, net.ErrClosed) && !errors.Is(err, http.ErrServerClosed) {
s.l.Errorf("start relay %s meet error: %s", r.UniqueID(), err)
s.errCH <- err
}
}
func (s *Server) stopOneRelay(r *Relay) {
_ = r.Stop()
s.relayM.Delete(r.UniqueID())
}
func (s *Server) Start(ctx context.Context) error {
// init and relay servers
for idx := range s.cfg.RelayConfigs {
r, err := NewRelay(s.cfg.RelayConfigs[idx], s.Cmgr)
if err != nil {
return err
}
go s.startOneRelay(ctx, r)
}
if s.cfg.PATH != "" && (s.cfg.ReloadInterval > 0) {
s.l.Infof("Start to watch relay config %s ", s.cfg.PATH)
go s.WatchAndReload(ctx)
}
// start Cmgr when need sync from server
if s.cfg.NeedStartCmgr() {
go s.Cmgr.Start(ctx, s.errCH)
}
select {
case err := <-s.errCH:
s.l.Errorf("meet error: %s exit now.", err)
return err
case <-ctx.Done():
s.l.Info("ctx cancelled start to stop all relay servers")
return s.Stop()
}
}
func (s *Server) Stop() error {
var err error
s.relayM.Range(func(key, value interface{}) bool {
r := value.(*Relay)
if e := r.Stop(); e != nil {
err = errors.Join(err, e)
}
return true
})
return err
}
func (s *Server) TriggerReload() {
s.reloadCH <- struct{}{}
}
func (s *Server) WatchAndReload(ctx context.Context) {
go s.TriggerReloadBySignal(ctx)
go s.triggerReloadByTicker(ctx)
for {
select {
case <-ctx.Done():
return
case <-s.reloadCH:
if err := s.Reload(false); err != nil {
s.l.Errorf("auto reloading relay conf meet error: %s will retry in next loop", err)
}
}
}
}
func (s *Server) triggerReloadByTicker(ctx context.Context) {
if s.cfg.ReloadInterval > 0 {
ticker := time.NewTicker(time.Second * time.Duration(s.cfg.ReloadInterval))
defer ticker.Stop()
for {
select {
case <-ctx.Done():
return
case <-ticker.C:
s.l.Warn("Trigger Reloading Relay Conf By ticker! ")
s.TriggerReload()
}
}
}
}
func (s *Server) TriggerReloadBySignal(ctx context.Context) {
// listen syscall.SIGHUP to trigger reload
sigHubCH := make(chan os.Signal, 1)
signal.Notify(sigHubCH, syscall.SIGHUP)
for {
select {
case <-ctx.Done():
return
case <-sigHubCH:
s.l.Warn("Trigger Reloading Relay Conf By HUP Signal! ")
s.TriggerReload()
}
}
}