From cc93830a96846412bc694e1771ba11c705a3fa54 Mon Sep 17 00:00:00 2001 From: Claude Date: Sun, 8 Nov 2026 19:59:38 +0000 Subject: [PATCH] Upscale in Always mode tries again after a running generation instead of giving up; tests for relink wiring, first-40 window, RTX at job start Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01Tw9fjX7o2YfeGQE18MEtPu --- app/ui/features/upscale/index.js | 7 ++++- core/tests/test_leak_report.py | 28 +++++++++++++++++++ .../output/render/tests/test_jobs_routes.py | 10 +++++++ 3 files changed, 44 insertions(+), 1 deletion(-) diff --git a/app/ui/features/upscale/index.js b/app/ui/features/upscale/index.js index 79536688..bf86f6ec 100644 --- a/app/ui/features/upscale/index.js +++ b/app/ui/features/upscale/index.js @@ -12,7 +12,7 @@ export default { modules: ["render"], setup({ host, app }) { const p = app.project, sel = app.selection; - const put = inPlace(host), busy = new Set(); + const put = inPlace(host), busy = new Set(), waited = new Map(); // waited: how often "Always" put a render off for a running generation const render = (id) => ((p.project || {}).scene_renders || {})[id]; const target = () => { // the picked clip's generation unit root, when it has a render const open = p.project, sc = open && open.scenes.find((s) => s.id === sel.focus); @@ -55,6 +55,11 @@ export default { else c.toast.good({ text: "Upscaled: the render now plays the larger video." }); } catch (err) { if (err.stopped) c.toast.info({ text: "Upscale stopped. The render stays as it was." }); + else if (!pressed && /^ComfyUI is generating/.test(err.message || "") && (waited.get(key) || 0) < 60) { // Always: not lost, tried again when the generation is over + if (!waited.has(key)) c.toast.info({ text: "Upscale waits for the generation to finish." }); + waited.set(key, (waited.get(key) || 0) + 1); + setTimeout(() => run(media), 15000); + } else { gaveUp(media, "upscale"); c.toast.warn({ text: `Upscale failed: ${err.message}` }); } } finally { busy.delete(key); draw(); } diff --git a/core/tests/test_leak_report.py b/core/tests/test_leak_report.py index f551fba7..8475cb76 100644 --- a/core/tests/test_leak_report.py +++ b/core/tests/test_leak_report.py @@ -138,3 +138,31 @@ def test_a_hand_over_with_no_parent_is_logged_after_the_first_forty(monkeypatch) leak_report.watch_switch(mm) LM()._switch_parent() assert lines and "hand-over" in lines[0][1] and "gone" in lines[0][1] + + +def test_install_runs_relink_before_comfyuis_own_cleanup(monkeypatch): + order = [] + mm = type("MM", (), {})() + mm.cleanup_models_gc = lambda: order.append("comfy") + monkeypatch.setattr(leak_report, "watch_switch", lambda m: True) + monkeypatch.setattr(leak_report, "relink", lambda m: order.append("relink") or 0) + monkeypatch.setattr(leak_report, "report", lambda m, when: order.append(when) or 0) + assert leak_report.install(mm) + mm.cleanup_models_gc() + assert order == ["relink", "before ComfyUI's gc", "comfy", "after ComfyUI's gc"] + + +def test_the_first_forty_hand_overs_with_a_parent_are_logged_and_the_forty_first_is_not(monkeypatch): + monkeypatch.setattr(leak_report, "_seen", [0]) + lines = [] + monkeypatch.setattr(leak_report.log, "info", lambda *a, **k: lines.append(a)) + mm = type("MM", (), {})() + + class LM: + def __init__(self): self._parent_model = lambda: object(); self.real_model = lambda: None + def _switch_parent(self): pass + mm.LoadedModel = LM + leak_report.watch_switch(mm) + for _ in range(45): + LM()._switch_parent() + assert len(lines) == 40 diff --git a/modules/output/render/tests/test_jobs_routes.py b/modules/output/render/tests/test_jobs_routes.py index 0c10bb9b..54aa44ad 100644 --- a/modules/output/render/tests/test_jobs_routes.py +++ b/modules/output/render/tests/test_jobs_routes.py @@ -344,3 +344,13 @@ def test_upscale_job_with_a_model_file_stops_if_comfyui_got_busy_after_the_route import pytest with pytest.raises(RuntimeError, match="ComfyUI is generating"): upscale.run({"filename": "x.mp4"}, "4x.safetensors") + + +def test_rtx_upscale_job_runs_while_comfyui_is_busy(monkeypatch, tmp_path): + from modules.output.render import upscale, rtx + monkeypatch.setattr(upscale, "comfy_busy", lambda: True) + import pytest + # the busy check is passed (RTX); the run then stops on the missing file, not on the busy message + with pytest.raises(RuntimeError) as e: + upscale.run({"filename": "missing.mp4"}, rtx.NAME) + assert "ComfyUI is generating" not in str(e.value)