mirror of
https://github.com/suyiiyii/nonebot-bison.git
synced 2026-05-09 18:27:56 +08:00
🐛 Bilibili调度新增回避策略 (#573)
* 🐛 将Bilibili的调度速度降低到60s * ✨ 增加回避策略 * ✨ 降低轮询间隔,增加回避次数,抛出阶段随机刷新 * ♻️ 更清晰的调度逻辑实现 * 🐛 兼容3.10的NamedTuple多继承 * ♻️ 合并重复逻辑 * ♻️ ctx放入fsm * 🐛 测试并调整逻辑 * 🐛 补全类型标注 * ♻️ 添加Condition和State.on_exit/on_enter,以实现自动状态切换 * ✅ 调整测试 * 🐛 私有化命名方法 * 🔊 调整补充日志 * 🐛 添加测试后清理 * ✏️ fix typing typo
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
import random
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
import respx
|
||||
import pytest
|
||||
from loguru import logger
|
||||
from nonebug.app import App
|
||||
from httpx import URL, Response
|
||||
from freezegun import freeze_time
|
||||
from pytest_mock import MockerFixture
|
||||
from nonebot.compat import model_dump, type_validate_python
|
||||
|
||||
from .utils import get_json
|
||||
@@ -55,12 +59,15 @@ def without_dynamic(app: App):
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_retry_for_352(app: App):
|
||||
async def test_retry_for_352(app: App, mocker: MockerFixture):
|
||||
from nonebot_bison.post import Post
|
||||
from nonebot_bison.types import Target, RawPost
|
||||
from nonebot_bison.platform.platform import NewMessage
|
||||
from nonebot_bison.types import Target, RawPost, ApiError
|
||||
from nonebot_bison.platform.bilibili.platforms import ApiCode352Error
|
||||
from nonebot_bison.utils import ClientManager, ProcessContext, http_client
|
||||
from nonebot_bison.platform.bilibili.platforms import MAX_352_RETRY_COUNT, ApiCode352Error, retry_for_352
|
||||
from nonebot_bison.platform.bilibili.retry import RetryAddon, RetryState, _retry_fsm, retry_for_352
|
||||
|
||||
mocker.patch.object(random, "random", return_value=0.0) # 稳定触发RAISE阶段的随缘刷新
|
||||
|
||||
now = time()
|
||||
raw_post_1 = {"id": 1, "text": "p1", "date": now, "tags": ["tag1"], "category": 1}
|
||||
@@ -118,18 +125,26 @@ async def test_retry_for_352(app: App):
|
||||
refresh_client_call_count = 0
|
||||
|
||||
async def get_client(self, target: Target | None):
|
||||
logger.debug(f"call get_client: {target}, {datetime.now()}")
|
||||
logger.debug(f"times: {self.get_client_call_count} + 1")
|
||||
self.get_client_call_count += 1
|
||||
return http_client()
|
||||
|
||||
async def get_client_for_static(self):
|
||||
logger.debug(f"call get_client_for_static: {datetime.now()}")
|
||||
logger.debug(f"times: {self.get_client_for_static_call_count} + 1")
|
||||
self.get_client_for_static_call_count += 1
|
||||
return http_client()
|
||||
|
||||
async def get_query_name_client(self):
|
||||
logger.debug(f"call get_query_name_client: {datetime.now()}")
|
||||
logger.debug(f"times: {self.get_query_name_client_call_count} + 1")
|
||||
self.get_query_name_client_call_count += 1
|
||||
return http_client()
|
||||
|
||||
async def refresh_client(self):
|
||||
logger.debug(f"call refresh_client: {datetime.now()}")
|
||||
logger.debug(f"times: {self.refresh_client_call_count} + 1")
|
||||
self.refresh_client_call_count += 1
|
||||
|
||||
fakebili = MockPlatform(ProcessContext(MockClientManager()))
|
||||
@@ -141,29 +156,83 @@ async def test_retry_for_352(app: App):
|
||||
assert client_mgr.refresh_client_call_count == 0
|
||||
|
||||
# 无异常
|
||||
res: list[dict[str, Any]] = await fakebili.get_sub_list(Target("1")) # type: ignore
|
||||
res: list[dict[str, Any]] = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert len(res) == 1
|
||||
assert res[0]["id"] == 1
|
||||
assert client_mgr.get_client_call_count == 1
|
||||
assert client_mgr.refresh_client_call_count == 0
|
||||
|
||||
res = await fakebili.get_sub_list(Target("1")) # type: ignore
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert len(res) == 2
|
||||
assert res[0]["id"] == 1
|
||||
assert res[1]["id"] == 2
|
||||
assert client_mgr.get_client_call_count == 2
|
||||
assert client_mgr.refresh_client_call_count == 0
|
||||
|
||||
# 有异常
|
||||
addon = RetryAddon()
|
||||
|
||||
# 异常直到最终报错
|
||||
test_state_list: list[RetryState] = [RetryState.NROMAL] + [RetryState.REFRESH] * addon.max_refresh_count
|
||||
for _ in range(addon.max_backoff_count):
|
||||
test_state_list += [RetryState.BACKOFF] * 2
|
||||
test_state_list += [RetryState.REFRESH] * addon.max_refresh_count
|
||||
test_state_list += [RetryState.RAISE] * 2
|
||||
|
||||
freeze_start = datetime(2024, 6, 19, 0, 0, 0, 0)
|
||||
timedelta_length = addon.backoff_timedelta
|
||||
|
||||
fakebili.set_raise352(True)
|
||||
for i in range(MAX_352_RETRY_COUNT):
|
||||
res1: list[dict[str, Any]] = await fakebili.get_sub_list(Target("1")) # type: ignore
|
||||
assert len(res1) == 0
|
||||
assert client_mgr.get_client_call_count == 3 + i
|
||||
assert client_mgr.refresh_client_call_count == i + 1
|
||||
# 超过最大重试次数,抛出异常
|
||||
with pytest.raises(ApiError):
|
||||
await fakebili.get_sub_list(Target("1"))
|
||||
|
||||
for state in test_state_list:
|
||||
logger.info(f"\n\nnow state should be {state}")
|
||||
assert _retry_fsm.current_state == state
|
||||
|
||||
with freeze_time(freeze_start):
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
|
||||
if state == RetryState.BACKOFF:
|
||||
freeze_start += timedelta_length * (_retry_fsm.addon.backoff_count + 1) ** 2
|
||||
|
||||
assert client_mgr.refresh_client_call_count == 4 * 3 + 3 # refresh + raise
|
||||
assert client_mgr.get_client_call_count == 2 + 4 * 3 + 3 # previous + refresh + raise
|
||||
|
||||
# 重置回正常状态
|
||||
fakebili.set_raise352(False)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert res
|
||||
|
||||
# REFRESH阶段中途正常返回
|
||||
test_state_list2 = [RetryState.NROMAL, RetryState.REFRESH, RetryState.NROMAL]
|
||||
for idx, _ in enumerate(test_state_list2):
|
||||
if idx == len(test_state_list2) - 1:
|
||||
fakebili.set_raise352(False)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert res
|
||||
else:
|
||||
fakebili.set_raise352(True)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
|
||||
fakebili.set_raise352(False)
|
||||
# BACKOFF阶段在回避时间中
|
||||
test_state_list3 = [RetryState.NROMAL] + [RetryState.REFRESH] * addon.max_refresh_count + [RetryState.BACKOFF]
|
||||
for idx, _ in enumerate(test_state_list3):
|
||||
if idx == len(test_state_list3) - 1:
|
||||
fakebili.set_raise352(False)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
else:
|
||||
fakebili.set_raise352(True)
|
||||
res = await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
assert not res
|
||||
|
||||
# 测试重置
|
||||
await _retry_fsm.reset()
|
||||
|
||||
await fakebili.get_sub_list(Target("t1")) # type: ignore
|
||||
|
||||
await _retry_fsm.reset()
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
|
||||
Reference in New Issue
Block a user