cmd/utils, rpc/comms: stop XEth when IPC connection ends
There are a bunch of changes required to make this work: - in miner: allow unregistering agents, fix RemoteAgent.Stop - in eth/filters: make FilterSystem.Stop not crash - in rpc/comms: move listen loop to platform-independent code Fixes #1930. I ran the shell loop there for a few minutes and didn't see any changes in the memory profile.
This commit is contained in:
		
							parent
							
								
									56f8699a6c
								
							
						
					
					
						commit
						fbdb44dcc1
					
				| @ -627,17 +627,14 @@ func StartIPC(eth *eth.Ethereum, ctx *cli.Context) error { | ||||
| 		Endpoint: IpcSocketPath(ctx), | ||||
| 	} | ||||
| 
 | ||||
| 	initializer := func(conn net.Conn) (shared.EthereumApi, error) { | ||||
| 	initializer := func(conn net.Conn) (comms.Stopper, shared.EthereumApi, error) { | ||||
| 		fe := useragent.NewRemoteFrontend(conn, eth.AccountManager()) | ||||
| 		xeth := xeth.New(eth, fe) | ||||
| 		codec := codec.JSON | ||||
| 
 | ||||
| 		apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec, xeth, eth) | ||||
| 		apis, err := api.ParseApiString(ctx.GlobalString(IPCApiFlag.Name), codec.JSON, xeth, eth) | ||||
| 		if err != nil { | ||||
| 			return nil, err | ||||
| 			return nil, nil, err | ||||
| 		} | ||||
| 
 | ||||
| 		return api.Merge(apis...), nil | ||||
| 		return xeth, api.Merge(apis...), nil | ||||
| 	} | ||||
| 
 | ||||
