test: 修复 test_task_runner_lru_eviction 的竞态 flaky

根因:测试用 max_workers=1 串行执行 5 个任务,断言「前 2 个被淘汰」。
但 LRU 淘汰发生在 submit() 时,淘汰的是「当时最旧的非 running 任务」。
快机器上 t0 还在 running(被跳过),实际淘汰 t1/t2;慢机器(CI windows
3.12)上 t0 已完成,淘汰 t0/t1——取决于提交速度 vs 执行速度的竞态。

修复:不断言特定 task_id 被淘汰,改为验证不变量:
(1) 最后提交的任务一定存活(LRU 最近)
(2) 至少淘汰 2 个(5 - max_results 3)
(3) 存活任务数 ≤ max_results

本地跑 10 次全过,不再 flaky。
This commit is contained in:
Justin Gu
2026-07-07 15:47:13 +08:00
parent 27a8ab1c76
commit 6b51487477
+24 -9
View File
@@ -306,23 +306,38 @@ def test_task_runner_captures_failure():
def test_task_runner_lru_eviction():
"""超过上限应丢弃最旧任务。"""
"""超过上限应丢弃最旧的非 running 任务。
注意:淘汰发生在 submit 时,淘汰对象是「当时最旧的非 running 任务」。
用 max_workers=1 串行执行时,哪个任务被淘汰取决于提交速度 vs 执行速度
的竞态(快机器上 t0 还在 running 会被跳过,慢机器上 t0 已完成会被淘汰)。
所以本测试不断言「特定 task_id 被淘汰」,而是验证:
(1) 存活的 non-running 任务数 ≤ max_results
(2) 最后提交的任务一定存活(它是最近的,不可能被 LRU 淘汰)
(3) 至少有 2 个任务被淘汰(5 提交 - 3 上限 = 2)
"""
from easy_tdx.web.task_runner import BacktestTaskRunner
runner = BacktestTaskRunner(max_workers=1, max_results=3)
ids = [runner.submit(lambda: {"i": i}, description=f"t{i}") for i in range(5)]
# 等待全部完成
# 等待存活的任务全部完成(被淘汰的 peek 返回 None,跳过)
for _ in range(200):
if all(runner.peek(tid) and runner.peek(tid).status in ("done", "failed") for tid in ids):
alive = [tid for tid in ids if runner.peek(tid) is not None]
if all(runner.peek(tid).status in ("done", "failed") for tid in alive):
break
time.sleep(0.02)
# 前 2 个应被淘汰
assert runner.peek(ids[0]) is None
assert runner.peek(ids[1]) is None
# 后 3 个保留
assert runner.peek(ids[2]) is not None
assert runner.peek(ids[4]) is not None
# 最后提交的任务一定存活(LRU 最近,不可能被淘汰
assert runner.peek(ids[4]) is not None, "最后提交的任务不应被淘汰"
# 至少淘汰 2 个(5 提交 - max_results 3 = 2
surviving = [tid for tid in ids if runner.peek(tid) is not None]
evicted = [tid for tid in ids if runner.peek(tid) is None]
assert len(evicted) >= 2, f"应至少淘汰 2 个任务,实际淘汰 {len(evicted)}"
# 存活任务数不超过 max_resultsrunning 完成后)
assert len(surviving) <= 3, f"存活任务 {len(surviving)} 超过上限 3"
runner.shutdown()