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
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
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
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()
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