Exemplo n.º 1
0
def test_get_or_make_loop_handles_closed_loop():
    asyncio.set_event_loop(mock.Mock(asyncio.AbstractEventLoop, is_closed=mock.Mock(return_value=True)))
    mock_loop = mock.Mock(asyncio.AbstractEventLoop)

    with mock.patch.object(asyncio, "new_event_loop", return_value=mock_loop) as new_event_loop:
        assert aio.get_or_make_loop() is mock_loop

        new_event_loop.assert_called_once_with()

    assert asyncio.get_event_loop_policy().get_event_loop() is mock_loop
Exemplo n.º 2
0
def test_get_or_make_loop_handles_runtime_error():
    asyncio.set_event_loop(None)
    mock_loop = mock.Mock(asyncio.AbstractEventLoop)

    with mock.patch.object(asyncio, "new_event_loop", return_value=mock_loop) as new_event_loop:
        assert aio.get_or_make_loop() is mock_loop

        new_event_loop.assert_called_once_with()

    assert asyncio.get_event_loop_policy().get_event_loop() is mock_loop
Exemplo n.º 3
0
def test_get_or_make_loop():
    mock_loop = mock.Mock(asyncio.AbstractEventLoop,
                          is_closed=mock.Mock(return_value=False))
    asyncio.set_event_loop(mock_loop)

    assert aio.get_or_make_loop() is mock_loop
