GraiaProject / Ariadne

一个优雅且完备的 Python QQ 自动化框架,基于 Mirai API HTTP v2。 Powered by Graia Project.
https://graia.cn/ariadne
GNU Affero General Public License v3.0
751 stars 45 forks source link

[Bug] 消息发送成功后发生异常导致`graia.ariadne.util.send.Safe`重复发送消息 #234

Closed EZForever closed 1 year ago

EZForever commented 1 year ago

问题

文档app.send_message()action参数可以决定当消息发送失败时该方法的行为,而graia.ariadne.util.send.Safe可以自动在发送失败后进行若干规避且重发消息。

https://github.com/GraiaProject/Ariadne/blob/4b8f52b897d35a69806c95d8255c31902a477dc0/src/graia/ariadne/util/send.py#L78-L82

问题在于app.send_message()在发送消息成功后仍有可能产生异常(虽然这种情况极其少见,例如日志记录故障之类),这种情况下app.send_message(action=Ignore)会像消息发送失败一样返回None,导致graia.ariadne.util.send.Safe多次试图重发已经发出的消息,造成简短的消息轰炸,并可能造成风控察觉。这个行为直接把Safe变成了不安全的。

如何复现

这里借用Python的一个bug python/cpython#104231 ,以及issue中举的一个例子,构造一个日志抛异常的情形。这种情形在现实应用中也可能出现,例如使用toml读取发送内容的时候。

import tomlkit # pip install tomlkit==0.11.8

from graia.ariadne.util.send import Safe
from graia.ariadne.entry import Ariadne, WebsocketClientConfig
from graia.ariadne.event.message import FriendMessage, MessageEvent

app = Ariadne(ariadne_config(
    ********,
    '********',
    WebsocketClientConfig('http://********')
))

@app.broadcast.receiver('FriendMessage')
async def on_metacmd(app: Ariadne, event: FriendMessage) -> None:
    message = tomlkit.value('"hello"')
    await app.send_message(event, message, action = Safe)

app.launch_blocking()

之后向机器人私聊任意消息,可以观察到hello被连续回复了六次。

预期行为

最直接的workaround是令graia.ariadne.util.send.Safe使用app.send_message(action=Strict),之后自行判断哪些异常是致命的,但这样在将来可能还是会出现语义问题。

考虑到app.send_message()的作用就是发送消息,消息发送成功就应当返回成功的结果,所以更根本的解决方案是将所有消息发送成功后出现的异常降级为warning,或者至少令其不要体现在异常或返回值上。

使用环境:

日志/截图

无。

BlueGlassBlock commented 1 year ago

@EZForever 请尝试使用最新开发版进行验证

考虑到 app.send_message() 的作用就是发送消息,消息发送成功就应当返回成功的结果,所以更根本的解决方案是将所有消息发送成功后出现的异常降级为warning,或者至少令其不要体现在异常或返回值上。

很遗憾,Python 不像其他语言可以预先了解所有可能的异常/错误,所以只能暂时先如此修复

EZForever commented 1 year ago

理解,这也不能算是Python语言的问题(当然Java只用Checked Exception的话当我没说),考虑到目前只有action=Safe会试图重发信息,只要这个可重试的异常列表是完备的,这个修复方案没问题。

不过issue这种情况的存在表示了app.send_message()的报错状态并不能完全代表消息发送成功与否,而这是反用户直觉的。在例如说有人自己使用action=Ignore然后判断返回值是否为None的情况下,可能会造成迷惑。建议更新action的文档app.send_message()的文档添加提示,并且列出实际代表消息发送失败的异常列表。