Skip to content

Commit c3d5109

Browse files
committed
more elegant shutdown, use run_in_executor
1 parent b2089ee commit c3d5109

File tree

3 files changed

+62
-28
lines changed

3 files changed

+62
-28
lines changed

bot.py

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
__version__ = '2.12.5'
2626

2727
import asyncio
28+
import traceback
2829
import os
2930
from datetime import datetime
3031
from textwrap import dedent
@@ -39,7 +40,7 @@
3940
from emoji import UNICODE_EMOJI
4041
from motor.motor_asyncio import AsyncIOMotorClient
4142

42-
from core.changelog import ChangeLog
43+
from core.changelog import Changelog
4344
from core.clients import ModmailApiClient, SelfHostedClient, PluginDatabaseClient
4445
from core.config import ConfigManager
4546
from core.models import Bot
@@ -130,6 +131,7 @@ def _load_extensions(self):
130131
self.load_extension(cog)
131132
except Exception:
132133
print(f'Failed to load {cog}')
134+
traceback.print_exc()
133135

134136
async def is_owner(self, user):
135137
allowed = {int(x) for x in
@@ -144,8 +146,21 @@ async def logout(self):
144146

145147
def run(self, *args, **kwargs):
146148
try:
147-
super().run(self.token, *args, **kwargs)
149+
self.loop.run_until_complete(self.start(self.token))
150+
except discord.LoginFailure:
151+
print('Invalid token')
152+
except KeyboardInterrupt:
153+
pass
154+
except Exception:
155+
print('Fatal exception')
156+
traceback.print_exc()
148157
finally:
158+
self.data_task.cancel()
159+
self.autoupdate_task.cancel()
160+
161+
self.loop.run_until_complete(self.logout())
162+
self.loop.run_until_complete(self.session.close())
163+
self.loop.close()
149164
print(Fore.RED + ' - Shutting down bot' + Style.RESET_ALL)
150165

151166
@property
@@ -293,7 +308,7 @@ async def setup_indexes(self):
293308
await coll.create_index([
294309
('messages.content', 'text'),
295310
('messages.author.name', 'text')
296-
])
311+
])
297312

298313
async def on_ready(self):
299314
"""Bot startup, sets uptime."""
@@ -658,7 +673,7 @@ async def autoupdate_loop(self):
658673
print(LINE)
659674
return
660675

661-
while True:
676+
while not self.is_closed():
662677
metadata = await self.api.get_metadata()
663678

664679
if metadata['latest_version'] != self.version:

cogs/plugins.py

Lines changed: 42 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
import asyncio
12
import importlib
2-
import subprocess
3+
import os
34
import shutil
5+
import stat
6+
import subprocess
47

58
from colorama import Fore, Style
69
from discord.ext import commands
710

8-
from core.decorators import owner_only
911
from core.models import Bot
1012

1113

@@ -24,6 +26,10 @@ def __init__(self, bot: Bot):
2426
self.bot = bot
2527
self.bot.loop.create_task(self.download_initial_plugins())
2628

29+
def _asubprocess_run(self, cmd):
30+
return subprocess.run(cmd, shell=True, check=True,
31+
capture_output=True)
32+
2733
@staticmethod
2834
def parse_plugin(name):
2935
# returns: (username, repo, plugin_name)
@@ -42,18 +48,20 @@ async def download_initial_plugins(self):
4248
try:
4349
await self.download_plugin_repo(*parsed_plugin[:-1])
4450
except DownloadError as exc:
45-
msg = 'Unable to download plugin '
46-
msg += f'({parsed_plugin[0]}/{parsed_plugin[1]} - {exc}'
51+
msg = f'{exc} ({parsed_plugin[0]}/{parsed_plugin[1]} - {exc}'
4752
print(Fore.RED + msg + Style.RESET_ALL)
4853

4954
await self.load_plugin(*parsed_plugin)
5055

51-
@staticmethod
52-
async def download_plugin_repo(username, repo):
56+
async def download_plugin_repo(self, username, repo):
5357
try:
5458
cmd = f'git clone https://github.com/{username}/{repo} '
5559
cmd += f'plugins/{username}-{repo} -q'
56-
subprocess.run(cmd, check=True, capture_output=True)
60+
await self.bot.loop.run_in_executor(
61+
None,
62+
self._asubprocess_run,
63+
cmd
64+
)
5765
# -q (quiet) so there's no terminal output unless there's an error
5866
except subprocess.CalledProcessError as exc:
5967
error = exc.stderr.decode('utf-8').strip()
@@ -63,17 +71,17 @@ async def download_plugin_repo(username, repo):
6371
raise DownloadError(error) from exc
6472

