Skip to content

Commit b008d8a

Browse files
committed
better run command, 3.9.1
1 parent 5efdbbe commit b008d8a

File tree

3 files changed

+98
-26
lines changed

3 files changed

+98
-26
lines changed

CHANGELOG.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
66
This project mostly adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html);
77
however, insignificant breaking changes do not guarantee a major version bump, see the reasoning [here](https://github.com/kyb3r/modmail/issues/319). If you're a plugin developer, note the "BREAKING" section.
88

9+
# v3.9.1
10+
11+
### Internal
12+
13+
- `bot.run` now more gracefully handles closing, similar to how discord.py handles it.
14+
- No longer displays `PrivilegedIntentsRequired` exception when exiting when presence intent is disabled.
15+
916
# v3.9.0
1017

1118
### Breaking

bot.py

Lines changed: 90 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
__version__ = "3.9.0"
1+
__version__ = "3.9.1"
22

33

44
import asyncio
55
import copy
66
import logging
77
import os
88
import re
9+
import signal
910
import sys
1011
import typing
1112
from datetime import datetime
@@ -184,40 +185,104 @@ def db(self):
184185
async def get_prefix(self, message=None):
185186
return [self.prefix, f"<@{self.user.id}> ", f"<@!{self.user.id}> "]
186187

187-
def run(self, *args, **kwargs):
188+
def run(self):
189+
loop = self.loop
190+
188191
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:
191195
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():
202198
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)
204217
except discord.PrivilegedIntentsRequired:
205218
logger.critical(
206219
"Privileged intents are not explicitly granted in the discord developers dashboard."
207220
)
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:
213247
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+
214272
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())
218276
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
221286

222287
@property
223288
def bot_owner_ids(self):

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ exclude = '''
2121

2222
[tool.poetry]
2323
name = 'Modmail'
24-
version = '3.8.6'
24+
version = '3.9.1'
2525
description = "Modmail is similar to Reddit's Modmail, both in functionality and purpose. It serves as a shared inbox for server staff to communicate with their users in a seamless way."
2626
license = 'AGPL-3.0-only'
2727
authors = [

0 commit comments

Comments
 (0)