{"record":{"id":"d27b4b401eb67136","repo":"invoke-ai/InvokeAI","slug":"video-decode-worker-timed-out-after-timeout-s","errorCode":null,"errorMessage":"Video decode worker timed out after {timeout}s","messagePattern":"Video decode worker timed out after (.+?)s","errorType":"exception","errorClass":"VideoDecodeTimeoutError","httpStatus":null,"severity":"error","filePath":"invokeai/app/util/video_thumbnails.py","lineNumber":189,"sourceCode":"\n    With ``raise_on_timeout``, a timeout raises VideoDecodeTimeoutError instead of\n    returning None, letting callers distinguish \"could not decode\" from \"ran out of\n    time on a loaded machine\".\n    \"\"\"\n    monitor_stop: threading.Event | None = None\n    monitor: threading.Thread | None = None\n    try:\n        proc = _spawn_worker(*args, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True)\n    except Exception:\n        return None\n    try:\n        monitor_stop, _memory_exceeded, monitor = _start_worker_memory_monitor(proc)\n        stdout, _ = proc.communicate(timeout=timeout)\n    except subprocess.TimeoutExpired as error:\n        _terminate_process_tree(proc)\n        proc.communicate()\n        if raise_on_timeout:\n            raise VideoDecodeTimeoutError(f\"Video decode worker timed out after {timeout}s\") from error\n        return None\n    except Exception:\n        # An unexpected failure (e.g. OSError from communicate()) must not leak the\n        # worker tree: the finally below stops the RSS-monitor backstop, so nothing\n        # else would ever reap a still-running worker and its ffmpeg child.\n        _terminate_process_tree(proc)\n        return None\n    finally:\n        if monitor_stop is not None and monitor is not None:\n            monitor_stop.set()\n            monitor.join(timeout=1)\n    if proc.returncode != 0:\n        return None\n    try:\n        result = json.loads(stdout)\n    except ValueError:\n        return None\n    return result if isinstance(result, dict) else None","sourceCodeStart":171,"sourceCodeEnd":207,"githubUrl":"https://github.com/invoke-ai/InvokeAI/blob/0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06/invokeai/app/util/video_thumbnails.py#L171-L207","documentation":"Raised (as VideoDecodeTimeoutError) when the spawned video-decode worker subprocess does not finish within `timeout` seconds during proc.communicate(). The worker process tree is terminated first, and this error is only raised when raise_on_timeout is True; otherwise the caller gets None. It exists so high-level APIs fail fast instead of hanging on pathological videos.","triggerScenarios":"Calling _run_worker_unbounded (via _run_worker with raise_on_timeout=True) with a video large/long enough that decoding exceeds the timeout, a worker that deadlocks (e.g. stdout pipe backpressure), or an extremely small timeout value. Also when the machine is heavily loaded and the subprocess is starved of CPU.","commonSituations":"Thumbnail generation on a multi-hour 4K video with a 60s default timeout; CI machines with throttled CPU; worker memory limit causing swap thrashing; accidentally passing timeout=0.001 in tests or config.","solutions":["Increase the timeout argument passed to extract_video_frame/probe_video_with_codec/decoder_frame_count for large or long videos.","Test the file manually: time ffmpeg -i video.mp4 -f null - to see if the file itself is pathologically slow to decode, and pre-transcode if so.","Check system load / available CPU; decoding is CPU-bound, so reduce concurrency (the library already caps via _VIDEO_DECODER_SLOTS).","Catch VideoDecodeTimeoutError and fall back to a lower-resolution proxy video for thumbnailing."],"exampleFix":"// before\nframe = extract_video_frame(path, timestamp=0)  # VideoDecodeTimeoutError after 60s\n// after\ntry:\n    frame = extract_video_field(path, timestamp=0, timeout=300)\nexcept VideoDecodeTimeoutError:\n    frame = None  # fall back to placeholder thumbnail","handlingStrategy":"try-catch","validationCode":"# rough pre-check: is the file pathologically big?\nimport os\nif os.path.getsize(path) > 2_000_000_000:\n    timeout = 600  # allow more time for huge files","typeGuard":null,"tryCatchPattern":"from invokeai.app.util.video_thumbnails import VideoDecodeTimeoutError\ntry:\n    result = _run_worker(args, timeout, raise_on_timeout=True)\nexcept VideoDecodeTimeoutError:\n    result = None  # retry with larger timeout or mark job failed","preventionTips":["Scale timeout to video size/duration","Pre-transcode long 4K footage to proxies","Avoid scheduling many decodes on CPU-starved machines","Monitor worker durations and tune timeout from p95 metrics"],"tags":["video","timeout","subprocess","decoding"],"backgroundTag":"subprocess-timeout","analyzedSha":"0b6a024f2ff6a86bfb953dcdb9cc504ef7397a06","analyzedAt":"2026-08-29T04:46:49.967Z","schemaVersion":2},"datasetVersion":"2026-08-29T07:17:48.351Z"}