From 6f9fb9d1da2f4575eeb87a5d056991d45d8dcba8 Mon Sep 17 00:00:00 2001 From: JukLee0ira Date: Mon, 18 Nov 2024 12:03:32 +0800 Subject: [PATCH] node, p2p/simulations: fix node.Node AccountsManager leak (#19004) --- cmd/XDC/chaincmd.go | 16 +++++++++++++++- cmd/XDC/consolecmd.go | 4 ++-- cmd/XDC/main.go | 1 + cmd/faucet/faucet.go | 2 +- console/console_test.go | 4 ++-- node/config_test.go | 18 +++++++++++++++--- node/node.go | 23 +++++++++++++++++++++++ node/node_example_test.go | 10 ++++++---- node/node_test.go | 24 ++++++++++++++++++++++++ node/service_test.go | 1 + p2p/simulations/adapters/inproc.go | 6 ++++++ p2p/simulations/network.go | 11 +++++++++-- 12 files changed, 105 insertions(+), 15 deletions(-) diff --git a/cmd/XDC/chaincmd.go b/cmd/XDC/chaincmd.go index ae4221e4b2..54795af0db 100644 --- a/cmd/XDC/chaincmd.go +++ b/cmd/XDC/chaincmd.go @@ -203,6 +203,8 @@ func initGenesis(ctx *cli.Context) error { // Open an initialise both full and light databases stack, _ := makeFullNode(ctx) + defer stack.Close() + for _, name := range []string{"chaindata", "lightchaindata"} { chaindb, err := stack.OpenDatabase(name, 0, 0, "") if err != nil { @@ -228,6 +230,8 @@ func importChain(ctx *cli.Context) error { go metrics.CollectProcessMetrics(3 * time.Second) stack, _ := makeFullNode(ctx) + defer stack.Close() + chain, chainDb := utils.MakeChain(ctx, stack) defer chainDb.Close() @@ -319,6 +323,8 @@ func exportChain(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack, _ := makeFullNode(ctx) + defer stack.Close() + chain, db := utils.MakeChain(ctx, stack) defer db.Close() start := time.Now() @@ -353,6 +359,8 @@ func importPreimages(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack, _ := makeFullNode(ctx) + defer stack.Close() + diskdb := utils.MakeChainDatabase(ctx, stack) defer diskdb.Close() @@ -370,6 +378,8 @@ func exportPreimages(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack, _ := makeFullNode(ctx) + defer stack.Close() + diskdb := utils.MakeChainDatabase(ctx, stack) defer diskdb.Close() @@ -388,6 +398,8 @@ func copyDb(ctx *cli.Context) error { } // Initialize a new chain for the running node to sync into stack, _ := makeFullNode(ctx) + defer stack.Close() + chain, chainDb := utils.MakeChain(ctx, stack) defer chainDb.Close() @@ -461,9 +473,11 @@ func removeDB(ctx *cli.Context) error { func dump(ctx *cli.Context) error { stack, _ := makeFullNode(ctx) + defer stack.Close() + chain, chainDb := utils.MakeChain(ctx, stack) defer chainDb.Close() - + for _, arg := range ctx.Args() { var block *types.Block if hashish(arg) { diff --git a/cmd/XDC/consolecmd.go b/cmd/XDC/consolecmd.go index e36dda489c..c9bf6b2982 100644 --- a/cmd/XDC/consolecmd.go +++ b/cmd/XDC/consolecmd.go @@ -79,7 +79,7 @@ func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags node, cfg := makeFullNode(ctx) startNode(ctx, node, cfg) - defer node.Stop() + defer node.Close() // Attach to the newly started node and start the JavaScript console client, err := node.Attach() @@ -181,7 +181,7 @@ func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags node, cfg := makeFullNode(ctx) startNode(ctx, node, cfg) - defer node.Stop() + defer node.Close() // Attach to the newly started node and start the JavaScript console client, err := node.Attach() diff --git a/cmd/XDC/main.go b/cmd/XDC/main.go index 57c77e6f29..dcf2613ab9 100644 --- a/cmd/XDC/main.go +++ b/cmd/XDC/main.go @@ -226,6 +226,7 @@ func main() { // blocking mode, waiting for it to be shut down. func XDC(ctx *cli.Context) error { node, cfg := makeFullNode(ctx) + defer node.Close() startNode(ctx, node, cfg) node.Wait() return nil diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index dcee12c32b..0d94389243 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -287,7 +287,7 @@ func newFaucet(genesis *core.Genesis, port int, enodes []*discv5.Node, network u // close terminates the Ethereum connection and tears down the faucet. func (f *faucet) close() error { - return f.stack.Stop() + return f.stack.Close() } // listenAndServe registers the HTTP handlers for the faucet and boots it up diff --git a/console/console_test.go b/console/console_test.go index 9433026db8..0d5c94a754 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -153,8 +153,8 @@ func (env *tester) Close(t *testing.T) { if err := env.console.Stop(false); err != nil { t.Errorf("failed to stop embedded console: %v", err) } - if err := env.stack.Stop(); err != nil { - t.Errorf("failed to stop embedded node: %v", err) + if err := env.stack.Close(); err != nil { + t.Errorf("failed to tear down embedded node: %v", err) } os.RemoveAll(env.workspace) } diff --git a/node/config_test.go b/node/config_test.go index 8c187323b3..321a7bad6e 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -37,14 +37,22 @@ func TestDatadirCreation(t *testing.T) { } defer os.RemoveAll(dir) - if _, err := New(&Config{DataDir: dir}); err != nil { + node, err := New(&Config{DataDir: dir}) + if err != nil { t.Fatalf("failed to create stack with existing datadir: %v", err) } + if err := node.Close(); err != nil { + t.Fatalf("failed to close node: %v", err) + } // Generate a long non-existing datadir path and check that it gets created by a node dir = filepath.Join(dir, "a", "b", "c", "d", "e", "f") - if _, err := New(&Config{DataDir: dir}); err != nil { + node, err = New(&Config{DataDir: dir}) + if err != nil { t.Fatalf("failed to create stack with creatable datadir: %v", err) } + if err := node.Close(); err != nil { + t.Fatalf("failed to close node: %v", err) + } if _, err := os.Stat(dir); err != nil { t.Fatalf("freshly created datadir not accessible: %v", err) } @@ -56,8 +64,12 @@ func TestDatadirCreation(t *testing.T) { defer os.Remove(file.Name()) dir = filepath.Join(file.Name(), "invalid/path") - if _, err := New(&Config{DataDir: dir}); err == nil { + node, err = New(&Config{DataDir: dir}) + if err == nil { t.Fatalf("protocol stack created with an invalid datadir") + if err := node.Close(); err != nil { + t.Fatalf("failed to close node: %v", err) + } } } diff --git a/node/node.go b/node/node.go index dfa261640c..40cef4bab3 100644 --- a/node/node.go +++ b/node/node.go @@ -131,6 +131,29 @@ func New(conf *Config) (*Node, error) { }, nil } +// Close stops the Node and releases resources acquired in +// Node constructor New. +func (n *Node) Close() error { + var errs []error + + // Terminate all subsystems and collect any errors + if err := n.Stop(); err != nil && err != ErrNodeStopped { + errs = append(errs, err) + } + if err := n.accman.Close(); err != nil { + errs = append(errs, err) + } + // Report any errors that might have occurred + switch len(errs) { + case 0: + return nil + case 1: + return errs[0] + default: + return fmt.Errorf("%v", errs) + } +} + // Register injects a new service into the node's stack. The service created by // the passed constructor must be unique in its type with regard to sibling ones. func (n *Node) Register(constructor ServiceConstructor) error { diff --git a/node/node_example_test.go b/node/node_example_test.go index f882673718..b2ad1a8ed3 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -29,10 +29,10 @@ import ( // life cycle management. // // The following methods are needed to implement a node.Service: -// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on -// - APIs() []rpc.API - api methods the service wants to expose on rpc channels -// - Start() error - method invoked when the node is ready to start the service -// - Stop() error - method invoked when the node terminates the service +// - Protocols() []p2p.Protocol - devp2p protocols the service can communicate on +// - APIs() []rpc.API - api methods the service wants to expose on rpc channels +// - Start() error - method invoked when the node is ready to start the service +// - Stop() error - method invoked when the node terminates the service type SampleService struct{} func (s *SampleService) Protocols() []p2p.Protocol { return nil } @@ -47,6 +47,8 @@ func ExampleService() { if err != nil { log.Fatalf("Failed to create network node: %v", err) } + defer stack.Close() + // Create and register a simple network service. This is done through the definition // of a node.ServiceConstructor that will instantiate a node.Service. The reason for // the factory method approach is to support service restarts without relying on the diff --git a/node/node_test.go b/node/node_test.go index 4b4eec464d..df97c66c71 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -47,6 +47,8 @@ func TestNodeLifeCycle(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Ensure that a stopped node can be stopped again for i := 0; i < 3; i++ { if err := stack.Stop(); err != ErrNodeStopped { @@ -89,6 +91,8 @@ func TestNodeUsedDataDir(t *testing.T) { if err != nil { t.Fatalf("failed to create original protocol stack: %v", err) } + defer original.Close() + if err := original.Start(); err != nil { t.Fatalf("failed to start original protocol stack: %v", err) } @@ -99,6 +103,8 @@ func TestNodeUsedDataDir(t *testing.T) { if err != nil { t.Fatalf("failed to create duplicate protocol stack: %v", err) } + defer duplicate.Close() + if err := duplicate.Start(); err != ErrDatadirUsed { t.Fatalf("duplicate datadir failure mismatch: have %v, want %v", err, ErrDatadirUsed) } @@ -110,6 +116,8 @@ func TestServiceRegistry(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Register a batch of unique services and ensure they start successfully services := []ServiceConstructor{NewNoopServiceA, NewNoopServiceB, NewNoopServiceC} for i, constructor := range services { @@ -142,6 +150,8 @@ func TestServiceLifeCycle(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Register a batch of life-cycle instrumented services services := map[string]InstrumentingWrapper{ "A": InstrumentedServiceMakerA, @@ -192,6 +202,8 @@ func TestServiceRestarts(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Define a service that does not support restarts var ( running bool @@ -240,6 +252,8 @@ func TestServiceConstructionAbortion(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Define a batch of good services services := map[string]InstrumentingWrapper{ "A": InstrumentedServiceMakerA, @@ -287,6 +301,8 @@ func TestServiceStartupAbortion(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Register a batch of good services services := map[string]InstrumentingWrapper{ "A": InstrumentedServiceMakerA, @@ -340,6 +356,8 @@ func TestServiceTerminationGuarantee(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Register a batch of good services services := map[string]InstrumentingWrapper{ "A": InstrumentedServiceMakerA, @@ -415,6 +433,8 @@ func TestServiceRetrieval(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + if err := stack.Register(NewNoopService); err != nil { t.Fatalf("noop service registration failed: %v", err) } @@ -450,6 +470,8 @@ func TestProtocolGather(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Register a batch of services with some configured number of protocols services := map[string]struct { Count int @@ -506,6 +528,8 @@ func TestAPIGather(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() + // Register a batch of services with some configured APIs calls := make(chan string, 1) makeAPI := func(result string) *OneMethodAPI { diff --git a/node/service_test.go b/node/service_test.go index 23dc807084..1c275e2ecb 100644 --- a/node/service_test.go +++ b/node/service_test.go @@ -67,6 +67,7 @@ func TestContextServices(t *testing.T) { if err != nil { t.Fatalf("failed to create protocol stack: %v", err) } + defer stack.Close() // Define a verifier that ensures a NoopA is before it and NoopB after verifier := func(ctx *ServiceContext) (Service, error) { var objA *NoopServiceA diff --git a/p2p/simulations/adapters/inproc.go b/p2p/simulations/adapters/inproc.go index 1f64941dfa..4363e5eaab 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -160,6 +160,12 @@ type SimNode struct { connected map[discover.NodeID]bool } +// Close closes the underlaying node.Node to release +// acquired resources. +func (sn *SimNode) Close() error { + return sn.node.Close() +} + // Addr returns the node's discovery address func (sn *SimNode) Addr() []byte { return []byte(sn.Node().String()) diff --git a/p2p/simulations/network.go b/p2p/simulations/network.go index 4c992fd1d5..2c4a3d5a3f 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -21,6 +21,7 @@ import ( "context" "encoding/json" "fmt" + "io" "sync" "time" @@ -497,12 +498,18 @@ func (net *Network) Shutdown() { if err := node.Stop(); err != nil { log.Warn(fmt.Sprintf("error stopping node %s", node.ID().TerminalString()), "err", err) } + // If the node has the close method, call it. + if closer, ok := node.Node.(io.Closer); ok { + if err := closer.Close(); err != nil { + log.Warn("Can't close node", "id", node.ID(), "err", err) + } + } } close(net.quitc) } -//Reset resets all network properties: -//emtpies the nodes and the connection list +// Reset resets all network properties: +// emtpies the nodes and the connection list func (net *Network) Reset() { net.lock.Lock() defer net.lock.Unlock()