perf(consultation): 健康探测加 TTL、yoga 表达式只编译一次
BUG-734: probe_official_rest_health 不是 ping —— 它把一份虚构 smoke 排盘 POST 给官方 HoroscopePredictions(实测单次 1,034 ms),还经 call() 吃掉对方 每分钟 5 个限流令牌之一,而结论零 TTL。连续 6 轮前台请求下,第 6 次探测会被 本地限流器挡下、反过来报出假的 official_blocked,真正的业务调用还要和它抢令牌。 加进程级 TTL 缓存(成功 60 s / 失败 10 s,配置变即失效,缓存键只记 key 配没配 的布尔)。force_refresh 默认 True:诊断端点调的是裸 gateway_status() 且该文件 本轮不得改,默认实时才不会让运维看到旧结论;run_gateway_packet 显式走 False。 探测在锁外执行。6 轮实测:探测 6→1、握手 5→1、限流令牌 6→1。 连接复用(任务书 6.2)按让步顺序砍掉:实测 urllib.request 没有连接池,模块级 共享 opener 在 6 次请求下仍开 6 条 TCP 连接,按任务书写法只能骗过「同一个 opener 实例」的断言而省不掉任何握手。另立单。 BUG-735: yoga_engine._eval_custom 对静态规则表的表达式每请求重新编译。实测每次 检测 386 次源码编译(182 次 eval 字符串 + 204 次 ast.parse/compile)。拆出 _build_custom_exec_globals(每次新建,绑定当前盘 ctx)与 lru_cache 的 _compiled_eval_code / _compiled_exec_code,只缓存 code object,绝不缓存求值 结果。求值语义逐条对齐改前。第二次检测起编译 0 次,单次 27.4 ms → 3.3 ms。 等价用同进程差分(BUG-733 的做法):192 条去重表达式 × 3 张公开虚构示例盘, 与逐字复刻的改前实现同值,另有反向验证证明差分非恒真。 未改 scripts/jyotish_api_server.py。既有断言一条未改。新增 23 条测试。 快速门唯一红的 test_chat_page_uses_authenticated_cloud_persistence 经干净 origin/staging 检出复跑确认为基线红,记入 BLOCKED.md BLK-002,不顺手修。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JUei7K13cYxLHE3Axe4A45
This commit is contained in:
co-authored by
Claude Opus 5
parent
10afdcddeb
commit
63c419f375
@@ -6,6 +6,8 @@ from __future__ import annotations
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import threading
|
||||
import time
|
||||
from datetime import datetime, timezone
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
@@ -240,7 +242,53 @@ def run_gateway_job(job_id: str) -> dict[str, Any] | None:
|
||||
return complete_gateway_job(job_id, result)
|
||||
|
||||
|
||||
def probe_official_rest_health() -> dict[str, Any]:
|
||||
# ---------------------------------------------------------------------------
|
||||
# BUG-734: 健康探测的 TTL 缓存
|
||||
# ---------------------------------------------------------------------------
|
||||
# `probe_official_rest_health` 不是 ping:它 POST 一份虚构 smoke 排盘到官方
|
||||
# HoroscopePredictions,用返回的 Status 当健康信号,并且会消耗对方每分钟 5 个的
|
||||
# 限流令牌之一。它问的是「这个外部服务此刻可用吗」——这种状态不会每 500 毫秒变一次,
|
||||
# 却被每一轮前台请求重新问一遍(本机实测每次约 0.5 秒)。
|
||||
#
|
||||
# 成功 60 秒:覆盖一串连续问答,又不超过对方限流窗口的长度,
|
||||
# 「已恢复」被压住的时间不会超过一个限流窗口。
|
||||
# 失败 10 秒:对方恢复后我们最多瞎等 10 秒。必须显著短于成功 TTL(否则服务恢复了
|
||||
# 我们还要再等一分钟),这是任务书 §5.4 的红线。
|
||||
_REST_HEALTH_SUCCESS_TTL_SECONDS = 60.0
|
||||
_REST_HEALTH_FAILURE_TTL_SECONDS = 10.0
|
||||
|
||||
_REST_HEALTH_LOCK = threading.Lock()
|
||||
_REST_HEALTH_CACHE: dict[str, Any] = {}
|
||||
|
||||
|
||||
def _monotonic() -> float:
|
||||
"""单调时钟;测试把它推过 TTL。用 monotonic 而非墙钟,避免 NTP 校时影响过期判断。"""
|
||||
return time.monotonic()
|
||||
|
||||
|
||||
def _rest_health_cache_key() -> tuple[Any, ...]:
|
||||
"""影响探测结论的有效配置。配置一变立刻失效。
|
||||
|
||||
只记 ``VEDASTRO_API_KEY`` 是否配置的布尔值,绝不把 key 本身放进键(AGENTS §8)。
|
||||
"""
|
||||
config = build_gateway_config()
|
||||
return (
|
||||
config["mode"],
|
||||
os.environ.get("VEDASTRO_API_ENDPOINT", OFFICIAL_ENDPOINT).strip(),
|
||||
os.environ.get("VEDASTRO_SELF_HOST_ENDPOINT", "").strip(),
|
||||
_official_network_enabled(),
|
||||
bool(os.environ.get("VEDASTRO_API_KEY", "").strip()),
|
||||
os.environ.get("JYOTISH_SKIP_LOCAL_ENV", "").strip().lower(),
|
||||
)
|
||||
|
||||
|
||||
def reset_rest_health_cache() -> None:
|
||||
"""清空探测缓存。测试与运维用;生产路径不调用。"""
|
||||
with _REST_HEALTH_LOCK:
|
||||
_REST_HEALTH_CACHE.clear()
|
||||
|
||||
|
||||
def _probe_official_rest_health_uncached() -> dict[str, Any]:
|
||||
"""Health of the default official path: REST ``/api/Calculate`` Status Pass."""
|
||||
base = {
|
||||
"transport": "rest",
|
||||
@@ -260,12 +308,44 @@ def probe_official_rest_health() -> dict[str, Any]:
|
||||
return {**base, **result}
|
||||
|
||||
|
||||
def gateway_status() -> dict[str, Any]:
|
||||
def probe_official_rest_health(*, force_refresh: bool = True) -> dict[str, Any]:
|
||||
"""Health of the default official path.
|
||||
|
||||
``force_refresh=True``(默认)永远打真探测——诊断端点靠的就是这个,
|
||||
运维不得看到一个 60 秒前的假象。前台每轮都走的路径显式传 ``force_refresh=False``
|
||||
去读 TTL 缓存。
|
||||
"""
|
||||
if force_refresh:
|
||||
return _probe_official_rest_health_uncached()
|
||||
|
||||
key = _rest_health_cache_key()
|
||||
with _REST_HEALTH_LOCK:
|
||||
entry = _REST_HEALTH_CACHE.get("entry")
|
||||
if entry is not None and entry["key"] == key and entry["expires_at"] > _monotonic():
|
||||
return dict(entry["result"])
|
||||
|
||||
# 探测本身不持锁:不得在等外网时把进程级锁按住(BUG-718 的教训)。
|
||||
result = _probe_official_rest_health_uncached()
|
||||
ttl = (
|
||||
_REST_HEALTH_SUCCESS_TTL_SECONDS
|
||||
if result.get("status") == "official_verified"
|
||||
else _REST_HEALTH_FAILURE_TTL_SECONDS
|
||||
)
|
||||
with _REST_HEALTH_LOCK:
|
||||
_REST_HEALTH_CACHE["entry"] = {
|
||||
"key": key,
|
||||
"expires_at": _monotonic() + ttl,
|
||||
"result": dict(result),
|
||||
}
|
||||
return result
|
||||
|
||||
|
||||
def gateway_status(*, force_refresh: bool = True) -> dict[str, Any]:
|
||||
from scripts.diagnose_vedastro_mode import build_report as build_vedastro_mode_report
|
||||
|
||||
readiness = build_vedastro_mode_report()
|
||||
config = build_gateway_config()
|
||||
rest_health = probe_official_rest_health()
|
||||
rest_health = probe_official_rest_health(force_refresh=force_refresh)
|
||||
return {
|
||||
"scope": "vedastro_gateway",
|
||||
"mode": config["mode"],
|
||||
@@ -387,7 +467,10 @@ def run_gateway_packet(
|
||||
) -> dict[str, Any]:
|
||||
from scripts.vedastro_user_entrypoint import build_report
|
||||
|
||||
gateway = gateway_status()
|
||||
# BUG-734: 这是每一轮前台请求都走的路。健康探测读 TTL 缓存,
|
||||
# 不再每轮拿一个对方的限流令牌去做一次完整业务调用。
|
||||
# 诊断端点仍走默认的 force_refresh=True。
|
||||
gateway = gateway_status(force_refresh=False)
|
||||
args = _entrypoint_args(case, question, themes, reference_date)
|
||||
report = build_report(args)
|
||||
catalog = report.get("official_capability_catalog") or {}
|
||||
|
||||
Reference in New Issue
Block a user