Files
RemLink/internal/control/supervisor.go
T
qsc20001102 142e5dc7d6
ci / Go checks (ubuntu-latest) (push) Has been cancelled
ci / Go checks (windows-latest) (push) Has been cancelled
初版功能完成
2026-08-29 13:12:17 +08:00

135 lines
3.2 KiB
Go

package control
import (
"context"
"errors"
"fmt"
"net"
"net/http"
"net/netip"
"strconv"
"sync"
"time"
)
// Supervisor supports the required live Control-listener move during an
// Overlay network migration.
type Supervisor struct {
mu sync.Mutex
handler http.Handler
port int
ctx context.Context
server *http.Server
listener net.Listener
errors chan error
closed bool
}
func NewSupervisor(handler http.Handler, port int) (*Supervisor, error) {
if handler == nil || port < 1 || port > 65535 {
return nil, errors.New("Control Supervisor requires Handler and valid port")
}
return &Supervisor{handler: handler, port: port, errors: make(chan error, 1)}, nil
}
func (s *Supervisor) Start(ctx context.Context, address netip.Addr) error {
if !address.Is4() {
return errors.New("Control listener address must be IPv4")
}
s.mu.Lock()
defer s.mu.Unlock()
if s.server != nil {
return errors.New("Control Supervisor already started")
}
s.ctx = ctx
server, listener, err := s.open(address)
if err != nil {
return err
}
s.server, s.listener = server, listener
s.serve(server, listener)
return nil
}
func (s *Supervisor) Rebind(address netip.Addr) error {
if !address.Is4() {
return errors.New("Control listener address must be IPv4")
}
s.mu.Lock()
defer s.mu.Unlock()
if s.closed || s.server == nil {
return errors.New("Control Supervisor is not running")
}
newServer, newListener, err := s.open(address)
if err != nil {
return err
}
oldServer := s.server
oldListener := s.listener
s.server, s.listener = newServer, newListener
s.serve(newServer, newListener)
// From this point the rebind is committed and callers may safely switch the
// rest of the Overlay. An old-server close failure must not be reported as
// if the new listener were absent; that would cause the caller to roll back
// wg0 while this Supervisor remained bound to the new address.
_ = oldServer.Close()
_ = oldListener.Close()
return nil
}
func (s *Supervisor) Wait(ctx context.Context) error {
select {
case <-ctx.Done():
return ctx.Err()
case err := <-s.errors:
return err
}
}
func (s *Supervisor) Close() error {
s.mu.Lock()
if s.closed {
s.mu.Unlock()
return nil
}
s.closed = true
server := s.server
s.mu.Unlock()
if server == nil {
return nil
}
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
_ = server.Close()
return err
}
return nil
}
func (s *Supervisor) open(address netip.Addr) (*http.Server, net.Listener, error) {
listenAddress := net.JoinHostPort(address.String(), strconv.Itoa(s.port))
listener, err := net.Listen("tcp4", listenAddress)
if err != nil {
return nil, nil, fmt.Errorf("listen for Overlay Control on %s: %w", listenAddress, err)
}
server := &http.Server{
Addr: listenAddress, Handler: s.handler, ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 0, WriteTimeout: 0, IdleTimeout: 0, MaxHeaderBytes: 1 << 20,
}
return server, listener, nil
}
func (s *Supervisor) serve(server *http.Server, listener net.Listener) {
go func() {
err := server.Serve(listener)
if errors.Is(err, http.ErrServerClosed) || errors.Is(err, net.ErrClosed) {
return
}
select {
case s.errors <- err:
default:
}
}()
}