2026-10-29 长任务实弹:Docker goal 断点续跑在 host-state 里找 不到任务时抛 KeyError,worker 把无结论异常退出判 interrupted, 自动恢复再排队、再 KeyError,12 秒一轮空转 49 次(人工暂停 拦下,第 50 次人工续跑又烧掉),文件 32 章原样未动。 修法:host.py goal-resume 先查任务在不在 state 里,不在就打印 拒绝行、正常退出;sandbox 三处解析(本地、Docker resume、 Docker 断点)见拒绝行即判 failed 终态,不进自动恢复循环。 单测:host 缺任务拒绝不炸 + 三处解析拒绝判 failed。
67 lines
2.3 KiB
Python
67 lines
2.3 KiB
Python
"""host.py goal-resume 断点丢失守卫:state 里没有任务时拒绝不炸。
|
||
|
||
2026-10-29 长任务实弹:断点续跑在 state 里找不到任务时抛
|
||
KeyError traceback,worker 把无结论异常退出判成 interrupted,
|
||
自动恢复再排队、再 KeyError,12 秒一轮空转到 50 次上限。
|
||
守卫后:打印拒绝行、正常返回,state 文件不动、线程不恢复。
|
||
"""
|
||
|
||
import io
|
||
import json
|
||
import os
|
||
import sys
|
||
import tempfile
|
||
import unittest
|
||
from contextlib import redirect_stdout
|
||
from pathlib import Path
|
||
from unittest import mock
|
||
|
||
REPO_ROOT = Path(__file__).resolve().parents[1]
|
||
sys.path.insert(0, str(REPO_ROOT))
|
||
|
||
import host
|
||
|
||
|
||
class FakeServer(object):
|
||
def __init__(self):
|
||
self.resumed = []
|
||
|
||
def initialize(self):
|
||
pass
|
||
|
||
def close(self):
|
||
pass
|
||
|
||
def resume_thread(self, thread_id):
|
||
self.resumed.append(thread_id)
|
||
|
||
|
||
class GoalResumeMissingTest(unittest.TestCase):
|
||
def test_missing_task_refuses_without_traceback(self):
|
||
with tempfile.TemporaryDirectory() as tmp:
|
||
state_path = os.path.join(tmp, "host-state.json")
|
||
cfg_path = os.path.join(tmp, "host-config.json")
|
||
with open(state_path, "w", encoding="utf-8") as f:
|
||
json.dump({"tasks": {"g": {"name": "g",
|
||
"thread_id": "x",
|
||
"status": "goal-running"}},
|
||
"automations": []}, f)
|
||
with open(cfg_path, "w", encoding="utf-8") as f:
|
||
json.dump({}, f)
|
||
fake = FakeServer()
|
||
argv = ["host.py", "--state", state_path,
|
||
"--config", cfg_path,
|
||
"goal-resume", "--task", "missing-task"]
|
||
out = io.StringIO()
|
||
with mock.patch.object(sys, "argv", argv):
|
||
with mock.patch.object(host, "Server",
|
||
lambda **kw: fake):
|
||
with redirect_stdout(out):
|
||
host.main()
|
||
self.assertIn("goal-resume 拒绝", out.getvalue())
|
||
self.assertIn("missing-task", out.getvalue())
|
||
self.assertEqual(fake.resumed, [])
|
||
with open(state_path, encoding="utf-8") as f:
|
||
saved = json.load(f)
|
||
self.assertEqual(list(saved["tasks"].keys()), ["g"])
|