invoke-ai/InvokeAI · error · TimeoutError

Timed out waiting to decode frames from {video_path}

Error message

Timed out waiting to decode frames from {video_path}

What it means

Raised by iter_video_frames when it cannot acquire the streaming capacity slots (_VIDEO_STREAM_SLOTS then _VIDEO_DECODER_SLOTS) within the capacity deadline, which is shared with the first-frame timeout. This bounds total concurrent streamers+decoders; the shared deadline prevents waiting ~2x timeout for slot plus first frame. Cancellation is checked while waiting.

Source

Thrown at invokeai/app/util/video_thumbnails.py:355

        stderr_reader.join(timeout=1)
        proc.stderr.close()


def iter_video_frames(
    video_path: Path,
    timeout: float = VIDEO_DECODE_TIMEOUT_SECONDS,
    is_canceled: Optional[Callable[[], bool]] = None,
) -> Iterator[np.ndarray]:
    acquired: list[threading.BoundedSemaphore] = []
    capacity_deadline = time.monotonic() + timeout
    try:
        for slot in (_VIDEO_STREAM_SLOTS, _VIDEO_DECODER_SLOTS):
            while True:
                if is_canceled is not None and is_canceled():
                    raise CanceledException
                remaining = capacity_deadline - time.monotonic()
                if remaining <= 0:
                    raise TimeoutError(f"Timed out waiting to decode frames from {video_path}")
                if slot.acquire(timeout=min(0.1, remaining)):
                    acquired.append(slot)
                    break
        # Charge the capacity wait against the same deadline as the first frame, the way
        # _run_worker does. Handing the decoder a fresh full timeout here would let a
        # caller that waited just under `timeout` for a slot block for nearly 2 * timeout
        # before failing — twice the bound the callers (upload probing, node decodes)
        # believe they are enforcing. Later frames still get a full `timeout` each: after
        # the first frame the budget is an inactivity bound, not a queueing one.
        yield from _iter_video_frames_unbounded(
            video_path,
            timeout,
            is_canceled,
            first_frame_timeout=max(0.0, capacity_deadline - time.monotonic()),
        )
    finally:
        for slot in reversed(acquired):
            slot.release()

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Ensure generators are fully consumed or explicitly closed (gen.close() / context management) so slots are released promptly.
  2. Raise the timeout to tolerate queueing, or serialize video jobs through an application-level queue.
  3. Reduce concurrent callers; the slot limits are intentional back-pressure, so scale workers/machines instead.
  4. If slots appear permanently stuck, look for leaked generator objects or hung worker processes holding the semaphores.

Example fix

// before
gen = iter_video_frames(path)
first = next(gen)  # TimeoutError waiting for slots under load
# gen abandoned → slot leaked
// after
with contextlib.closing(iter_video_frames(path, timeout=120)) as gen:
    for frame in gen:
        process(frame)
Defensive patterns

Strategy: try-catch

Try / catch

try:
    gen = iter_video_frames(path, timeout=120)
    for frame in gen:
        process(frame)
except TimeoutError as e:
    if "waiting to decode frames" in str(e):
        backoff_and_requeue(path)  # pool saturated, retry later
finally:
    gen.close()

Prevention

When it happens

Trigger: Calling iter_video_frames while the maximum number of concurrent stream and decoder slots are held for longer than `timeout`; heavy concurrent thumbnail/frame extraction load; slots leaked by a stuck earlier job; timeout set too small relative to queue depth.

Common situations: Burst uploads to a small server saturating decoder slots; long-running stream consumers holding stream slots; load tests opening many iter_video_frames generators at once without closing them (unclosed generators never release slots).

Understand the failure class

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/b0fab570c2fc96c0. Report an issue: GitHub.