Exemplo n.º 4
0
    def run(
        self,
        asyncio_debug: bool = False,
        backlog: int = 128,
        check_for_updates: bool = True,
        close_loop: bool = True,
        close_passed_executor: bool = False,
        coroutine_tracking_depth: typing.Optional[int] = None,
        enable_signal_handlers: typing.Optional[bool] = None,
        host: typing.Optional[typing.Union[str, typing.Sequence[str]]] = None,
        path: typing.Optional[str] = None,
        port: typing.Optional[int] = None,
        reuse_address: typing.Optional[bool] = None,
        reuse_port: typing.Optional[bool] = None,
        shutdown_timeout: float = 60.0,
        socket: typing.Optional[socket_.socket] = None,
        ssl_context: typing.Optional[ssl.SSLContext] = None,
    ) -> None:
        """Open this REST server and block until it closes.

        Other Parameters
        ----------------
        asyncio_debug : builtins.bool
            Defaults to `builtins.False`. If `builtins.True`, then debugging is
            enabled for the asyncio event loop in use.
        backlog : builtins.int
            The number of unaccepted connections that the system will allow before
            refusing new connections.
        check_for_updates : builtins.bool
            Defaults to `builtins.True`. If `builtins.True`, will check for
            newer versions of `hikari` on PyPI and notify if available.
        close_loop : builtins.bool
            Defaults to `builtins.True`. If `builtins.True`, then once the bot
            enters a state where all components have shut down permanently
            during application shutdown, then all asyncgens and background tasks
            will be destroyed, and the event loop will be shut down.

            This will wait until all `hikari`-owned `aiohttp` connectors have
            had time to attempt to shut down correctly (around 250ms), and on
            Python 3.9 and newer, will also shut down the default event loop
            executor too.
        close_passed_executor : builtins.bool
            Defaults to `builtins.False`. If `builtins.True`, any custom
            `concurrent.futures.Executor` passed to the constructor will be
            shut down when the application terminates. This does not affect the
            default executor associated with the event loop, and will not
            do anything if you do not provide a custom executor to the
            constructor.
        coroutine_tracking_depth : typing.Optional[builtins.int]
            Defaults to `builtins.None`. If an integer value and supported by
            the interpreter, then this many nested coroutine calls will be
            tracked with their call origin state. This allows you to determine
            where non-awaited coroutines may originate from, but generally you
            do not want to leave this enabled for performance reasons.
        enable_signal_handlers : typing.Optional[builtins.bool]
            Defaults to `builtins.True` if this is started in the main thread.

            If on a __non-Windows__ OS with builtin support for kernel-level
            POSIX signals, then setting this to `builtins.True` will allow
            treating keyboard interrupts and other OS signals to safely shut
            down the application as calls to shut down the application properly
            rather than just killing the process in a dirty state immediately.
            You should leave this enabled unless you plan to implement your own
            signal handling yourself.
        host : typing.Optional[typing.Union[builtins.str, aiohttp.web.HostSequence]]
            TCP/IP host or a sequence of hosts for the HTTP server.
        port : typing.Optional[builtins.int]
            TCP/IP port for the HTTP server.
        path : typing.Optional[builtins.str]
            File system path for HTTP server unix domain socket.
        reuse_address : typing.Optional[builtins.bool]
            Tells the kernel to reuse a local socket in TIME_WAIT state, without
            waiting for its natural timeout to expire.
        reuse_port : typing.Optional[builtins.bool]
            Tells the kernel to allow this endpoint to be bound to the same port
            as other existing endpoints are also bound to.
        socket : typing.Optional[socket.socket]
            A pre-existing socket object to accept connections on.
        shutdown_timeout : builtins.float
            A delay to wait for graceful server shutdown before forcefully
            disconnecting all open client sockets. This defaults to 60 seconds.
        ssl_context : typing.Optional[ssl.SSLContext]
            SSL context for HTTPS servers.
        """
        if self.is_alive:
            raise errors.ComponentStateConflictError("Cannot start a bot that's already active")

        loop = aio.get_or_make_loop()
        if asyncio_debug:
            loop.set_debug(True)

        if coroutine_tracking_depth is not None:
            try:
                # Provisionally defined in CPython, may be removed without notice.
                sys.set_coroutine_origin_tracking_depth(coroutine_tracking_depth)
            except AttributeError:
                _LOGGER.log(ux.TRACE, "cannot set coroutine tracking depth for sys, no functionality exists for this")

        try:
            loop.run_until_complete(
                self.start(
                    backlog=backlog,
                    check_for_updates=check_for_updates,
                    enable_signal_handlers=enable_signal_handlers,
                    host=host,
                    port=port,
                    path=path,
                    reuse_address=reuse_address,
                    reuse_port=reuse_port,
                    socket=socket,
                    shutdown_timeout=shutdown_timeout,
                    ssl_context=ssl_context,
                )
            )
            loop.run_until_complete(self.join())

        finally:
            if close_passed_executor and self._executor:
                self._executor.shutdown(wait=True)
                self._executor = None

            if close_loop:
                loop.close()
