Show the retained failure when a video page mounts after a failed job
Mount-time recovery handled only phase=completed, so reloading the page after a multi-minute generation failed left an idle view with no diagnosis: the backend keeps the terminal failed record only until the next job, and nothing else survives the reload. Surface it the same way the poll does, filtering the cancelled sentinel.
This commit is contained in:
parent
b782b85a13
commit
5411747726
2 changed files with 45 additions and 0 deletions
|
|
@ -8,6 +8,7 @@ stack loads."""
|
|||
|
||||
import contextlib
|
||||
import sys
|
||||
import time
|
||||
import types
|
||||
|
||||
import pytest
|
||||
|
|
@ -1107,6 +1108,44 @@ def test_generate_progress_derives_total_steps_and_fraction(fake_runtime):
|
|||
assert gen["step"] == 5 and gen["fraction"] == 0.25
|
||||
|
||||
|
||||
def test_failed_background_generate_retains_terminal_error(fake_runtime, tmp_path, monkeypatch):
|
||||
# A page mounted AFTER a background job failed (browser reload during a minutes-long
|
||||
# generation) reads the outcome from this retained terminal record -- its only diagnosis,
|
||||
# since nothing else survives the reload. So a failure must stay pollable as
|
||||
# active=False + phase="failed" + error, repeatedly, until the next job replaces it.
|
||||
backend = VideoBackend()
|
||||
_load_gguf(backend, tmp_path)
|
||||
|
||||
def _boom(
|
||||
self,
|
||||
*,
|
||||
prompt = None,
|
||||
negative_prompt = None,
|
||||
num_inference_steps = None,
|
||||
guidance_scale = None,
|
||||
width = None,
|
||||
height = None,
|
||||
num_frames = None,
|
||||
frame_rate = None,
|
||||
generator = None,
|
||||
callback_on_step_end = None,
|
||||
**kwargs,
|
||||
):
|
||||
raise ValueError("frames exceed the device memory")
|
||||
|
||||
monkeypatch.setattr(type(backend._state.pipe), "__call__", _boom)
|
||||
backend.begin_generate(prompt = "a clip")
|
||||
deadline = time.monotonic() + 10
|
||||
while backend.generate_progress()["active"] and time.monotonic() < deadline:
|
||||
time.sleep(0.01)
|
||||
gen = backend.generate_progress()
|
||||
assert gen["active"] is False
|
||||
assert gen["phase"] == "failed"
|
||||
assert gen["error"] == "frames exceed the device memory"
|
||||
# Re-poll: a mount-time probe is a second read of the same record, not a one-shot drain.
|
||||
assert backend.generate_progress()["phase"] == "failed"
|
||||
|
||||
|
||||
def test_cache_bytes_counts_incomplete_blobs(fake_runtime, tmp_path, monkeypatch):
|
||||
# scan_cache_dir skips in-flight *.incomplete blobs, so the old counter froze at the
|
||||
# last completed blob for the whole multi-GB shard pull. The walk must count both,
|
||||
|
|
|
|||
|
|
@ -1002,6 +1002,12 @@ export function VideoPage({ active = true }: { active?: boolean }) {
|
|||
const clip = g.video;
|
||||
setVideos((prev) => (prev.some((v) => v.id === clip.id) ? prev : [clip, ...prev]));
|
||||
void ensureSrc(clip);
|
||||
} else if (g.phase === "failed") {
|
||||
// The other terminal phase, and the backend keeps it only until the next job: without
|
||||
// this a reload after a minutes-long generation failed shows an idle page and the
|
||||
// error (OOM, bad input, disk) is lost. Same cancelled-sentinel filter the poll uses.
|
||||
const msg = g.error || "Video generation failed";
|
||||
if (!msg.toLowerCase().includes("cancelled")) toast.error(msg);
|
||||
}
|
||||
} catch {
|
||||
// Resume is best-effort; a failed probe just leaves the idle view.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue