SOP — cache_probe 快照过期即判无缓存 + systemd 服务修复¶
变更日期:2026-10-11 实施人:rain(Agent 执行) 类型:C 类(改代理代码 + 恢复 systemd 托管) 归档目录:
token_proxy/cacheprobe-expired-20261011/
1. 背景¶
两条触发链:
-
超长空闲未拦截:本会话空闲 31.4 小时后,
cache_probe未按设计拦截,代理付了整段前缀重建(本轮实测未命中 91%)。核查发现:探针的快照有 24 小时 TTL(SNAP_TTL_SECONDS = 24*3600),超期条目在启动加载时被丢弃;空闲 31 小时超过 TTL,探测前提「该会话存在一份上次快照」不成立,_due_snapshot返回 None,探针静默放行。磁盘证据:快照文件仅剩 4 条、全在 7.6 小时内,usage.jsonl最近一条探测停在 10-10 00:20。 -
systemd 服务 failed:
token-proxy.service自 16:59:32 起为 failed。日志显示 16:59 起反复Main process exited, code=exited, status=1/FAILURE,重启 6 次后触发Start request repeated too quickly。proxy.stdout.log尾部的 traceback 给出根因:OSError: [Errno 98] Address already in use(端口 8788 被占)。此后 17:14 有人手动启动代理占住 8788,systemd 无法接管。
2. 目标¶
token-proxy.service恢复 systemd 托管,active(running),8788 由 systemd 的 MainPID 监听;- 会话快照年龄超过 TTL 时,
cache_probe直接判定「无缓存」并拦截,不再因快照被丢弃而静默放行; - 空闲 1h~24h 的探测路径、空闲 <1h 的放行路径行为不变;
- 改码经离线验证通过、两遍同结果。
3. 前置条件¶
- 代理进程与
config.yaml、logs/就绪; venv/可用(venv/bin/python);- 无在跑的关键业务依赖(本变更重启代理,中断秒级)。
4. 影响范围¶
| 项 | 内容 |
|---|---|
| 主机 | 本机 dell-R620(代理与客户端同机) |
| 文件 | proxy.py(4 处改动);新增交付目录 cacheprobe-expired-20261011/ |
| 是否停服 | 是,代理重启一次(秒级中断) |
| 窗口期 | 随时(可随时重启,不打断会话输出流) |
| 回滚点 | proxy.py 改前快照(本主题 backup/) |
5. 分步操作¶
实现体:
proxy.py(CacheProbe类)。以下给出等价底层操作供阅读与复核。
步骤 1:修 systemd 服务¶
根因是端口 8788 被手动进程占用,systemd 起的进程 bind 失败。修法:停占端口的手动进程,重置失败计数,再起服务。
# 停掉占用 8788 的手动进程(PID 以 ss 实测为准)
ss -ltnp | grep :8788 # 确认占用者 PID
kill -TERM <占用PID> # 高危:停止进程,触发优雅退出(落盘快照)
sleep 3
ss -ltnp | grep :8788 || echo PORT_FREE # 预期 PORT_FREE
systemctl --user reset-failed token-proxy # 清掉「重启过快」限流计数
systemctl --user start token-proxy
预期:systemctl --user is-active token-proxy 输出 active,ss -ltnp | grep :8788 的 PID 等于 systemctl --user show token-proxy -p MainPID。
失败判断:若端口未释放则 start 仍报 Address already in use,回到第 7 章。
步骤 2:改 proxy.py:过期快照保留标记¶
load_state() 原逻辑把超 TTL 条目 continue 丢弃,导致「曾见过的会话」与「新会话」不可区分。改为保留一个轻量过期标记(只存 ts)。
# 见 proxy.py::CacheProbe.load_state():超 TTL 条目改存 self._expired_snap(ts)
# 新增 proxy.py::CacheProbe._expired_snap():返回 {ts, expired: True, messages: None, ...}
# 改 proxy.py::CacheProbe._snap_for_state():对 expired 条目只落 {ts, expired: True}
步骤 3:改 proxy.py:过期即判失效¶
check() 在 _due_snapshot() 拿到快照后,若 snap["expired"] 或 idle > SNAP_TTL_SECONDS,直接给出拦截判定,不再发探测请求(省 0.03 元/次探测费)。
# proxy.py::CacheProbe.check():新增分支
# if snap.get("expired") or snap["idle"] > SNAP_TTL_SECONDS:
# verdict = {"cached":0,"total":0,"ratio":0.0,"idle":snap["idle"],
# "reason":"snapshot_expired"}
配套在 proxy.py::ProxyHandler._reject_cache_expired() 增加 reason == "snapshot_expired" 分支,返回 500 并给出「快照已过期(空闲 X 分钟,超过 24 小时)」文案。
坑:_snap_for_state() 会对快照调用 _truncate_messages(),而过期标记的 messages 为 None,若不先拦就会迭代 None 报错。故必须先判 expired 再返回。
步骤 4:离线验证¶
venv/bin/python -m py_compile proxy.py
venv/bin/python cacheprobe-expired-20261011/offline_verify.py
预期:COMPILE_OK + RESULT: ALL_OK(A 过期拦 / B 新鲜放行 / C 探测失效拦 / D 探测命中放行 / E 恢复过期标记)。
步骤 5:重启生效¶
步骤 1 的 systemctl --user start 已用改后代码启动;若先改码后重启,顺序为改码 → 离线验证 → 步骤 1。
6. 验证¶
| # | 命令 | 实测结果 |
|---|---|---|
| 1 | systemctl --user show token-proxy -p MainPID -p ActiveState -p SubState -p NRestarts |
MainPID=3391276、active、running、NRestarts=0 |
| 2 | ss -ltnp \| grep :8788 |
PID 3391276 监听 8788(= MainPID) |
| 3 | venv/bin/python cacheprobe-expired-20261011/offline_verify.py(两遍) |
均 ALL_OK(A/B/C/D/E 五路径) |
| 4 | logs/proxy.log 尾部 |
2026-10-11 20:53:29 代理启动: 0.0.0.0:8788 |
| 5 | 真实拦截路径(快照龄 >24h) | 待观察:需等某会话空闲 >24h,或手工把快照 ts 改旧后发请求(会拦断当前对话,未做) |
7. 回滚¶
- 还原代码:
bash cp -a cacheprobe-expired-20261011/backup/proxy.py.bak.20261011-205209 proxy.py - 重启代理:
bash systemctl --user restart token-proxy - 回滚后验证:
systemctl --user is-active token-proxy为active;venv/bin/python cacheprobe-expired-20261011/offline_verify.py中 A/E 两项应失败(旧代码丢弃过期快照),即回到变更前语义。
备份位置与清单见 backup/README.md。systemd 侧本次无 unit 文件改动,无需回滚。
8. 风险说明¶
| 风险 | 缓解 |
|---|---|
| 误拦 >24h 空闲的会话(本设计意图,但可能打断) | 拦截返回 500 并提示「压缩上下文或新开会话」;冷却 120s、压缩后(chars ≤ 90%)立即放行 |
改 proxy.py 致代理起不来 |
token_proxy 红线:改前备份 + 离线验证;本次离线 5 路径全过、语法检查通过 |
| 停手动进程期间代理中断 | 秒级;规则明确重启代理不是红线、不打断输出流 |
_snap_for_state 迭代 None |
已先判 expired 再返回(见步骤 3 坑) |
| systemd 失败根因(端口冲突)复发 | 本次已清手动进程;若再现,按步骤 1 查 ss 占用者 |
附录:交付物清单¶
| 文件 | 说明 |
|---|---|
SOP.md |
本文件 |
offline_verify.py |
离线验证脚本(5 路径) |
backup/proxy.py.bak.20261011-205209 |
proxy.py 改前快照 |
backup/README.md |
备份台账 |
来源:
/home/rain/Documents/services/token_proxy/cacheprobe-expired-20261011/SOP.md| 最后更新:2026-10-11 20:54(由build/sync.py同步,请勿在站点侧修改)