Exemplo n.º 5
0
    def run(
        self,
        *,
        activity: typing.Optional[presences.Activity] = None,
        afk: bool = False,
        asyncio_debug: typing.Optional[bool] = None,
        check_for_updates: bool = True,
        close_passed_executor: bool = False,
        close_loop: bool = True,
        coroutine_tracking_depth: typing.Optional[int] = None,
        enable_signal_handlers: bool = True,
        idle_since: typing.Optional[datetime.datetime] = None,
        ignore_session_start_limit: bool = False,
        large_threshold: int = 250,
        propagate_interrupts: bool = False,
        status: presences.Status = presences.Status.ONLINE,
        shard_ids: typing.Optional[typing.AbstractSet[int]] = None,
        shard_count: typing.Optional[int] = None,
    ) -> None:
        """Start the bot, wait for all shards to become ready, and then return.

        Other Parameters
        ----------------
        activity : typing.Optional[hikari.presences.Activity]
            The initial activity to display in the bot user presence, or
            `builtins.None` (default) to not show any.
        afk : builtins.bool
            The initial AFK state to display in the bot user presence, or
            `builtins.False` (default) to not show any.
        asyncio_debug : builtins.bool
            Defaults to `builtins.False`. If `builtins.True`, then debugging is
            enabled for the asyncio event loop in use.
        check_for_updates : builtins.bool
            Defaults to `builtins.True`. If `builtins.True`, will check for
            newer versions of `hikari` on PyPI and notify if available.
        close_passed_executor : builtins.bool
            Defaults to `builtins.False`. If `builtins.True`, any custom
            `concurrent.futures.Executor` passed to the constructor will be
            shut down when the application terminates. This does not affect the
            default executor associated with the event loop, and will not
            do anything if you do not provide a custom executor to the
            constructor.
        close_loop : builtins.bool
            Defaults to `builtins.True`. If `builtins.True`, then once the bot
            enters a state where all components have shut down permanently
            during application shutdown, then all asyngens and background tasks
            will be destroyed, and the event loop will be shut down.

            This will wait until all `hikari`-owned `aiohttp` connectors have
            had time to attempt to shut down correctly (around 250ms), and on
            Python 3.9 and newer, will also shut down the default event loop
            executor too.
        coroutine_tracking_depth : typing.Optional[builtins.int]
            Defaults to `builtins.None`. If an integer value and supported by
            the interpreter, then this many nested coroutine calls will be
            tracked with their call origin state. This allows you to determine
            where non-awaited coroutines may originate from, but generally you
            do not want to leave this enabled for performance reasons.
        enable_signal_handlers : builtins.bool
            Defaults to `builtins.True`. If on a __non-Windows__ OS with builtin
            support for kernel-level POSIX signals, then setting this to
            `builtins.True` will allow treating keyboard interrupts and other
            OS signals to safely shut down the application as calls to
            shut down the application properly rather than just killing the
            process in a dirty state immediately. You should leave this disabled
            unless you plan to implement your own signal handling yourself.
        idle_since : typing.Optional[datetime.datetime]
            The `datetime.datetime` the user should be marked as being idle
            since, or `builtins.None` (default) to not show this.
        ignore_session_start_limit : builtins.bool
            Defaults to `builtins.False`. If `builtins.False`, then attempting
            to start more sessions than you are allowed in a 24 hour window
            will throw a `hikari.errors.GatewayError` rather than going ahead
            and hitting the IDENTIFY limit, which may result in your token
            being reset. Setting to `builtins.True` disables this behavior.
        large_threshold : builtins.int
            Threshold for members in a guild before it is treated as being
            "large" and no longer sending member details in the `GUILD CREATE`
            event. Defaults to `250`.
        propagate_interrupts : builtins.bool
            Defaults to `builtins.False`. If set to `builtins.True`, then any
            internal `hikari.errors.HikariInterrupt` that is raises as a
            result of catching an OS level signal will result in the
            exception being rethrown once the application has closed. This can
            allow you to use hikari signal handlers and still be able to
            determine what kind of interrupt the application received after
            it closes. When `builtins.False`, nothing is raised and the call
            will terminate cleanly and silently where possible instead.
        shard_ids : typing.Optional[typing.AbstractSet[builtins.int]]
            The shard IDs to create shards for. If not `builtins.None`, then
            a non-`None` `shard_count` must ALSO be provided. Defaults to
            `builtins.None`, which means the Discord-recommended count is used
            for your application instead.
        shard_count : typing.Optional[builtins.int]
            The number of shards to use in the entire distributed application.
            Defaults to `builtins.None` which results in the count being
            determined dynamically on startup.
        status : hikari.presences.Status
            The initial status to show for the user presence on startup.
            Defaults to `hikari.presences.Status.ONLINE`.

        Raises
        ------
        hikari.errors.ComponentStateConflictError
            If bot is already running.
        builtins.TypeError
            If `shard_ids` is passed without `shard_count`.
        """
        if self._is_alive:
            raise errors.ComponentStateConflictError("bot is already running")

        if shard_ids is not None and shard_count is None:
            raise TypeError("'shard_ids' must be passed with 'shard_count'")

        loop = aio.get_or_make_loop()
        signals = ("SIGINT", "SIGTERM")

        if asyncio_debug:
            loop.set_debug(True)

        if coroutine_tracking_depth is not None:
            try:
                # Provisionally defined in CPython, may be removed without notice.
                sys.set_coroutine_origin_tracking_depth(coroutine_tracking_depth)  # type: ignore[attr-defined]
            except AttributeError:
                _LOGGER.log(ux.TRACE, "cannot set coroutine tracking depth for sys, no functionality exists for this")

        # Throwing this in the handler will lead to lots of fun OS specific shenanigans. So, lets just
        # cache it for later, I guess.
        interrupt: typing.Optional[errors.HikariInterrupt] = None
        loop_thread_id = threading.get_native_id()

        def handle_os_interrupt(signum: int, frame: types.FrameType) -> None:
            # If we use a POSIX system, then raising an exception in here works perfectly and shuts the loop down
            # with an exception, which is good.
            # Windows, however, is special on this front. On Windows, the exception is caught by whatever was
            # currently running on the event loop at the time, which is annoying for us, as this could be fired into
            # the task for an event dispatch, for example, which is a guarded call that is never waited for by design.

            # We can't always safely intercept this either, as Windows does not allow us to use asyncio loop
            # signal listeners (since Windows doesn't have kernel-level signals, only emulated system calls
            # for a remote few standard C signal types). Thus, the best solution here is to set the close bit
            # instead, which will let the bot start to clean itself up as if the user closed it manually via a call
            # to `bot.close()`.
            nonlocal interrupt
            signame = signal.strsignal(signum)
            assert signame is not None  # Will always be True

            interrupt = errors.HikariInterrupt(signum, signame)
            # The loop may or may not be running, depending on the state of the application when this occurs.
            # Signals on POSIX only occur on the main thread usually, too, so we need to ensure this is
            # threadsafe if we want the user's application to still shut down if on a separate thread.
            # We log native thread IDs purely for debugging purposes.
            if _LOGGER.isEnabledFor(ux.TRACE):
                _LOGGER.log(
                    ux.TRACE,
                    "interrupt %s occurred on thread %s, bot on thread %s will be notified to shut down shortly\n"
                    "Stacktrace for developer sanity:\n%s",
                    signum,
                    threading.get_native_id(),
                    loop_thread_id,
                    "".join(traceback.format_stack(frame)),
                )

            asyncio.run_coroutine_threadsafe(self._set_close_flag(signame, signum), loop)

        if enable_signal_handlers:
            for sig in signals:
                try:
                    signum = getattr(signal, sig)
                    signal.signal(signum, handle_os_interrupt)
                except AttributeError:
                    _LOGGER.log(ux.TRACE, "signal %s is not implemented on your platform", sig)

        try:
            loop.run_until_complete(
                self.start(
                    activity=activity,
                    afk=afk,
                    check_for_updates=check_for_updates,
                    idle_since=idle_since,
                    ignore_session_start_limit=ignore_session_start_limit,
                    large_threshold=large_threshold,
                    shard_ids=shard_ids,
                    shard_count=shard_count,
                    status=status,
                )
            )

            loop.run_until_complete(self.join())

        finally:
            try:
                loop.run_until_complete(self.close())

                if close_passed_executor and self._executor is not None:
                    _LOGGER.debug("shutting down executor %s", self._executor)
                    self._executor.shutdown(wait=True)
                    self._executor = None
            finally:
                if enable_signal_handlers:
                    for sig in signals:
                        try:
                            signum = getattr(signal, sig)
                            signal.signal(signum, signal.SIG_DFL)
                        except AttributeError:
                            # Signal not implemented probably. We should have logged this earlier.
                            pass

                if close_loop:
                    self._destroy_loop(loop)

                _LOGGER.info("successfully terminated")

                if propagate_interrupts and interrupt is not None:
                    raise interrupt