fix(channels): close Discord file handle after upload (#3561)

send_file opened the attachment with a bare open() and never closed it,
leaking a file descriptor on every Discord file delivery. The handle was
also leaked on the failure path: when target.send raised, the except
branch logged and returned without closing fp. The "# noqa: SIM115"
suppressed the lint warning instead of fixing it.

Wrap open() in a with statement that stays open for the full upload —
the discord client reads fp while target.send runs on _discord_loop, and
once that future resolves the bytes are consumed, so closing here is
safe. This closes the handle on both the success and exception paths and
matches how telegram and feishu already handle their file uploads.

Adds regression tests asserting the handle is closed after send_file on
both the success and failure paths.

Refs #3544
This commit is contained in:
hataa
2026-06-13 23:27:17 +08:00
committed by GitHub
parent d23eac227f
commit 1783da42f4
2 changed files with 134 additions and 5 deletions
+8 -4
View File
@@ -205,10 +205,14 @@ class DiscordChannel(Channel):
return False
try:
fp = open(str(attachment.actual_path), "rb") # noqa: SIM115
file = self._discord_module.File(fp, filename=attachment.filename)
send_future = asyncio.run_coroutine_threadsafe(target.send(file=file), self._discord_loop)
await asyncio.wrap_future(send_future)
# Keep the file handle open only for the duration of the upload: discord.py
# reads ``fp`` while ``target.send`` runs on ``_discord_loop``; once that
# future resolves the bytes are consumed, so closing here is safe and avoids
# leaking the handle on both the success and failure paths.
with open(str(attachment.actual_path), "rb") as fp:
file = self._discord_module.File(fp, filename=attachment.filename)
send_future = asyncio.run_coroutine_threadsafe(target.send(file=file), self._discord_loop)
await asyncio.wrap_future(send_future)
logger.info("[Discord] file uploaded: %s", attachment.filename)
return True
except Exception: