package muxer import ( "context" "net" "strings" "sync/atomic" "time" "github.com/multiformats/go-multiaddr" "github.com/nspcc-dev/neofs-node/pkg/network" "github.com/soheilhy/cmux" "github.com/valyala/fasthttp" "go.uber.org/zap" "google.golang.org/grpc" ) type ( // StoreParams groups the parameters of network connections muxer constructor. Params struct { Logger *zap.Logger API *fasthttp.Server Address multiaddr.Multiaddr ShutdownTTL time.Duration P2P *grpc.Server } // Mux is an interface of network connections muxer. Mux interface { Start(ctx context.Context) Stop() } muxer struct { maddr multiaddr.Multiaddr run *int32 lis net.Listener log *zap.Logger ttl time.Duration p2p *grpc.Server api *fasthttp.Server done chan struct{} } ) const ( // we close listener, that's why we ignore this errors errClosedConnection = "use of closed network connection" errMuxListenerClose = "mux: listener closed" errHTTPServerClosed = "http: Server closed" ) var ( ignoredErrors = []string{ errClosedConnection, errMuxListenerClose, errHTTPServerClosed, } ) // New constructs network connections muxer and returns Mux interface. func New(p Params) Mux { return &muxer{ maddr: p.Address, ttl: p.ShutdownTTL, run: new(int32), api: p.API, p2p: p.P2P, log: p.Logger, done: make(chan struct{}), } } func needCatch(err error) bool { if err == nil || containsErr(err) { return false } return true } func containsErr(err error) bool { for _, msg := range ignoredErrors { if strings.Contains(err.Error(), msg) { return true } } return false } func (m *muxer) Start(ctx context.Context) { var err error // if already started - ignore if !atomic.CompareAndSwapInt32(m.run, 0, 1) { m.log.Warn("already started") return } else if m.lis != nil { m.log.Info("try close old listener") if err = m.lis.Close(); err != nil { m.log.Fatal("could not close old listener", zap.Error(err)) } } if m.lis, err = network.Listen(m.maddr); err != nil { m.log.Fatal("could not close old listener", zap.Error(err)) } mux := cmux.New(m.lis) mux.HandleError(func(e error) bool { if needCatch(e) { m.log.Error("error-handler: something went wrong", zap.Error(e)) } return true }) // trpcL := mux.Match(cmux.Any()) // Any means anything that is not yet matched. hLis := mux.Match(cmux.HTTP1Fast()) gLis := mux.Match(cmux.HTTP2()) pLis := mux.Match(cmux.Any()) m.log.Debug("delay context worker") go func() { <-ctx.Done() m.Stop() }() m.log.Debug("delay tcp") go func() { m.log.Debug("tcp: serve") loop: for { select { case <-ctx.Done(): break loop default: } con, err := pLis.Accept() if err != nil { break loop } _ = con.Close() } m.log.Debug("tcp: stopped") }() m.log.Debug("delay p2p") go func() { if m.p2p == nil { m.log.Info("p2p: service is empty") return } m.log.Debug("p2p: serve") if err := m.p2p.Serve(gLis); needCatch(err) { m.log.Error("p2p: something went wrong", zap.Error(err)) } m.log.Debug("p2p: stopped") }() m.log.Debug("delay api") go func() { if m.api == nil { m.log.Info("api: service is empty") return } m.log.Debug("api: serve") if err := m.api.Serve(hLis); needCatch(err) { m.log.Error("rpc: something went wrong", zap.Error(err)) } m.log.Debug("rpc: stopped") }() m.log.Debug("delay serve") go func() { defer func() { close(m.done) }() m.log.Debug("mux: serve") if err := mux.Serve(); needCatch(err) { m.log.Fatal("mux: something went wrong", zap.Error(err)) } m.log.Debug("mux: stopped") }() } func (m *muxer) Stop() { if !atomic.CompareAndSwapInt32(m.run, 1, 0) { m.log.Warn("already stopped") return } if err := m.lis.Close(); err != nil { m.log.Error("could not close connection", zap.Error(err)) } m.log.Debug("lis: close ok") <-m.done // muxer stopped if m.api != nil { if err := m.api.Shutdown(); needCatch(err) { m.log.Error("api: could not shutdown", zap.Error(err)) } m.log.Debug("api: shutdown ok") } if m.p2p != nil { m.p2p.GracefulStop() m.log.Debug("p2p: shutdown ok") } }