Files
chongming-core/test/test_worker_suspect.py
T

92 lines
3.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
worker 超时冷静期(派发负反馈)测试
背景:半死副本(被 SIGKILL 的实例留下的孤儿 worker 等)只要还在服务发现里,
每次被随机选中都会白吃一整轮超时(默认 2s);worker_manager 只做"本次请求换副本
重试",没有跨请求记忆,于是 2 秒惩罚反复发生(实测一轮 31 条超时日志)。
修复:WorkerManager.mark_worker_suspect 给超时/无回复的副本记一段冷静期
(_suspect_until,TTL 30s),三条选副本路径(get_worker / _pick_from_instance /
_pick_other_worker)都优先跳过它,全在冷静期时退回全集(宁慢不缺)。
验证:
1. 标记后的副本跨请求不再被选中
2. 全部候选都处于冷静期时退回全集(不饿死)
3. TTL 到期自动恢复
4. 过期项会被清理(防长期运行膨胀)
5. 换副本路径同样避开冷静期
6. 源码兜底:超时/无回复分支确实调用了标记
运行: uv run python test/test_worker_suspect.py
"""
import asyncio
import sys
import time
from pathlib import Path
ROOT = Path(__file__).parent.parent
sys.path.insert(0, str(ROOT / "gateway" / "src"))
from chongming.app.core.worker_manager import WorkerManager # noqa: E402
failures = []
def check(name, cond, detail=""):
status = "PASS" if cond else "FAIL"
print(f"[{status}] {name}" + (f" — {detail}" if detail else ""))
if not cond:
failures.append(name)
# 跳过 __init__(依赖 zenoh 会话/缓存),只装配选副本所需的字段
m = WorkerManager.__new__(WorkerManager)
m._suspect_until = {}
m._workers_cache = {"demo": ["w1", "w2", "w3"]}
m._local_workers = {}
def sample(fn, n=40):
return {asyncio.run(fn()) for _ in range(n)}
# ========== 1. 标记后跨请求跳过 ==========
m.mark_worker_suspect("w1")
m.mark_worker_suspect("w2")
picked = sample(lambda: m.get_worker("demo"))
check("冷静期内的副本不再被选中", picked == {"w3"}, str(picked))
# ========== 2. 全在冷静期 → 退回全集 ==========
m.mark_worker_suspect("w3")
picked = sample(lambda: m.get_worker("demo"))
check("全部处于冷静期时退回全集(不饿死)", picked == {"w1", "w2", "w3"}, str(picked))
# ========== 3. TTL 到期恢复 ==========
m._suspect_until = {"w1": time.monotonic() - 0.1}
check("冷静期过期自动恢复", m._filter_suspects(["w1"]) == ["w1"])
# ========== 4. 过期项清理 ==========
m._suspect_until = {}
for i in range(40):
m.mark_worker_suspect(f"x{i}", ttl=-1.0) # 立即过期
check("过期项会被清理(防膨胀)", len(m._suspect_until) <= 32, str(len(m._suspect_until)))
# ========== 5. 换副本路径同样避开 ==========
m._suspect_until = {}
m.mark_worker_suspect("w1")
picked = sample(lambda: m._pick_other_worker("demo", []))
check("换副本路径同样避开冷静期", picked == {"w2", "w3"}, str(picked))
# ========== 6. 源码兜底:失败分支确实标记 ==========
src = (ROOT / "gateway" / "src" / "chongming" / "app" / "core" / "worker_manager.py").read_text(
encoding="utf-8"
)
n = src.count("self.mark_worker_suspect(worker_expr)")
check("超时/无回复分支都调用了标记", n >= 2, f"{n} 处")
print("\n" + ("=" * 40))
if failures:
print(f"❌ {len(failures)} 项未通过: {failures}")
sys.exit(1)
print("✅ 全部通过")