github.com/iotexproject/iotex-core@v1.14.1-rc1/server/itx/server.go (about) 1 // Copyright (c) 2022 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package itx 7 8 import ( 9 "context" 10 "fmt" 11 "net/http" 12 "net/http/pprof" 13 "runtime" 14 "sync" 15 16 "github.com/pkg/errors" 17 "go.uber.org/zap" 18 19 "github.com/iotexproject/iotex-core/api" 20 "github.com/iotexproject/iotex-core/chainservice" 21 "github.com/iotexproject/iotex-core/config" 22 "github.com/iotexproject/iotex-core/dispatcher" 23 "github.com/iotexproject/iotex-core/p2p" 24 "github.com/iotexproject/iotex-core/pkg/ha" 25 "github.com/iotexproject/iotex-core/pkg/log" 26 "github.com/iotexproject/iotex-core/pkg/probe" 27 "github.com/iotexproject/iotex-core/pkg/routine" 28 "github.com/iotexproject/iotex-core/pkg/util/httputil" 29 "github.com/iotexproject/iotex-core/server/itx/nodestats" 30 ) 31 32 // Server is the iotex server instance containing all components. 33 type Server struct { 34 cfg config.Config 35 rootChainService *chainservice.ChainService 36 chainservices map[uint32]*chainservice.ChainService 37 apiServers map[uint32]*api.ServerV2 38 p2pAgent p2p.Agent 39 dispatcher dispatcher.Dispatcher 40 nodeStats *nodestats.NodeStats 41 initializedSubChains map[uint32]bool 42 mutex sync.RWMutex 43 subModuleCancel context.CancelFunc 44 } 45 46 // NewServer creates a new server 47 // TODO clean up config, make root config contains network, dispatch and chainservice 48 func NewServer(cfg config.Config) (*Server, error) { 49 return newServer(cfg, false) 50 } 51 52 // NewInMemTestServer creates a test server in memory 53 func NewInMemTestServer(cfg config.Config) (*Server, error) { // notest 54 return newServer(cfg, true) 55 } 56 57 func newServer(cfg config.Config, testing bool) (*Server, error) { 58 // create dispatcher instance 59 dispatcher, err := dispatcher.NewDispatcher(cfg.Dispatcher) 60 if err != nil { 61 return nil, errors.Wrap(err, "fail to create dispatcher") 62 } 63 var p2pAgent p2p.Agent 64 switch cfg.Consensus.Scheme { 65 case config.StandaloneScheme: 66 p2pAgent = p2p.NewDummyAgent() 67 default: 68 p2pAgent = p2p.NewAgent(cfg.Network, cfg.Chain.ID, cfg.Genesis.Hash(), dispatcher.HandleBroadcast, dispatcher.HandleTell) 69 } 70 chains := make(map[uint32]*chainservice.ChainService) 71 apiServers := make(map[uint32]*api.ServerV2) 72 var cs *chainservice.ChainService 73 builder := chainservice.NewBuilder(cfg) 74 builder.SetP2PAgent(p2pAgent) 75 rpcStats := nodestats.NewAPILocalStats() 76 builder.SetRPCStats(rpcStats) 77 if testing { 78 cs, err = builder.BuildForTest() 79 } else { 80 cs, err = builder.Build() 81 } 82 if err != nil { 83 return nil, errors.Wrap(err, "fail to create chain service") 84 } 85 nodeStats := nodestats.NewNodeStats(rpcStats, cs.BlockSync(), p2pAgent) 86 apiServer, err := cs.NewAPIServer(cfg.API, cfg.Plugins) 87 if err != nil { 88 return nil, errors.Wrap(err, "failed to create api server") 89 } 90 if apiServer != nil { 91 apiServers[cs.ChainID()] = apiServer 92 if err := cs.Blockchain().AddSubscriber(apiServer); err != nil { 93 return nil, errors.Wrap(err, "failed to add api server as subscriber") 94 } 95 } 96 // TODO: explorer dependency deleted here at #1085, need to revive by migrating to api 97 chains[cs.ChainID()] = cs 98 dispatcher.AddSubscriber(cs.ChainID(), cs) 99 svr := Server{ 100 cfg: cfg, 101 p2pAgent: p2pAgent, 102 dispatcher: dispatcher, 103 rootChainService: cs, 104 chainservices: chains, 105 apiServers: apiServers, 106 nodeStats: nodeStats, 107 initializedSubChains: map[uint32]bool{}, 108 } 109 // Setup sub-chain starter 110 // TODO: sub-chain infra should use main-chain API instead of protocol directly 111 return &svr, nil 112 } 113 114 // Start starts the server 115 func (s *Server) Start(ctx context.Context) error { 116 cctx, cancel := context.WithCancel(ctx) 117 s.subModuleCancel = cancel 118 for id, cs := range s.chainservices { 119 if err := cs.Start(cctx); err != nil { 120 return errors.Wrap(err, "error when starting blockchain") 121 } 122 if as, ok := s.apiServers[id]; ok { 123 if err := as.Start(cctx); err != nil { 124 return errors.Wrapf(err, "failed to start api server for chain %d", id) 125 } 126 } 127 } 128 if err := s.p2pAgent.Start(cctx); err != nil { 129 return errors.Wrap(err, "error when starting P2P agent") 130 } 131 if err := s.dispatcher.Start(cctx); err != nil { 132 return errors.Wrap(err, "error when starting dispatcher") 133 } 134 if err := s.nodeStats.Start(cctx); err != nil { 135 return errors.Wrap(err, "error when starting node stats") 136 } 137 return nil 138 } 139 140 // Stop stops the server 141 func (s *Server) Stop(ctx context.Context) error { 142 defer s.subModuleCancel() 143 if err := s.nodeStats.Stop(ctx); err != nil { 144 return errors.Wrap(err, "error when stopping node stats") 145 } 146 if err := s.p2pAgent.Stop(ctx); err != nil { 147 // notest 148 return errors.Wrap(err, "error when stopping P2P agent") 149 } 150 if err := s.dispatcher.Stop(ctx); err != nil { 151 // notest 152 return errors.Wrap(err, "error when stopping dispatcher") 153 } 154 for id, cs := range s.chainservices { 155 if as, ok := s.apiServers[id]; ok { 156 if err := as.Stop(ctx); err != nil { 157 return errors.Wrapf(err, "error when stopping api server") 158 } 159 } 160 if err := cs.Stop(ctx); err != nil { 161 return errors.Wrap(err, "error when stopping blockchain") 162 } 163 } 164 return nil 165 } 166 167 // NewSubChainService creates a new chain service in this server. 168 func (s *Server) NewSubChainService(cfg config.Config) error { 169 s.mutex.Lock() 170 defer s.mutex.Unlock() 171 // TODO: explorer dependency deleted here at #1085, need to revive by migrating to api 172 builder := chainservice.NewBuilder(cfg) 173 cs, err := builder.SetP2PAgent(s.p2pAgent).BuildForSubChain() 174 if err != nil { 175 return err 176 } 177 s.chainservices[cs.ChainID()] = cs 178 return nil 179 } 180 181 // StopChainService stops the chain service run in the server. 182 func (s *Server) StopChainService(ctx context.Context, id uint32) error { 183 s.mutex.RLock() 184 defer s.mutex.RUnlock() 185 c, ok := s.chainservices[id] 186 if !ok { 187 return errors.New("Chain ID does not match any existing chains") 188 } 189 return c.Stop(ctx) 190 } 191 192 // Config returns the server's config 193 func (s *Server) Config() config.Config { 194 s.mutex.RLock() 195 defer s.mutex.RUnlock() 196 return s.cfg 197 } 198 199 // P2PAgent returns the P2P agent 200 func (s *Server) P2PAgent() p2p.Agent { 201 return s.p2pAgent 202 } 203 204 // ChainService returns the chainservice hold in Server with given id. 205 func (s *Server) ChainService(id uint32) *chainservice.ChainService { 206 s.mutex.RLock() 207 defer s.mutex.RUnlock() 208 return s.chainservices[id] 209 } 210 211 // APIServer returns the API Server hold in Server with given id. 212 func (s *Server) APIServer(id uint32) *api.ServerV2 { 213 s.mutex.RLock() 214 defer s.mutex.RUnlock() 215 return s.apiServers[id] 216 } 217 218 // Dispatcher returns the Dispatcher 219 func (s *Server) Dispatcher() dispatcher.Dispatcher { 220 return s.dispatcher 221 } 222 223 // StartServer starts a node server 224 func StartServer(ctx context.Context, svr *Server, probeSvr *probe.Server, cfg config.Config) { 225 if err := svr.Start(ctx); err != nil { 226 log.L().Fatal("Failed to start server.", zap.Error(err)) 227 return 228 } 229 defer func() { 230 if err := svr.Stop(context.Background()); err != nil { 231 log.L().Panic("Failed to stop server.", zap.Error(err)) 232 } 233 }() 234 if err := probeSvr.TurnOn(); err != nil { 235 log.L().Panic("Failed to turn on probe server.", zap.Error(err)) 236 } 237 238 if cfg.System.HeartbeatInterval > 0 { 239 task := routine.NewRecurringTask(NewHeartbeatHandler(svr, cfg.Network).Log, cfg.System.HeartbeatInterval) 240 if err := task.Start(ctx); err != nil { 241 log.L().Panic("Failed to start heartbeat routine.", zap.Error(err)) 242 } 243 defer func() { 244 if err := task.Stop(ctx); err != nil { 245 log.L().Panic("Failed to stop heartbeat routine.", zap.Error(err)) 246 } 247 }() 248 } 249 250 var adminserv http.Server 251 if cfg.System.HTTPAdminPort > 0 { 252 mux := http.NewServeMux() 253 log.RegisterLevelConfigMux(mux) 254 haCtl := ha.New(svr.rootChainService.Consensus()) 255 mux.Handle("/ha", http.HandlerFunc(haCtl.Handle)) 256 mux.Handle("/debug/pprof/", http.HandlerFunc(pprof.Index)) 257 mux.Handle("/debug/pprof/cmdline", http.HandlerFunc(pprof.Cmdline)) 258 mux.Handle("/debug/pprof/profile", http.HandlerFunc(pprof.Profile)) 259 mux.Handle("/debug/pprof/symbol", http.HandlerFunc(pprof.Symbol)) 260 mux.Handle("/debug/pprof/trace", http.HandlerFunc(pprof.Trace)) 261 262 port := fmt.Sprintf(":%d", cfg.System.HTTPAdminPort) 263 adminserv = httputil.NewServer(port, mux) 264 defer func() { 265 if err := adminserv.Shutdown(ctx); err != nil { 266 log.L().Error("Error when serving metrics data.", zap.Error(err)) 267 } 268 }() 269 go func() { 270 runtime.SetMutexProfileFraction(1) 271 runtime.SetBlockProfileRate(1) 272 ln, err := httputil.LimitListener(adminserv.Addr) 273 if err != nil { 274 log.L().Error("Error when listen to profiling port.", zap.Error(err)) 275 return 276 } 277 if err := adminserv.Serve(ln); err != nil { 278 log.L().Error("Error when serving performance profiling data.", zap.Error(err)) 279 } 280 }() 281 } 282 283 <-ctx.Done() 284 if err := probeSvr.TurnOff(); err != nil { 285 log.L().Panic("Failed to turn off probe server.", zap.Error(err)) 286 } 287 }