def kill_helios_gracefully(logger: logging.Logger, database_server_process: Any, chain_processes: List[Any], networking_process: Any, plugin_manager: PluginManager, main_endpoint: Endpoint, event_bus: EventBus, message: str="Helios shudown complete\n") -> None: # When a user hits Ctrl+C in the terminal, the SIGINT is sent to all processes in the # foreground *process group*, so both our networking and database processes will terminate # at the same time and not sequentially as we'd like. That shouldn't be a problem but if # we keep getting unhandled BrokenPipeErrors/ConnectionResetErrors like reported in # https://github.com/ethereum/py-evm/issues/827, we might want to change the networking # process' signal handler to wait until the DB process has terminated before doing its # thing. # Notice that we still need the kill_process_gracefully() calls here, for when the user # simply uses 'kill' to send a signal to the main process, but also because they will # perform a non-gracefull shutdown if the process takes too long to terminate. logger.info('Keyboard Interrupt: Stopping') plugin_manager.shutdown_blocking() main_endpoint.stop() event_bus.stop() for name, process in [("DB", database_server_process), ("Networking", networking_process), *[("Chain", chain_process) for chain_process in chain_processes]]: # Our sub-processes will have received a SIGINT already (see comment above), so here we # wait 2s for them to finish cleanly, and if they fail we kill them for real. if process is not None: process.join(2) if process.is_alive(): kill_process_gracefully(process, logger) logger.info('%s process (pid=%d) terminated', name, process.pid) # This is required to be within the `kill_helios_gracefully` so that # plugins can trigger a shutdown of the helios process. ArgumentParser().exit(message=message)
def database_server_ipc_path(): core_db = AtomicDB() core_db[b'key-a'] = b'value-a' chaindb = ChainDB(core_db) # TODO: use a custom chain class only for testing. chaindb.persist_header(ROPSTEN_GENESIS_HEADER) with tempfile.TemporaryDirectory() as temp_dir: chain_config = ChainConfig(network_id=ROPSTEN_NETWORK_ID, max_peers=1, data_dir=temp_dir) manager = get_chaindb_manager(chain_config, core_db) chaindb_server_process = multiprocessing.Process( target=serve_chaindb, args=(manager,), ) chaindb_server_process.start() wait_for_ipc(chain_config.database_ipc_path) try: yield chain_config.database_ipc_path finally: kill_process_gracefully(chaindb_server_process, logging.getLogger())
def stop(self) -> None: self.context.event_bus.stop() kill_process_gracefully(self._process, self.logger)
def helios_boot(args: Namespace, chain_config: ChainConfig, extra_kwargs: Dict[str, Any], plugin_manager: PluginManager, listener: logging.handlers.QueueListener, event_bus: EventBus, main_endpoint: Endpoint, logger: logging.Logger) -> None: # start the listener thread to handle logs produced by other processes in # the local logger. listener.start() networking_endpoint = event_bus.create_endpoint(NETWORKING_EVENTBUS_ENDPOINT) event_bus.start() # First initialize the database process. database_server_process = ctx.Process( target=run_database_process, args=( chain_config, LevelDB, ), kwargs=extra_kwargs, ) chain_processes = [] for i in range(chain_config.num_chain_processes): chain_process = ctx.Process( target=run_chain_process, args=( chain_config, i ), kwargs=extra_kwargs, ) chain_processes.append(chain_process) networking_process = ctx.Process( target=launch_node, args=(args, chain_config, networking_endpoint,), kwargs=extra_kwargs, ) # start the processes database_server_process.start() logger.info("Started DB server process (pid=%d)", database_server_process.pid) # networking process needs the IPC socket file provided by the database process try: wait_for_ipc(chain_config.database_ipc_path) except TimeoutError as e: logger.error("Timeout waiting for database to start. Exiting...") kill_process_gracefully(database_server_process, logger) ArgumentParser().error(message="Timed out waiting for database start") for i in range(chain_config.num_chain_processes): chain_process = chain_processes[i] chain_process.start() logger.info("Started chain instance {} process (pid={})".format(i,database_server_process.pid)) try: wait_for_ipc(chain_config.get_chain_ipc_path(i)) except TimeoutError as e: logger.error("Timeout waiting for chain instance {} to start. Exiting...".format(i)) kill_process_gracefully(database_server_process, logger) for j in range(i+1): kill_process_gracefully(chain_processes[j], logger) ArgumentParser().error(message="Timed out waiting for chain instance {} start".format(i)) networking_process.start() logger.info("Started networking process (pid=%d)", networking_process.pid) main_endpoint.subscribe( ShutdownRequest, lambda ev: kill_helios_gracefully( logger, database_server_process, chain_processes, networking_process, plugin_manager, main_endpoint, event_bus ) ) plugin_manager.prepare(args, chain_config, extra_kwargs) plugin_manager.broadcast(HeliosStartupEvent( args, chain_config )) try: loop = asyncio.get_event_loop() loop.run_forever() loop.close() except KeyboardInterrupt: kill_helios_gracefully( logger, database_server_process, chain_processes, networking_process, plugin_manager, main_endpoint, event_bus )