|
1 |
| -__version__ = "3.9.0" |
| 1 | +__version__ = "3.9.1" |
2 | 2 |
|
3 | 3 |
|
4 | 4 | import asyncio
|
5 | 5 | import copy
|
6 | 6 | import logging
|
7 | 7 | import os
|
8 | 8 | import re
|
| 9 | +import signal |
9 | 10 | import sys
|
10 | 11 | import typing
|
11 | 12 | from datetime import datetime
|
@@ -184,40 +185,104 @@ def db(self):
|
184 | 185 | async def get_prefix(self, message=None):
|
185 | 186 | return [self.prefix, f"<@{self.user.id}> ", f"<@!{self.user.id}> "]
|
186 | 187 |
|
187 |
| - def run(self, *args, **kwargs): |
| 188 | + def run(self): |
| 189 | + loop = self.loop |
| 190 | + |
188 | 191 | try:
|
189 |
| - self.loop.run_until_complete(self.start(self.token)) |
190 |
| - except KeyboardInterrupt: |
| 192 | + loop.add_signal_handler(signal.SIGINT, lambda: loop.stop()) |
| 193 | + loop.add_signal_handler(signal.SIGTERM, lambda: loop.stop()) |
| 194 | + except NotImplementedError: |
191 | 195 | pass
|
192 |
| - except discord.LoginFailure: |
193 |
| - logger.critical("Invalid token") |
194 |
| - except discord.PrivilegedIntentsRequired: |
195 |
| - intents = discord.Intents.default() |
196 |
| - intents.members = True |
197 |
| - # Try again with members intent |
198 |
| - self._connection._intents = intents |
199 |
| - logger.warning( |
200 |
| - "Attempting to login with only the server members privileged intent. Some plugins might not work correctly." |
201 |
| - ) |
| 196 | + |
| 197 | + async def runner(): |
202 | 198 | try:
|
203 |
| - self.loop.run_until_complete(self.start(self.token)) |
| 199 | + retry_intents = False |
| 200 | + try: |
| 201 | + await self.start(self.token) |
| 202 | + except discord.PrivilegedIntentsRequired: |
| 203 | + retry_intents = True |
| 204 | + if retry_intents: |
| 205 | + await self.http.close() |
| 206 | + if self.ws is not None and self.ws.open: |
| 207 | + await self.ws.close(code=1000) |
| 208 | + self._ready.clear() |
| 209 | + intents = discord.Intents.default() |
| 210 | + intents.members = True |
| 211 | + # Try again with members intent |
| 212 | + self._connection._intents = intents |
| 213 | + logger.warning( |
| 214 | + "Attempting to login with only the server members privileged intent. Some plugins might not work correctly." |
| 215 | + ) |
| 216 | + await self.start(self.token) |
204 | 217 | except discord.PrivilegedIntentsRequired:
|
205 | 218 | logger.critical(
|
206 | 219 | "Privileged intents are not explicitly granted in the discord developers dashboard."
|
207 | 220 | )
|
208 |
| - except Exception: |
209 |
| - logger.critical("Fatal exception", exc_info=True) |
210 |
| - finally: |
211 |
| - self.loop.run_until_complete(self.logout()) |
212 |
| - for task in asyncio.all_tasks(self.loop): |
| 221 | + except discord.LoginFailure: |
| 222 | + logger.critical("Invalid token") |
| 223 | + except Exception: |
| 224 | + logger.critical("Fatal exception", exc_info=True) |
| 225 | + finally: |
| 226 | + if not self.is_closed(): |
| 227 | + await self.close() |
| 228 | + if self._session: |
| 229 | + await self._session.close() |
| 230 | + |
| 231 | + def stop_loop_on_completion(f): |
| 232 | + loop.stop() |
| 233 | + |
| 234 | + def _cancel_tasks(): |
| 235 | + if sys.version_info < (3, 8): |
| 236 | + task_retriever = asyncio.Task.all_tasks |
| 237 | + else: |
| 238 | + task_retriever = asyncio.all_tasks |
| 239 | + |
| 240 | + tasks = {t for t in task_retriever(loop=loop) if not t.done()} |
| 241 | + |
| 242 | + if not tasks: |
| 243 | + return |
| 244 | + |
| 245 | + logger.info('Cleaning up after %d tasks.', len(tasks)) |
| 246 | + for task in tasks: |
213 | 247 | task.cancel()
|
| 248 | + |
| 249 | + loop.run_until_complete(asyncio.gather(*tasks, return_exceptions=True)) |
| 250 | + logger.info('All tasks finished cancelling.') |
| 251 | + |
| 252 | + for task in tasks: |
| 253 | + if task.cancelled(): |
| 254 | + continue |
| 255 | + if task.exception() is not None: |
| 256 | + loop.call_exception_handler({ |
| 257 | + 'message': 'Unhandled exception during Client.run shutdown.', |
| 258 | + 'exception': task.exception(), |
| 259 | + 'task': task |
| 260 | + }) |
| 261 | + |
| 262 | + future = asyncio.ensure_future(runner(), loop=loop) |
| 263 | + future.add_done_callback(stop_loop_on_completion) |
| 264 | + try: |
| 265 | + loop.run_forever() |
| 266 | + except KeyboardInterrupt: |
| 267 | + logger.info('Received signal to terminate bot and event loop.') |
| 268 | + finally: |
| 269 | + future.remove_done_callback(stop_loop_on_completion) |
| 270 | + logger.info('Cleaning up tasks.') |
| 271 | + |
214 | 272 | try:
|
215 |
| - self.loop.run_until_complete(asyncio.gather(*asyncio.all_tasks(self.loop))) |
216 |
| - except asyncio.CancelledError: |
217 |
| - logger.debug("All pending tasks has been cancelled.") |
| 273 | + _cancel_tasks() |
| 274 | + if sys.version_info >= (3, 6): |
| 275 | + loop.run_until_complete(loop.shutdown_asyncgens()) |
218 | 276 | finally:
|
219 |
| - self.loop.run_until_complete(self.session.close()) |
220 |
| - logger.error(" - Shutting down bot - ") |
| 277 | + logger.info('Closing the event loop.') |
| 278 | + loop.close() |
| 279 | + |
| 280 | + if not future.cancelled(): |
| 281 | + try: |
| 282 | + return future.result() |
| 283 | + except KeyboardInterrupt: |
| 284 | + # I am unsure why this gets raised here but suppress it anyway |
| 285 | + return None |
221 | 286 |
|
222 | 287 | @property
|
223 | 288 | def bot_owner_ids(self):
|
|
0 commit comments