6573
async def load_plugin(self, username, repo, plugin_name):
74+
ext = f'plugins.{username}-{repo}.{plugin_name}.{plugin_name}'
6675
try:
67-
ext = f'plugins.{username}-{repo}.{plugin_name}.{plugin_name}'
6876
self.bot.load_extension(ext)
6977
except ModuleNotFoundError as exc:
7078
raise DownloadError('Invalid plugin structure') from exc
7179
else:
72-
msg = f'Loading plugins.{username}-{repo}.{plugin_name}'
80+
msg = f'Loaded plugins.{username}-{repo}.{plugin_name}'
7381
print(Fore.LIGHTCYAN_EX + msg + Style.RESET_ALL)
7482

7583
@commands.group(aliases=['plugins'])
76-
@owner_only()
84+
@commands.is_owner()
7785
async def plugin(self, ctx):
7886
"""Plugin handler. Controls the plugins in the bot."""
7987
if ctx.invoked_subcommand is None:
@@ -83,7 +91,7 @@ async def plugin(self, ctx):
8391
@plugin.command()
8492
async def add(self, ctx, *, plugin_name):
8593
"""Adds a plugin"""
86-
# parsing plugin_name
94+
message = await ctx.send('Downloading plugin...')
8795
async with ctx.typing():
8896
if len(plugin_name.split('/')) >= 3:
8997
parsed_plugin = self.parse_plugin(plugin_name)
@@ -95,6 +103,7 @@ async def add(self, ctx, *, plugin_name):
95103
f'Unable to fetch plugin from Github: {exc}'
96104
)
97105

106+
importlib.invalidate_caches()
98107
try:
99108
await self.load_plugin(*parsed_plugin)
100109
except DownloadError as exc:
@@ -106,11 +115,11 @@ async def add(self, ctx, *, plugin_name):
106115
self.bot.config.plugins.append(plugin_name)
107116
await self.bot.config.update()
108117

109-
await ctx.send('Plugin installed. Any plugin that '
110-
'you install is of your OWN RISK.')
118+
await message.edit(content='Plugin installed. Any plugin that '
119+
'you install is of your OWN RISK.')
111120
else:
112-
await ctx.send('Invalid plugin name format. '
113-
'Use username/repo/plugin.')
121+
await message.edit(content='Invalid plugin name format. '
122+
'Use username/repo/plugin.')
114123

115124
@plugin.command()
116125
async def remove(self, ctx, *, plugin_name):
@@ -127,9 +136,13 @@ async def remove(self, ctx, *, plugin_name):
127136
if not any(i.startswith(f'{username}/{repo}')
128137
for i in self.bot.config.plugins):
129138
# if there are no more of such repos, delete the folder
130-
shutil.rmtree(f'plugins/{username}-{repo}',
131-
ignore_errors=True)
132-
await ctx.send('')
139+
def onerror(func, path, exc_info):
140+
if not os.access(path, os.W_OK):
141+
# Is the error an access error ?
142+
os.chmod(path, stat.S_IWUSR)
143+
func(path)
144+
145+
shutil.rmtree(f'plugins/{username}-{repo}', onerror=onerror)
133146
except Exception as exc:
134147
print(exc)
135148
self.bot.config.plugins.append(plugin_name)
@@ -143,12 +156,16 @@ async def remove(self, ctx, *, plugin_name):
143156

144157
@plugin.command()
145158
async def update(self, ctx, *, plugin_name):
159+
"""Updates a certain plugin"""
146160
async with ctx.typing():
147161
username, repo, name = self.parse_plugin(plugin_name)
148162
try:
149163
cmd = f'cd plugins/{username}-{repo} && git pull'
150-
cmd = subprocess.run(cmd, shell=True, check=True,
151-
capture_output=True)
164+
cmd = await self.bot.loop.run_in_executor(
165+
None,
166+
self._asubprocess_run,
167+
cmd
168+
)
152169
except subprocess.CalledProcessError as exc:
153170
error = exc.stdout.decode('utf8').strip()
154171
await ctx.send(f'Error when updating: {error}')
@@ -159,15 +176,17 @@ async def update(self, ctx, *, plugin_name):
159176
# repo was updated locally, now perform the cog reload
160177
ext = f'plugins.{username}-{repo}.{name}.{name}'
161178
importlib.reload(importlib.import_module(ext))
162-
self.bot.unload_extension(ext)
163-
self.bot.load_extension(ext)
179+
self.load_plugin(username, repo, name)
164180

165181
await ctx.send(f'```\n{output}\n```')
166182

167183
@plugin.command(name='list')
168184
async def list_(self, ctx):
169185
"""Shows a list of currently enabled plugins"""
170-
await ctx.send('```\n' + '\n'.join(self.bot.config.plugins) + '\n```')
186+
if self.bot.config.plugins:
187+
await ctx.send('```\n' + '\n'.join(self.bot.config.plugins) + '\n```')
188+
else:
189+
await ctx.send('No plugins installed')
171190

172191

173192
def setup(bot):

core/config.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ class ConfigManager(ConfigManagerABC):
3232
# threads
3333
'snippets', 'notification_squad', 'subscriptions', 'closures',
3434
# misc
35-
'aliases', 'plugins
35+
'aliases', 'plugins'
3636
}
3737

3838
protected_keys = {

0 commit comments

Comments
 (0)