fix(ai-outfit): 停止生成打断当前目录行 + 「停止中…」反馈 (§19.24)
修「开始生成→几秒后停止→两个按钮都灰几十秒像卡死」:非死锁,是温和停止 在等当前目录行收尾,而 _generate_directory_outfit 一次性 submit 全部图片、 shutdown(wait=True) 等全跑完,停止信号进不到该循环;且 _stop 后无反馈。 - outfit_batch:run() 经 _accepts_should_stop 给 generate_func 传 should_stop (= _stop_event.is_set),2-arg 才传、1-arg 仍兼容(含既有 mock) - ai_outfit:generate_outfit_image/_generate_directory_outfit 增 should_stop; 目录循环改有界提交——始终最多 image_concurrency 张在飞,每张完成后、提交 下一张前查 should_stop,已停止则停止提交、在飞收尾即返回(结果注明 「已停止,N 张未生成」,已生成的带 output_paths) - 面板:_OutfitWorker.gen(task, should_stop) 透传;_stop() 把停止按钮改 「停止中…」;_set_running(False) 复位「停止生成」 测试:目录行 should_stop 触发后只生成到停止点、返回「已停止」结果;2-arg generate_func 收到 should_stop;既有用例(1-arg mock、目录并发/部分失败)保持绿。 全套 py37 通过(test_config_service 的 packaging 模板失败属并行 §19.13,无关)。 离屏冒烟:stop 截断 run 且 runner 及时返回;面板停止按钮「停止中…」→结束复位。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -264,6 +264,28 @@ class TestAiOutfitCore(unittest.TestCase):
|
||||
self.assertEqual(len(result.output_paths), 2)
|
||||
self.assertFalse((out / "a" / "img2.jpg").exists())
|
||||
|
||||
def test_generate_directory_stops_remaining_on_should_stop(self):
|
||||
"""§19.24: should_stop 触发后不再提交剩余图片,返回「已停止」结果。"""
|
||||
from core.ai_outfit import generate_outfit_image
|
||||
|
||||
d = self._make_dir_with_images("a") # 3 images, image_concurrency=1
|
||||
out = self.tmp / "out"
|
||||
client = _RecordingClient(self._image_bytes())
|
||||
|
||||
# stop as soon as the first image has been generated
|
||||
def should_stop():
|
||||
return client.calls >= 1
|
||||
|
||||
result = generate_outfit_image(
|
||||
self._dir_task(d), "话术", out, model_config={},
|
||||
api_client=client, should_stop=should_stop)
|
||||
|
||||
self.assertFalse(result.success)
|
||||
self.assertEqual(client.calls, 1) # only 1 generated; rest not submitted
|
||||
self.assertIn("已停止", result.error)
|
||||
self.assertIn("2 张未生成", result.error)
|
||||
self.assertEqual(len(result.output_paths), 1)
|
||||
|
||||
def test_generate_directory_uses_image_concurrency(self):
|
||||
from core.ai_outfit import generate_outfit_image
|
||||
|
||||
|
||||
@@ -67,6 +67,23 @@ class TestOutfitBatchRunner(unittest.TestCase):
|
||||
self.assertEqual(summary.failure_count, 0)
|
||||
self.assertEqual(progress, [(1, 2, 2), (2, 2, 3)])
|
||||
|
||||
def test_two_arg_generate_func_receives_should_stop(self):
|
||||
"""§19.24: 2-arg generate_func 收到 should_stop callable;1-arg 仍兼容。"""
|
||||
from core.outfit_batch import OutfitBatchOptions, OutfitBatchRunner
|
||||
|
||||
seen = []
|
||||
|
||||
def generate(task, should_stop):
|
||||
seen.append(callable(should_stop) and should_stop() is False)
|
||||
return _result(task, True)
|
||||
|
||||
OutfitBatchRunner(
|
||||
[_task(2), _task(3)], generate_func=generate,
|
||||
options=OutfitBatchOptions(concurrency=1),
|
||||
).run()
|
||||
|
||||
self.assertEqual(seen, [True, True]) # both rows got a working should_stop
|
||||
|
||||
def test_retry_until_success(self):
|
||||
from core.outfit_batch import OutfitBatchOptions, OutfitBatchRunner
|
||||
|
||||
|
||||
Reference in New Issue
Block a user