Resolved

4K video rendering causes system freeze and OOM crashes in Python

Python
FFmpeg
Multiprocessing
Performance
Memory Management
Video Processing
Debugging
DevOps

Multiprocessing-based 4K/8K video rendering freezes systems due to FFmpeg stderr deadlocks, memory exhaustion (122GB+), and pool cleanup deadlocks | Fixed with background stderr readers, memory watchdogs, and optimized pool buffering

Issue

The 4K/8K video rendering system experienced system freezes and out-of-memory (OOM) crashes when processing high-resolution videos. Key problems included:

  • Subprocess Deadlock: FFmpeg’s stderr buffer filled, causing a mutual deadlock between FFmpeg and Python processes.
  • Memory Exhaustion: The system required 314GB of memory for a 7-minute 4K video, far exceeding the available 125GB RAM.
  • Excessive Pool Buffering: The pool.imap() internal buffering mechanism led to an unanticipated 66GB buffer allocation.
  • Post-Rendering Deadlock: The pool.join() operation hung indefinitely during cleanup, causing further system unresponsiveness.

Culprit

  1. FFmpeg Stderr Deadlock: The original design blocked indefinitely when FFmpeg’s stderr buffer reached its 64KB limit, preventing both FFmpeg and Python from progressing.
  2. Incorrect Memory Calculation: The system underestimated memory requirements, leading to OOM crashes due to unchecked frame buffering.
  3. Pool Buffering Miscalculation: The chunksize formula did not account for the pool.imap() internal 3× buffering, causing excessive memory usage.
  4. Cleanup Deadlock: The pool.join() operation lacked a timeout, resulting in infinite waits during system cleanup.

Solution

Background Stderr Reader

Added a daemon thread to continuously drain FFmpeg’s stderr buffer, preventing buffer overflow and deadlocks:

  • Used a queue.Queue to safely handle stderr output.
  • Implemented a daemon thread to continuously read and process stderr data.

Resource Monitor Watchdog

Implemented a monitoring loop to detect and prevent OOM conditions:

  • Monitored memory usage every 0.5 seconds.
  • Hard-killed the process if memory usage exceeded 120GB.
  • Logged metrics to ensure transparency and debugging.

Optimized Pool Buffering

Adjusted the chunksize calculation to account for the internal 3× buffering in pool.imap():

  • Reduced buffer size from 66GB to 14.4GB.
  • Ensured memory usage stayed within system limits.

Cleanup Timeout

Added a 30-second timeout to the pool.join() operation:

  • Terminated stuck workers if cleanup did not complete within the timeout.
  • Ensured the system could recover from deadlocks during cleanup.

The solution addressed all four failure modes, ensuring the system could reliably render high-resolution videos without crashing or freezing.