Skip to content

fix: 工程化收敛并移除 ASYNC230/ASYNC240 忽略 (#5729)#8499

Closed
Lemon-OvO wants to merge 3 commits into
AstrBotDevs:masterfrom
Lemon-OvO:master
Closed

fix: 工程化收敛并移除 ASYNC230/ASYNC240 忽略 (#5729)#8499
Lemon-OvO wants to merge 3 commits into
AstrBotDevs:masterfrom
Lemon-OvO:master

Conversation

@Lemon-OvO
Copy link
Copy Markdown

  • test(skills): align sandbox cache tests with readonly behavior

  • ci(release): enforce core quality gate before publish

  • ci: enforce locked dependency installs in workflows

  • security: remove curl-pipe-shell installs

  • chore: align project python baseline to 3.12

  • ci(dashboard): add explicit typecheck gate

  • chore(pre-commit): align ruff hook version with project

  • ci(codeql): add javascript-typescript analysis

  • chore(ruff): defer py312 migration lint rules

  • fix: resolve ruff violations without new ignores

  • fix: resolve ASYNC230 and ASYNC240 without ignores

  • fix(auth): replace utcnow with timezone-aware UTC now

  • fix: avoid blocking file read in file_to_base64

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

* test(skills): align sandbox cache tests with readonly behavior

* ci(release): enforce core quality gate before publish

* ci: enforce locked dependency installs in workflows

* security: remove curl-pipe-shell installs

* chore: align project python baseline to 3.12

* ci(dashboard): add explicit typecheck gate

* chore(pre-commit): align ruff hook version with project

* ci(codeql): add javascript-typescript analysis

* chore(ruff): defer py312 migration lint rules

* fix: resolve ruff violations without new ignores

* fix: resolve ASYNC230 and ASYNC240 without ignores

* fix(auth): replace utcnow with timezone-aware UTC now

* fix: avoid blocking file read in file_to_base64
@dosubot dosubot Bot added the size:L This PR changes 100-499 lines, ignoring generated files. label Jun 2, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @Lemon-OvO, your pull request is larger than the review limit of 150000 diff characters

@dosubot dosubot Bot added area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. labels Jun 2, 2026
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request upgrades the codebase to Python 3.12, updates Dockerfile and pre-commit configurations, and migrates various synchronous blocking I/O operations to asynchronous equivalents or asyncio.to_thread wrappers. It also adopts Python 3.12 type parameter syntax and standardizes timeout parameter names to timeout_seconds. The review feedback recommends further optimizing performance and non-blocking behavior by wrapping remaining synchronous calls (such as shutil.copy2, os.remove, and os.makedirs) in asyncio.to_thread, utilizing aiofiles to avoid thread-switching overhead during chunked file downloads, and updating legacy parameter names to match the new timeout_seconds convention.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

config_content = zf.read("config/cmd_config.json")
# 备份现有配置
if os.path.exists(self.config_path):
if await asyncio.to_thread(os.path.exists, self.config_path):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

import_all 异步函数中,shutil.copy2 是一个同步阻塞的 I/O 操作。为了彻底解决 ASYNC230/ASYNC240 带来的事件循环阻塞问题,建议将 shutil.copy2 包装在 asyncio.to_thread 中执行,例如:

await asyncio.to_thread(shutil.copy2, self.config_path, backup_path)

logger.info(f"[EdgeTTS] 返回值(0代表成功): {p.returncode}")

os.remove(mp3_path)
if (
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

get_audio 异步函数中,os.remove 是一个同步阻塞的文件删除操作。为了避免阻塞事件循环,建议将其包装在 asyncio.to_thread 中执行。

            await asyncio.to_thread(os.remove, mp3_path)

Comment on lines +116 to +117
if await asyncio.to_thread(os.path.exists, mp3_path):
os.remove(mp3_path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在异常处理块中,os.remove 同样是同步阻塞的。建议将其包装在 asyncio.to_thread 中以保持非阻塞特性。

Suggested change
if await asyncio.to_thread(os.path.exists, mp3_path):
os.remove(mp3_path)
if await asyncio.to_thread(os.path.exists, mp3_path):
await asyncio.to_thread(os.remove, mp3_path)

Comment on lines +125 to +126
if await asyncio.to_thread(os.path.exists, mp3_path):
os.remove(mp3_path)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在异常处理块中,os.remove 同样是同步阻塞的。建议将其包装在 asyncio.to_thread 中以保持非阻塞特性。

Suggested change
if await asyncio.to_thread(os.path.exists, mp3_path):
os.remove(mp3_path)
if await asyncio.to_thread(os.path.exists, mp3_path):
await asyncio.to_thread(os.remove, mp3_path)

Comment on lines +448 to +450
if converted_audio_path and await asyncio.to_thread(
os.path.exists, converted_audio_path
):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

_send_audio_message 异步函数中,清理临时音频文件时调用的 os.remove(converted_audio_path) 是一个同步阻塞操作。建议将其包装在 asyncio.to_thread 中执行,例如:

await asyncio.to_thread(os.remove, converted_audio_path)

Comment thread astrbot/core/utils/io.py
Comment on lines +183 to +184
file_obj = await asyncio.to_thread(Path(path).open, "wb")
try:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在 SSL 验证失败的 fallback 下载逻辑中,同样存在在循环中频繁调用 asyncio.to_thread 写入小数据块的性能开销问题。建议采用与主下载逻辑相同的 aiofiles 异步写入方案进行优化。

Comment on lines +17 to +28
input_data = await asyncio.to_thread(Path(silk_path).read_bytes)
if input_data.startswith(b"\x02"):
input_data = input_data[1:]
input_io = BytesIO(input_data)
output_io = BytesIO()
pysilk.decode(input_io, output_io, 24000)
output_io.seek(0)
with wave.open(output_path, "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(24000)
wav.writeframes(output_io.read())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

tencent_silk_to_wav 异步函数中,pysilk.decode 以及 wave.open 写入音频帧都是同步阻塞的 I/O 和 CPU 密集型操作。为了彻底避免阻塞事件循环,建议将整个转换逻辑封装为一个内部同步函数,并通过 asyncio.to_thread 异步执行。

Suggested change
input_data = await asyncio.to_thread(Path(silk_path).read_bytes)
if input_data.startswith(b"\x02"):
input_data = input_data[1:]
input_io = BytesIO(input_data)
output_io = BytesIO()
pysilk.decode(input_io, output_io, 24000)
output_io.seek(0)
with wave.open(output_path, "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(24000)
wav.writeframes(output_io.read())
def _sync_silk_to_wav():
input_data = Path(silk_path).read_bytes()
if input_data.startswith(b"\x02"):
input_data = input_data[1:]
input_io = BytesIO(input_data)
output_io = BytesIO()
pysilk.decode(input_io, output_io, 24000)
output_io.seek(0)
with wave.open(output_path, "wb") as wav:
wav.setnchannels(1)
wav.setsampwidth(2)
wav.setframerate(24000)
wav.writeframes(output_io.read())
await asyncio.to_thread(_sync_silk_to_wav)

return silk_b64, duration # 已是秒
finally:
if os.path.exists(wav_path) and wav_path != audio_path:
if await asyncio.to_thread(os.path.exists, wav_path) and wav_path != audio_path:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

finally 清理块中,os.remove 是同步阻塞的文件删除操作。建议将其包装在 asyncio.to_thread 中执行,以确保整个清理过程完全非阻塞。例如:

await asyncio.to_thread(os.remove, wav_path)
await asyncio.to_thread(os.remove, silk_path)


# 检查文件是否存在并注册令牌
if os.path.exists(logo_file_path):
if await asyncio.to_thread(os.path.exists, logo_file_path):
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

在调用 file_token_service.register_file 时,使用了旧的参数名 timeout=3600。虽然 register_file 内部通过 **kwargs 实现了向后兼容,但为了代码的整洁和一致性,建议直接使用新的参数名 timeout_seconds=3600

os.makedirs(local_dir, exist_ok=True)
with open(local_path, "wb") as f:
f.write(cast(bytes, content))
await asyncio.to_thread(Path(local_path).write_bytes, cast(bytes, content))
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

download_file 异步函数中,第 489 行的 os.makedirs(local_dir, exist_ok=True) 是一个同步阻塞的目录创建操作。为了保持非阻塞特性,建议将其包装在 asyncio.to_thread 中执行,或者使用 Path(local_dir).mkdir(parents=True, exist_ok=True) 并包装在 asyncio.to_thread 中。

@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. and removed size:L This PR changes 100-499 lines, ignoring generated files. labels Jun 2, 2026
Copy link
Copy Markdown
Member

@Dt8333 Dt8333 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

还请整理一下pr,清理无关的文件,并描述一下pr修改了什么

@Soulter
Copy link
Copy Markdown
Member

Soulter commented Jun 2, 2026

PR 改动太多了,无力审核,关闭。有大的改动请先邮箱告知。

@Soulter Soulter closed this Jun 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core The bug / feature is about astrbot's core, backend area:provider The bug / feature is about AI Provider, Models, LLM Agent, LLM Agent Runner. size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants