Esempio n. 1
0
def kill_trinity_gracefully(logger: logging.Logger,
                            database_server_process: Any,
                            networking_process: Any,
                            plugin_manager: PluginManager,
                            main_endpoint: Endpoint,
                            event_bus: EventBus,
                            message: str="Trinity 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)]:
        # 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', name, process.pid)

    # This is required to be within the `kill_trinity_gracefully` so that
    # plugins can trigger a shutdown of the trinity process.
    ArgumentParser().exit(message=message)
Esempio n. 2
0
def kill_trinity_gracefully(
        logger: logging.Logger,
        database_server_process: Any,
        networking_process: Any,
        plugin_manager: PluginManager,
        event_bus: EventBus,
        message: str = "Trinity 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()
    event_bus.shutdown()
    kill_process_gracefully(database_server_process, logger)
    logger.info('DB server process (pid=%d) terminated',
                database_server_process.pid)
    # XXX: This short sleep here seems to avoid us hitting a deadlock when attempting to
    # join() the networking subprocess: https://github.com/ethereum/py-evm/issues/940
    time.sleep(0.2)
    kill_process_gracefully(networking_process, logger)
    logger.info('Networking process (pid=%d) terminated',
                networking_process.pid)

    # This is required to be within the `kill_trinity_gracefully` so that
    # plugins can trigger a shutdown of the trinity process.
    ArgumentParser().exit(message=message)
Esempio n. 3
0
def kill_trinity_gracefully(logger: logging.Logger,
                            processes: Iterable[multiprocessing.Process],
                            plugin_manager: PluginManager,
                            main_endpoint: Endpoint,
                            event_bus: EventBus,
                            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)
    plugin_manager.shutdown_blocking()
    main_endpoint.stop()
    event_bus.stop()
    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)

    ArgumentParser().exit(message=f"Trinity shutdown complete {hint}\n")
Esempio n. 4
0
async def launch_node_coro(args: Namespace,
                           trinity_config: TrinityConfig) -> None:
    endpoint = TrinityEventBusEndpoint()
    NodeClass = trinity_config.get_app_config(Eth1AppConfig).node_class
    node = NodeClass(endpoint, trinity_config)

    networking_connection_config = ConnectionConfig.from_name(
        NETWORKING_EVENTBUS_ENDPOINT, trinity_config.ipc_dir)

    await endpoint.start_serving(networking_connection_config)
    endpoint.auto_connect_new_announced_endpoints()
    await endpoint.connect_to_endpoints(
        ConnectionConfig.from_name(MAIN_EVENTBUS_ENDPOINT,
                                   trinity_config.ipc_dir),
        # Plugins that run within the networking process broadcast and receive on the
        # the same endpoint
        networking_connection_config,
    )
    await endpoint.announce_endpoint()

    # This is a second PluginManager instance governing plugins in a shared process.
    plugin_manager = PluginManager(SharedProcessScope(endpoint),
                                   get_all_plugins())
    plugin_manager.prepare(args, trinity_config)

    asyncio.ensure_future(
        handle_networking_exit(node, plugin_manager, endpoint))
    asyncio.ensure_future(node.run())
Esempio n. 5
0
class PluginManagerService(BaseService):
    _endpoint: EndpointAPI

    def __init__(self,
                 trinity_boot_info: TrinityBootInfo,
                 plugins: Sequence[Type[BasePlugin]],
                 kill_trinity_fn: Callable[[str], Any],
                 cancel_token: CancelToken = None,
                 loop: asyncio.AbstractEventLoop = None) -> None:
        self._boot_info = trinity_boot_info
        self._plugins = plugins
        self._kill_trinity_fn = kill_trinity_fn
        super().__init__(cancel_token, loop)

    async def _run(self) -> None:
        self._connection_config = ConnectionConfig.from_name(
            MAIN_EVENTBUS_ENDPOINT, self._boot_info.trinity_config.ipc_dir)
        async with AsyncioEndpoint.serve(self._connection_config) as endpoint:
            self._endpoint = endpoint

            # start the background process that tracks and propagates available
            # endpoints to the other connected endpoints
            self.run_daemon_task(
                self._track_and_propagate_available_endpoints())
            self.run_daemon_task(self._handle_shutdown_request())

            # start the plugin manager
            self.plugin_manager = PluginManager(endpoint, self._plugins)
            self.plugin_manager.prepare(self._boot_info)
            await self.cancellation()

    async def _handle_shutdown_request(self) -> None:
        req = await self.wait(self._endpoint.wait_for(ShutdownRequest))
        self._kill_trinity_fn(req.reason)
        self.cancel_nowait()

    async def _cleanup(self) -> None:
        self.plugin_manager.shutdown_blocking()

    _available_endpoints: Tuple[ConnectionConfig, ...] = ()

    async def _track_and_propagate_available_endpoints(self) -> None:
        """
        Track new announced endpoints and propagate them across all other existing endpoints.
        """
        async for ev in self.wait_iter(
                self._endpoint.stream(EventBusConnected)):
            self._available_endpoints = self._available_endpoints + (
                ev.connection_config, )
            self.logger.debug("New EventBus Endpoint connected %s",
                              ev.connection_config.name)
            # Broadcast available endpoints to all connected endpoints, giving them
            # a chance to cross connect
            await self._endpoint.broadcast(
                AvailableEndpointsUpdated(self._available_endpoints))
            self.logger.debug("Connected EventBus Endpoints %s",
                              self._available_endpoints)
Esempio n. 6
0
def trinity_boot(args: Namespace, trinity_config: TrinityConfig,
                 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()

    event_bus.start()

    # First initialize the database process.
    database_server_process = ctx.Process(
        name="DB",
        target=run_database_process,
        args=(
            trinity_config,
            LevelDB,
        ),
        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(trinity_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")

    def kill_trinity_with_reason(reason: str) -> None:
        kill_trinity_gracefully(logger, (database_server_process, ),
                                plugin_manager,
                                main_endpoint,
                                event_bus,
                                reason=reason)

    main_endpoint.subscribe(ShutdownRequest,
                            lambda ev: kill_trinity_with_reason(ev.reason))

    plugin_manager.prepare(args, trinity_config, extra_kwargs)

    kill_trinity_with_reason("No beacon support yet. SOON!")

    try:
        loop = asyncio.get_event_loop()
        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")
Esempio n. 7
0
    async def _run(self) -> None:
        self._connection_config = ConnectionConfig.from_name(
            MAIN_EVENTBUS_ENDPOINT, self._boot_info.trinity_config.ipc_dir)
        async with AsyncioEndpoint.serve(self._connection_config) as endpoint:
            self._endpoint = endpoint

            # start the background process that tracks and propagates available
            # endpoints to the other connected endpoints
            self.run_daemon_task(
                self._track_and_propagate_available_endpoints())

            # start the plugin manager
            plugin_manager = PluginManager(endpoint, self._plugins)
            plugin_manager.prepare(self._boot_info)
            await self.cancellation()
Esempio n. 8
0
def trinity_boot(args: Namespace,
                 trinity_config: TrinityConfig,
                 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()

    event_bus.start()

    def kill_trinity_with_reason(reason: str) -> None:
        kill_trinity_gracefully(
            logger,
            (),
            plugin_manager,
            main_endpoint,
            event_bus,
            reason=reason
        )

    main_endpoint.subscribe(
        ShutdownRequest,
        lambda ev: kill_trinity_with_reason(ev.reason)
    )

    plugin_manager.prepare(args, trinity_config, extra_kwargs)

    try:
        loop = asyncio.get_event_loop()
        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")
Esempio n. 9
0
async def launch_node_coro(args: Namespace,
                           trinity_config: TrinityConfig) -> None:
    networking_connection_config = ConnectionConfig.from_name(
        NETWORKING_EVENTBUS_ENDPOINT, trinity_config.ipc_dir)
    async with TrinityEventBusEndpoint.serve(
            networking_connection_config) as endpoint:
        NodeClass = trinity_config.get_app_config(Eth1AppConfig).node_class
        node = NodeClass(endpoint, trinity_config)

        asyncio.ensure_future(endpoint.auto_connect_new_announced_endpoints())
        await endpoint.connect_to_endpoints(
            ConnectionConfig.from_name(MAIN_EVENTBUS_ENDPOINT,
                                       trinity_config.ipc_dir), )
        await endpoint.announce_endpoint()

        # This is a second PluginManager instance governing plugins in a shared process.
        plugin_manager = PluginManager(SharedProcessScope(endpoint),
                                       get_plugins_for_eth1_client())
        plugin_manager.prepare(args, trinity_config)

        asyncio.ensure_future(
            handle_networking_exit(node, plugin_manager, endpoint))
        await node.run()
Esempio n. 10
0
def setup_plugins(scope: BaseManagerProcessScope) -> PluginManager:
    plugin_manager = PluginManager(scope)
    # TODO: Implement auto-discovery of plugins based on some convention/configuration scheme
    plugin_manager.register(ENABLED_PLUGINS)

    return plugin_manager
Esempio n. 11
0
def trinity_boot(args: Namespace,
                 trinity_config: TrinityConfig,
                 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=(
            trinity_config,
            LevelDB,
        ),
        kwargs=extra_kwargs,
    )

    networking_process = ctx.Process(
        target=launch_node,
        args=(args, trinity_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(trinity_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")

    networking_process.start()
    logger.info("Started networking process (pid=%d)", networking_process.pid)

    main_endpoint.subscribe(
        ShutdownRequest,
        lambda ev: kill_trinity_gracefully(
            logger,
            database_server_process,
            networking_process,
            plugin_manager,
            main_endpoint,
            event_bus
        )
    )

    plugin_manager.prepare(args, trinity_config, extra_kwargs)
    plugin_manager.broadcast(TrinityStartupEvent(
        args,
        trinity_config
    ))
    try:
        loop = asyncio.get_event_loop()
        loop.run_forever()
        loop.close()
    except KeyboardInterrupt:
        kill_trinity_gracefully(
            logger,
            database_server_process,
            networking_process,
            plugin_manager,
            main_endpoint,
            event_bus
        )
Esempio n. 12
0
def setup_plugins(scope: BaseManagerProcessScope,
                  plugins: Iterable[BasePlugin]) -> PluginManager:
    plugin_manager = PluginManager(scope)
    plugin_manager.register(plugins)

    return plugin_manager
Esempio n. 13
0
def main_entry(trinity_boot: BootFn,
               app_identifier: str,
               plugins: Iterable[Type[BasePlugin]],
               sub_configs: Iterable[Type[BaseAppConfig]]) -> None:

    main_endpoint = TrinityMainEventBusEndpoint()

    plugin_manager = PluginManager(
        MainAndIsolatedProcessScope(main_endpoint),
        plugins
    )
    plugin_manager.amend_argparser_config(parser, subparser)
    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,
    }

    # Plugins 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

    processes = trinity_boot(
        args,
        trinity_config,
        extra_kwargs,
        plugin_manager,
        listener,
        main_endpoint,
        stderr_logger,
    )

    def kill_trinity_with_reason(reason: str) -> None:
        kill_trinity_gracefully(
            trinity_config,
            stderr_logger,
            processes,
            plugin_manager,
            main_endpoint,
            reason=reason
        )

    try:
        loop = asyncio.get_event_loop()
        asyncio.ensure_future(trinity_boot_coro(
            kill_trinity_with_reason,
            main_endpoint,
            trinity_config,
            plugin_manager,
            args, extra_kwargs,
        ))
        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")