diff --git a/cmd/faucet/faucet.go b/cmd/faucet/faucet.go index a7c20db7732b9b3e7c39dab8b1ff43194da2e5a7..5e4b77a6a543aeb60db56286623b0ece0abd1259 100644 --- a/cmd/faucet/faucet.go +++ b/cmd/faucet/faucet.go @@ -282,7 +282,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/cmd/geth/chaincmd.go b/cmd/geth/chaincmd.go index 562c7e0deee0c095b9f4fbb3b639b386e624dbc1..6d41261f8017cefa3b39315a52ec831870f20d5c 100644 --- a/cmd/geth/chaincmd.go +++ b/cmd/geth/chaincmd.go @@ -190,6 +190,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 { @@ -209,6 +211,8 @@ func importChain(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) + defer stack.Close() + chain, chainDb := utils.MakeChain(ctx, stack) defer chainDb.Close() @@ -303,6 +307,8 @@ func exportChain(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) + defer stack.Close() + chain, _ := utils.MakeChain(ctx, stack) start := time.Now() @@ -336,9 +342,11 @@ func importPreimages(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) - diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase) + defer stack.Close() + diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase) start := time.Now() + if err := utils.ImportPreimages(diskdb, ctx.Args().First()); err != nil { utils.Fatalf("Import error: %v\n", err) } @@ -352,9 +360,11 @@ func exportPreimages(ctx *cli.Context) error { utils.Fatalf("This command requires an argument.") } stack := makeFullNode(ctx) - diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase) + defer stack.Close() + diskdb := utils.MakeChainDatabase(ctx, stack).(*ethdb.LDBDatabase) start := time.Now() + if err := utils.ExportPreimages(diskdb, ctx.Args().First()); err != nil { utils.Fatalf("Export error: %v\n", err) } @@ -369,8 +379,9 @@ func copyDb(ctx *cli.Context) error { } // Initialize a new chain for the running node to sync into stack := makeFullNode(ctx) - chain, chainDb := utils.MakeChain(ctx, stack) + defer stack.Close() + chain, chainDb := utils.MakeChain(ctx, stack) syncmode := *utils.GlobalTextMarshaler(ctx, utils.SyncModeFlag.Name).(*downloader.SyncMode) dl := downloader.New(syncmode, chainDb, new(event.TypeMux), chain, nil, nil) @@ -441,6 +452,8 @@ func removeDB(ctx *cli.Context) error { func dump(ctx *cli.Context) error { stack := makeFullNode(ctx) + defer stack.Close() + chain, chainDb := utils.MakeChain(ctx, stack) for _, arg := range ctx.Args() { var block *types.Block diff --git a/cmd/geth/consolecmd.go b/cmd/geth/consolecmd.go index 2500a969cf5ee68b6c8703073ed1caa6712db8c5..d4443b328295dbc07381b422ca7f71bce1d711e0 100644 --- a/cmd/geth/consolecmd.go +++ b/cmd/geth/consolecmd.go @@ -79,7 +79,7 @@ func localConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags node := makeFullNode(ctx) startNode(ctx, node) - defer node.Stop() + defer node.Close() // Attach to the newly started node and start the JavaScript console client, err := node.Attach() @@ -180,7 +180,7 @@ func ephemeralConsole(ctx *cli.Context) error { // Create and start the node based on the CLI flags node := makeFullNode(ctx) startNode(ctx, node) - 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/geth/main.go b/cmd/geth/main.go index dca02c82e4595f4f01f79078328cb33f1b26d920..17bf438e2a5359f7c748b4dbf94a97c424f73fb9 100644 --- a/cmd/geth/main.go +++ b/cmd/geth/main.go @@ -279,6 +279,7 @@ func geth(ctx *cli.Context) error { return fmt.Errorf("invalid command: %q", args[0]) } node := makeFullNode(ctx) + defer node.Close() startNode(ctx, node) node.Wait() return nil diff --git a/cmd/swarm/main.go b/cmd/swarm/main.go index 53888b615f96644868f83b095d5582bee8dca063..11b51124d0ff699ac10674ac0ce2b15ba78a132d 100644 --- a/cmd/swarm/main.go +++ b/cmd/swarm/main.go @@ -288,6 +288,7 @@ func bzzd(ctx *cli.Context) error { if err != nil { utils.Fatalf("can't create node: %v", err) } + defer stack.Close() //a few steps need to be done after the config phase is completed, //due to overriding behavior @@ -365,6 +366,8 @@ func getPrivKey(ctx *cli.Context) *ecdsa.PrivateKey { if err != nil { utils.Fatalf("can't create node: %v", err) } + defer stack.Close() + return getAccount(bzzconfig.BzzAccount, ctx, stack) } diff --git a/console/console_test.go b/console/console_test.go index 26465ca6f4502ee083cc850a2b6fe92f2bc3f117..55d799725a3f69f25461f6b26b3971ab8153b1f6 100644 --- a/console/console_test.go +++ b/console/console_test.go @@ -149,8 +149,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/miner/stress_clique.go b/miner/stress_clique.go index 7e19975aec2ff5536e81e403e08b8544ab52094e..8a355a4dc4fde12428db868a2f81ec6b115c9d66 100644 --- a/miner/stress_clique.go +++ b/miner/stress_clique.go @@ -69,7 +69,7 @@ func main() { if err != nil { panic(err) } - defer node.Stop() + defer node.Close() for node.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) diff --git a/miner/stress_ethash.go b/miner/stress_ethash.go index 044ca9a218348bd7d13ae006c00611a71fef39c3..040af9fba25808dd3737f022fd44852303ef4e94 100644 --- a/miner/stress_ethash.go +++ b/miner/stress_ethash.go @@ -69,7 +69,7 @@ func main() { if err != nil { panic(err) } - defer node.Stop() + defer node.Close() for node.Server().NodeInfo().Ports.Listener == 0 { time.Sleep(250 * time.Millisecond) diff --git a/mobile/geth.go b/mobile/geth.go index 781f42f708cc7f52cbbb7fcc88bc5cf1c51011a4..fba3e5711b3f8d13453f1b12f8c0f8996cf1c810 100644 --- a/mobile/geth.go +++ b/mobile/geth.go @@ -192,6 +192,12 @@ func NewNode(datadir string, config *NodeConfig) (stack *Node, _ error) { return &Node{rawStack}, nil } +// Close terminates a running node along with all it's services, tearing internal +// state doen too. It's not possible to restart a closed node. +func (n *Node) Close() error { + return n.node.Close() +} + // Start creates a live P2P node and starts running it. func (n *Node) Start() error { return n.node.Start() diff --git a/node/config_test.go b/node/config_test.go index b81d3d612025b2fe551730ef87066db0f8d17c34..00c24a239123c180d23eab635aac115afd18f85d 100644 --- a/node/config_test.go +++ b/node/config_test.go @@ -38,14 +38,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) } @@ -57,8 +65,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 c35a509723c2bdee86ad4d1b00db78cc59488a76..f267cdc4611195627f1e03bf1fe8bb58dc4c634a 100644 --- a/node/node.go +++ b/node/node.go @@ -121,6 +121,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 ee06f4065c3792c14150092b761f66348532b236..57b18855f1ed95421b5014db6f3b90df25b6f6c8 100644 --- a/node/node_example_test.go +++ b/node/node_example_test.go @@ -46,6 +46,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 f833cd6880376e97d297fbffd0ce3fcce14c0730..c464771cd807be1e4d0a21a8fbe9e8b48bfb0114 100644 --- a/node/node_test.go +++ b/node/node_test.go @@ -46,6 +46,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 { @@ -88,6 +90,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) } @@ -98,6 +102,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) } @@ -109,6 +115,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 { @@ -141,6 +149,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, @@ -191,6 +201,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 @@ -239,6 +251,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, @@ -286,6 +300,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, @@ -339,6 +355,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, @@ -414,6 +432,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) } @@ -449,6 +469,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 @@ -505,6 +527,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 a7ae439e0a908799d6c8f89c8185b820dcbea61c..73c51eccbbfa75d71ffd39005030489397aa0a42 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 eada9579ed0a89112abe0e2326144ab331ee67cb..bdfbf1c732d599a89fb13e7e3e006849f70069fe 100644 --- a/p2p/simulations/adapters/inproc.go +++ b/p2p/simulations/adapters/inproc.go @@ -172,6 +172,12 @@ type SimNode struct { registerOnce sync.Once } +// 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 6edcefc5320f8819498258155fe81a7da1700a03..c2a3b9647f6aade60d50cb623e822caed6f2e000 100644 --- a/p2p/simulations/network.go +++ b/p2p/simulations/network.go @@ -22,6 +22,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "math/rand" "sync" "time" @@ -569,6 +570,12 @@ func (net *Network) Shutdown() { if err := node.Stop(); err != nil { log.Warn("Can't stop node", "id", node.ID(), "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) }