| 	return comms.StartIpc(config, codec.JSON, initializer) | ||||
|  | ||||
| @ -31,30 +31,32 @@ import ( | ||||
| // block, transaction and log events. The Filtering system can be used to listen
 | ||||
| // for specific LOG events fired by the EVM (Ethereum Virtual Machine).
 | ||||
| type FilterSystem struct { | ||||
| 	eventMux *event.TypeMux | ||||
| 
 | ||||
| 	filterMu sync.RWMutex | ||||
| 	filterId int | ||||
| 	filters  map[int]*Filter | ||||
| 	created  map[int]time.Time | ||||
| 
 | ||||
| 	quit chan struct{} | ||||
| 	sub      event.Subscription | ||||
| } | ||||
| 
 | ||||
| // NewFilterSystem returns a newly allocated filter manager
 | ||||
| func NewFilterSystem(mux *event.TypeMux) *FilterSystem { | ||||
| 	fs := &FilterSystem{ | ||||
| 		eventMux: mux, | ||||
| 		filters: make(map[int]*Filter), | ||||
| 		created: make(map[int]time.Time), | ||||
| 	} | ||||
| 	fs.sub = mux.Subscribe( | ||||
| 		//core.PendingBlockEvent{},
 | ||||
| 		core.ChainEvent{}, | ||||
| 		core.TxPreEvent{}, | ||||
| 		vm.Logs(nil), | ||||
| 	) | ||||
| 	go fs.filterLoop() | ||||
| 	return fs | ||||
| } | ||||
| 
 | ||||
| // Stop quits the filter loop required for polling events
 | ||||
| func (fs *FilterSystem) Stop() { | ||||
| 	close(fs.quit) | ||||
| 	fs.sub.Unsubscribe() | ||||
| } | ||||
| 
 | ||||
| // Add adds a filter to the filter manager
 | ||||
| @ -89,26 +91,7 @@ func (fs *FilterSystem) Get(id int) *Filter { | ||||
| // filterLoop waits for specific events from ethereum and fires their handlers
 | ||||
| // when the filter matches the requirements.
 | ||||
| func (fs *FilterSystem) filterLoop() { | ||||
| 	// Subscribe to events
 | ||||
| 	eventCh := fs.eventMux.Subscribe( | ||||
| 		//core.PendingBlockEvent{},
 | ||||
| 		core.ChainEvent{}, | ||||
| 		core.TxPreEvent{}, | ||||
| 		vm.Logs(nil), | ||||
| 	).Chan() | ||||
| 
 | ||||
| out: | ||||
| 	for { | ||||
| 		select { | ||||
| 		case <-fs.quit: | ||||
| 			break out | ||||
| 		case event, ok := <-eventCh: | ||||
| 			if !ok { | ||||
| 				// Event subscription closed, set the channel to nil to stop spinning
 | ||||
| 				eventCh = nil | ||||
| 				continue | ||||
| 			} | ||||
| 			// A real event arrived, notify the registered filters
 | ||||
| 	for event := range fs.sub.Chan() { | ||||
| 		switch ev := event.Data.(type) { | ||||
| 		case core.ChainEvent: | ||||
| 			fs.filterMu.RLock() | ||||
| @ -141,5 +124,4 @@ out: | ||||
| 			fs.filterMu.RUnlock() | ||||
| 		} | ||||
| 	} | ||||
| 	} | ||||
| } | ||||
|  | ||||
| @ -133,10 +133,13 @@ func (self *Miner) Register(agent Agent) { | ||||
| 	if self.Mining() { | ||||
| 		agent.Start() | ||||
| 	} | ||||
| 
 | ||||
| 	self.worker.register(agent) | ||||
| } | ||||
| 
 | ||||
| func (self *Miner) Unregister(agent Agent) { | ||||
| 	self.worker.unregister(agent) | ||||
| } | ||||
| 
 | ||||
| func (self *Miner) Mining() bool { | ||||
| 	return atomic.LoadInt32(&self.mining) > 0 | ||||
| } | ||||
| @ -146,7 +149,7 @@ func (self *Miner) HashRate() (tot int64) { | ||||
| 	// do we care this might race? is it worth we're rewriting some
 | ||||
| 	// aspects of the worker/locking up agents so we can get an accurate
 | ||||
| 	// hashrate?
 | ||||
| 	for _, agent := range self.worker.agents { | ||||
| 	for agent := range self.worker.agents { | ||||
| 		tot += agent.GetHashRate() | ||||
| 	} | ||||
| 	return | ||||
|  | ||||
| @ -48,9 +48,10 @@ type RemoteAgent struct { | ||||
| } | ||||
| 
 | ||||
| func NewRemoteAgent() *RemoteAgent { | ||||
| 	agent := &RemoteAgent{work: make(map[common.Hash]*Work), hashrate: make(map[common.Hash]hashrate)} | ||||
| 
 | ||||
| 	return agent | ||||
| 	return &RemoteAgent{ | ||||
| 		work:     make(map[common.Hash]*Work), | ||||
| 		hashrate: make(map[common.Hash]hashrate), | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (a *RemoteAgent) SubmitHashrate(id common.Hash, rate uint64) { | ||||
| @ -75,8 +76,12 @@ func (a *RemoteAgent) Start() { | ||||
| } | ||||
| 
 | ||||
| func (a *RemoteAgent) Stop() { | ||||
| 	if a.quit != nil { | ||||
| 		close(a.quit) | ||||
| 	} | ||||
| 	if a.workCh != nil { | ||||
| 		close(a.workCh) | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| // GetHashRate returns the accumulated hashrate of all identifier combined
 | ||||
|  | ||||
| @ -92,7 +92,7 @@ type Result struct { | ||||
| type worker struct { | ||||
| 	mu sync.Mutex | ||||
| 
 | ||||
| 	agents []Agent | ||||
| 	agents map[Agent]struct{} | ||||
| 	recv   chan *Result | ||||
| 	mux    *event.TypeMux | ||||
| 	quit   chan struct{} | ||||
| @ -136,6 +136,7 @@ func newWorker(coinbase common.Address, eth core.Backend) *worker { | ||||
| 		coinbase:       coinbase, | ||||
| 		txQueue:        make(map[common.Hash]*types.Transaction), | ||||
| 		quit:           make(chan struct{}), | ||||
| 		agents:         make(map[Agent]struct{}), | ||||
| 		fullValidation: false, | ||||
| 	} | ||||
| 	go worker.update() | ||||
| @ -180,7 +181,7 @@ func (self *worker) start() { | ||||
| 	atomic.StoreInt32(&self.mining, 1) | ||||
| 
 | ||||
| 	// spin up agents
 | ||||
| 	for _, agent := range self.agents { | ||||
| 	for agent := range self.agents { | ||||
| 		agent.Start() | ||||
| 	} | ||||
| } | ||||
| @ -190,16 +191,14 @@ func (self *worker) stop() { | ||||
| 	defer self.mu.Unlock() | ||||
| 
 | ||||
| 	if atomic.LoadInt32(&self.mining) == 1 { | ||||
| 		var keep []Agent | ||||
| 		// stop all agents
 | ||||
| 		for _, agent := range self.agents { | ||||
| 		// Stop all agents.
 | ||||
| 		for agent := range self.agents { | ||||
| 			agent.Stop() | ||||
| 			// keep all that's not a cpu agent
 | ||||
| 			if _, ok := agent.(*CpuAgent); !ok { | ||||
| 				keep = append(keep, agent) | ||||
| 			// Remove CPU agents.
 | ||||
| 			if _, ok := agent.(*CpuAgent); ok { | ||||
| 				delete(self.agents, agent) | ||||
| 			} | ||||
| 		} | ||||
| 		self.agents = keep | ||||
| 	} | ||||
| 
 | ||||
| 	atomic.StoreInt32(&self.mining, 0) | ||||
| @ -209,10 +208,17 @@ func (self *worker) stop() { | ||||
| func (self *worker) register(agent Agent) { | ||||
| 	self.mu.Lock() | ||||
| 	defer self.mu.Unlock() | ||||
| 	self.agents = append(self.agents, agent) | ||||
| 	self.agents[agent] = struct{}{} | ||||
| 	agent.SetReturnCh(self.recv) | ||||
| } | ||||
| 
 | ||||
| func (self *worker) unregister(agent Agent) { | ||||
| 	self.mu.Lock() | ||||
| 	defer self.mu.Unlock() | ||||
| 	delete(self.agents, agent) | ||||
| 	agent.Stop() | ||||
| } | ||||
| 
 | ||||
| func (self *worker) update() { | ||||
| 	eventSub := self.mux.Subscribe(core.ChainHeadEvent{}, core.ChainSideEvent{}, core.TxPreEvent{}) | ||||
| 	defer eventSub.Unsubscribe() | ||||
| @ -341,11 +347,9 @@ func (self *worker) push(work *Work) { | ||||
| 			glog.Infoln("You turn back and abort mining") | ||||
| 			return | ||||
| 		} | ||||
| 
 | ||||
| 		// push new work to agents
 | ||||
| 		for _, agent := range self.agents { | ||||
| 		for agent := range self.agents { | ||||
| 			atomic.AddInt32(&self.atWork, 1) | ||||
| 
 | ||||
| 			if agent.Work() != nil { | ||||
| 				agent.Work() <- work | ||||
| 			} | ||||
|  | ||||
| @ -20,13 +20,22 @@ import ( | ||||
| 	"fmt" | ||||
| 	"math/rand" | ||||
| 	"net" | ||||
| 	"os" | ||||
| 
 | ||||
| 	"encoding/json" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/logger" | ||||
| 	"github.com/ethereum/go-ethereum/logger/glog" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/codec" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/shared" | ||||
| ) | ||||
| 
 | ||||
| type Stopper interface { | ||||
| 	Stop() | ||||
| } | ||||
| 
 | ||||
| type InitFunc func(conn net.Conn) (Stopper, shared.EthereumApi, error) | ||||
| 
 | ||||
| type IpcConfig struct { | ||||
| 	Endpoint string | ||||
| } | ||||
| @ -90,8 +99,38 @@ func NewIpcClient(cfg IpcConfig, codec codec.Codec) (*ipcClient, error) { | ||||
| } | ||||
| 
 | ||||
| // Start IPC server
 | ||||
| func StartIpc(cfg IpcConfig, codec codec.Codec, initializer func(conn net.Conn) (shared.EthereumApi, error)) error { | ||||
| 	return startIpc(cfg, codec, initializer) | ||||
| func StartIpc(cfg IpcConfig, codec codec.Codec, initializer InitFunc) error { | ||||
| 	l, err := ipcListen(cfg) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 	} | ||||
| 	go ipcLoop(cfg, codec, initializer, l) | ||||
| 	return nil | ||||
| } | ||||
| 
 | ||||
| func ipcLoop(cfg IpcConfig, codec codec.Codec, initializer InitFunc, l net.Listener) { | ||||
| 	glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint) | ||||
| 	defer os.Remove(cfg.Endpoint) | ||||
| 	defer l.Close() | ||||
| 	for { | ||||
| 		conn, err := l.Accept() | ||||
| 		if err != nil { | ||||
| 			glog.V(logger.Debug).Infof("accept: %v", err) | ||||
| 			return | ||||
| 		} | ||||
| 		id := newIpcConnId() | ||||
| 		go func() { | ||||
| 			defer conn.Close() | ||||
| 			glog.V(logger.Debug).Infof("new connection with id %06d started", id) | ||||
| 			stopper, api, err := initializer(conn) | ||||
| 			if err != nil { | ||||
| 				glog.V(logger.Error).Infof("Unable to initialize IPC connection: %v", err) | ||||
| 				return | ||||
| 			} | ||||
| 			defer stopper.Stop() | ||||
| 			handle(id, conn, api, codec) | ||||
| 		}() | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func newIpcConnId() int { | ||||
|  | ||||
| @ -23,8 +23,6 @@ import ( | ||||
| 	"os" | ||||
| 	"path/filepath" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/logger" | ||||
| 	"github.com/ethereum/go-ethereum/logger/glog" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/codec" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/shared" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/useragent" | ||||
| @ -69,44 +67,16 @@ func (self *ipcClient) reconnect() error { | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func startIpc(cfg IpcConfig, codec codec.Codec, initializer func(conn net.Conn) (shared.EthereumApi, error)) error { | ||||
| func ipcListen(cfg IpcConfig) (net.Listener, error) { | ||||
| 	// Ensure the IPC path exists and remove any previous leftover
 | ||||
| 	if err := os.MkdirAll(filepath.Dir(cfg.Endpoint), 0751); err != nil { | ||||
| 		return err | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	os.Remove(cfg.Endpoint) | ||||
| 
 | ||||
| 	l, err := net.ListenUnix("unix", &net.UnixAddr{Name: cfg.Endpoint, Net: "unix"}) | ||||
| 	l, err := net.Listen("unix", cfg.Endpoint) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	os.Chmod(cfg.Endpoint, 0600) | ||||
| 
 | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			conn, err := l.AcceptUnix() | ||||
| 			if err != nil { | ||||
| 				glog.V(logger.Error).Infof("Error accepting ipc connection - %v\n", err) | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			id := newIpcConnId() | ||||
| 			glog.V(logger.Debug).Infof("New IPC connection with id %06d started\n", id) | ||||
| 
 | ||||
| 			api, err := initializer(conn) | ||||
| 			if err != nil { | ||||
| 				glog.V(logger.Error).Infof("Unable to initialize IPC connection - %v\n", err) | ||||
| 				conn.Close() | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			go handle(id, conn, api, codec) | ||||
| 		} | ||||
| 
 | ||||
| 		os.Remove(cfg.Endpoint) | ||||
| 	}() | ||||
| 
 | ||||
| 	glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint) | ||||
| 
 | ||||
| 	return nil | ||||
| 	return l, nil | ||||
| } | ||||
|  | ||||
| @ -28,8 +28,6 @@ import ( | ||||
| 	"time" | ||||
| 	"unsafe" | ||||
| 
 | ||||
| 	"github.com/ethereum/go-ethereum/logger" | ||||
| 	"github.com/ethereum/go-ethereum/logger/glog" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/codec" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/shared" | ||||
| 	"github.com/ethereum/go-ethereum/rpc/useragent" | ||||
| @ -688,40 +686,12 @@ func (self *ipcClient) reconnect() error { | ||||
| 	return err | ||||
| } | ||||
| 
 | ||||
| func startIpc(cfg IpcConfig, codec codec.Codec, initializer func(conn net.Conn) (shared.EthereumApi, error)) error { | ||||
| func ipcListen(cfg IpcConfig) (net.Listener, error) { | ||||
| 	os.Remove(cfg.Endpoint) // in case it still exists from a previous run
 | ||||
| 
 | ||||
| 	l, err := Listen(cfg.Endpoint) | ||||
| 	if err != nil { | ||||
| 		return err | ||||
| 		return nil, err | ||||
| 	} | ||||
| 	os.Chmod(cfg.Endpoint, 0600) | ||||
| 
 | ||||
| 	go func() { | ||||
| 		for { | ||||
| 			conn, err := l.Accept() | ||||
| 			if err != nil { | ||||
| 				glog.V(logger.Error).Infof("Error accepting ipc connection - %v\n", err) | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			id := newIpcConnId() | ||||
| 			glog.V(logger.Debug).Infof("New IPC connection with id %06d started\n", id) | ||||
| 
 | ||||
| 			api, err := initializer(conn) | ||||
| 			if err != nil { | ||||
| 				glog.V(logger.Error).Infof("Unable to initialize IPC connection - %v\n", err) | ||||
| 				conn.Close() | ||||
| 				continue | ||||
| 			} | ||||
| 
 | ||||
| 			go handle(id, conn, api, codec) | ||||
| 		} | ||||
| 
 | ||||
| 		os.Remove(cfg.Endpoint) | ||||
| 	}() | ||||
| 
 | ||||
| 	glog.V(logger.Info).Infof("IPC service started (%s)\n", cfg.Endpoint) | ||||
| 
 | ||||
| 	return nil | ||||
| 	return l, nil | ||||
| } | ||||
|  | ||||
							
								
								
									
										14
									
								
								xeth/xeth.go
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								xeth/xeth.go
									
									
									
									
									
								
							| @ -113,19 +113,15 @@ func New(ethereum *eth.Ethereum, frontend Frontend) *XEth { | ||||
| 	if frontend == nil { | ||||
| 		xeth.frontend = dummyFrontend{} | ||||
| 	} | ||||
| 	state, err := xeth.backend.BlockChain().State() | ||||
| 	if err != nil { | ||||
| 		return nil | ||||
| 	} | ||||
| 	state, _ := xeth.backend.BlockChain().State() | ||||
| 	xeth.state = NewState(xeth, state) | ||||
| 
 | ||||
| 	go xeth.start() | ||||
| 
 | ||||
| 	return xeth | ||||
| } | ||||
| 
 | ||||
| func (self *XEth) start() { | ||||
| 	timer := time.NewTicker(2 * time.Second) | ||||
| 	defer timer.Stop() | ||||
| done: | ||||
| 	for { | ||||
| 		select { | ||||
| @ -171,8 +167,12 @@ done: | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| func (self *XEth) stop() { | ||||
| // Stop releases any resources associated with self.
 | ||||
| // It may not be called more than once.
 | ||||
| func (self *XEth) Stop() { | ||||
| 	close(self.quit) | ||||
| 	self.filterManager.Stop() | ||||
| 	self.backend.Miner().Unregister(self.agent) | ||||
| } | ||||
| 
 | ||||
| func cAddress(a []string) []common.Address { | ||||
|  | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user