mirror of
https://ghfast.top/https://github.com/aeroxw/tick-stock-panel.git
synced 2026-09-12 17:54:15 +08:00
fix(monitor): 规则表并发迭代加快照, 防评估线程与 API 线程竞态
evaluate()/has_rule_type() 跑在行情轮询线程, 直接迭代 self._rules 时若 API 线程并发 add_rule/remove_rule 会抛 RuntimeError (dictionary changed size during iteration), 该轮全部告警被上层 except 吞掉静默丢失。统一 list() 快照后迭代; set_rules 改为构建 完整 dict 后原子替换, 避免读到半装载状态; remove_rule 重建 _last_fire 同理。
This commit is contained in:
@@ -350,11 +350,16 @@ class MonitorRuleEngine:
|
||||
|
||||
# ── 规则管理 ───────────────────────────────────────
|
||||
def set_rules(self, rules: list[dict]) -> None:
|
||||
"""批量设置规则 (覆盖)。用于启动时 reload。"""
|
||||
self._rules = {}
|
||||
"""批量设置规则 (覆盖)。用于启动时 reload。
|
||||
|
||||
先构建完整 dict 再原子替换 self._rules, 避免评估线程 (行情轮询)
|
||||
读到装载了一半的规则表。
|
||||
"""
|
||||
new_rules: dict[str, dict] = {}
|
||||
for r in rules:
|
||||
if r.get("enabled") is not False:
|
||||
self._rules[r["id"]] = r
|
||||
new_rules[r["id"]] = r
|
||||
self._rules = new_rules
|
||||
logger.info("MonitorRuleEngine: 装载 %d 条规则", len(self._rules))
|
||||
|
||||
def add_rule(self, rule: dict) -> None:
|
||||
@@ -365,8 +370,8 @@ class MonitorRuleEngine:
|
||||
|
||||
def remove_rule(self, rule_id: str) -> None:
|
||||
self._rules.pop(rule_id, None)
|
||||
# 清理对应的 cooldown 记录
|
||||
self._last_fire = {k: v for k, v in self._last_fire.items() if k[0] != rule_id}
|
||||
# 清理对应的 cooldown 记录 (list 快照: 评估线程可能并发写 _last_fire)
|
||||
self._last_fire = {k: v for k, v in list(self._last_fire.items()) if k[0] != rule_id}
|
||||
|
||||
def clear(self) -> None:
|
||||
self._rules.clear()
|
||||
@@ -392,9 +397,10 @@ class MonitorRuleEngine:
|
||||
"""是否存在指定类型的 (已启用) 规则。供 quote_service 判断是否需要注入特殊数据。"""
|
||||
if not self._rules:
|
||||
return False
|
||||
# list() 快照: API 线程可能并发增删规则, 直接迭代 dict 会抛 RuntimeError
|
||||
return any(
|
||||
r.get("enabled", True) and r.get("type") == rtype
|
||||
for r in self._rules.values()
|
||||
for r in list(self._rules.values())
|
||||
)
|
||||
|
||||
# ── 评估 ───────────────────────────────────────────
|
||||
@@ -414,7 +420,9 @@ class MonitorRuleEngine:
|
||||
# 每轮重置: 只保留本次 evaluate 产出的策略结果
|
||||
self._latest_strategy_results = {}
|
||||
|
||||
for rule_id, rule in self._rules.items():
|
||||
# list() 快照: 本方法跑在行情轮询线程, API 线程同时 add/remove 规则
|
||||
# 会触发 "dictionary changed size during iteration", 整轮告警丢失
|
||||
for rule_id, rule in list(self._rules.items()):
|
||||
try:
|
||||
events.extend(self._evaluate_rule(df, rule, now))
|
||||
except Exception as e:
|
||||
|
||||
Reference in New Issue
Block a user