def kill_trinity_gracefully(trinity_config: TrinityConfig, logger: logging.Logger, processes: Iterable[multiprocessing.Process], component_manager_service: ComponentManagerService, reason: str = None) -> 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. hint = f"({reason})" if reason else f"" logger.info('Shutting down Trinity %s', hint) component_manager_service.cancel_nowait() for process in 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. process.join(2) if process.is_alive(): kill_process_gracefully(process, logger) logger.info('%s process (pid=%d) terminated', process.name, process.pid) remove_dangling_ipc_files(logger, trinity_config.ipc_dir) ArgumentParser().exit(message=f"Trinity shutdown complete {hint}\n")
def main_entry(trinity_boot: BootFn, app_identifier: str, components: Tuple[Type[BaseComponent], ...], sub_configs: Sequence[Type[BaseAppConfig]]) -> None: for component_type in components: component_type.configure_parser(parser, subparser) argcomplete.autocomplete(parser) args = parser.parse_args() if not args.genesis and args.network_id not in PRECONFIGURED_NETWORKS: raise NotImplementedError( f"Unsupported network id: {args.network_id}. To use a network besides " "mainnet or ropsten, you must supply a genesis file with a flag, like " "`--genesis path/to/genesis.json`, also you must specify a data " "directory with `--data-dir path/to/data/directory`") # The `common_log_level` is derived from `--log-level <Level>` / `-l <Level>` without # specifying any module. If present, it is used for both `stderr` and `file` logging. common_log_level = args.log_levels and args.log_levels.get(None) has_ambigous_logging_config = ( (common_log_level is not None and args.stderr_log_level is not None) or (common_log_level is not None and args.file_log_level is not None)) if has_ambigous_logging_config: parser.error(f"""\n Ambiguous logging configuration: The `--log-level (-l)` flag sets the log level for both file and stderr logging. To configure different log level for file and stderr logging, remove the `--log-level` flag and use `--stderr-log-level` and/or `--file-log-level` separately. Alternatively, remove the `--stderr-log-level` and/or `--file-log-level` flags to share one single log level across both handlers. """) if is_prerelease(): # this modifies the asyncio logger, but will be overridden by any custom settings below enable_warnings_by_default() stderr_logger, handler_stream = setup_trinity_stderr_logging( args.stderr_log_level or common_log_level) if args.log_levels: setup_log_levels(args.log_levels) try: trinity_config = TrinityConfig.from_parser_args( args, app_identifier, sub_configs) except AmbigiousFileSystem: parser.error(TRINITY_AMBIGIOUS_FILESYSTEM_INFO) if not is_data_dir_initialized(trinity_config): # TODO: this will only work as is for chains with known genesis # parameters. Need to flesh out how genesis parameters for custom # chains are defined and passed around. try: initialize_data_dir(trinity_config) except AmbigiousFileSystem: parser.error(TRINITY_AMBIGIOUS_FILESYSTEM_INFO) except MissingPath as e: parser.error( "\n" f"It appears that {e.path} does not exist. " "Trinity does not attempt to create directories outside of its root path. " "Either manually create the path or ensure you are using a data directory " "inside the XDG_TRINITY_ROOT path") file_logger, log_queue, listener = setup_trinity_file_and_queue_logging( stderr_logger, handler_stream, trinity_config.logfile_path, args.file_log_level or common_log_level, ) display_launch_logs(trinity_config) # compute the minimum configured log level across all configured loggers. min_configured_log_level = min(stderr_logger.level, file_logger.level, *(args.log_levels or {}).values()) extra_kwargs = { 'log_queue': log_queue, 'log_level': min_configured_log_level, 'log_levels': args.log_levels if args.log_levels else {}, 'profile': args.profile, } # Components can provide a subcommand with a `func` which does then control # the entire process from here. if hasattr(args, 'func'): args.func(args, trinity_config) return if hasattr(args, 'munge_func'): args.munge_func(args, trinity_config) processes = trinity_boot( args, trinity_config, extra_kwargs, listener, stderr_logger, ) def kill_trinity_with_reason(reason: str) -> None: kill_trinity_gracefully(trinity_config, stderr_logger, processes, component_manager_service, reason=reason) boot_info = TrinityBootInfo(args, trinity_config, extra_kwargs) component_manager_service = ComponentManagerService( boot_info, components, kill_trinity_with_reason) try: loop = asyncio.get_event_loop() asyncio.ensure_future(exit_with_services(component_manager_service)) asyncio.ensure_future(component_manager_service.run()) loop.add_signal_handler(signal.SIGTERM, lambda: kill_trinity_with_reason("SIGTERM")) loop.run_forever() loop.close() except KeyboardInterrupt: kill_trinity_with_reason("CTRL+C / Keyboard Interrupt") finally: if trinity_config.trinity_tmp_root_dir: import shutil shutil.rmtree(trinity_config.trinity_root_dir)