Repository navigation
Make the video decoders' FFmpeg calls safe from several threads - #6557
jantonguirao merged 3 commits into
Conversation
av_error_string formatted into a function-static buffer, and every decoder constructor set FFmpeg's process-wide log level. Use a local buffer, and set the log level only once. Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
There was a problem hiding this comment.
🟢 Approval recommended
The localized changes use appropriate storage and synchronization, with no identified blocking issues; tests were not run.
No open issues 🎉
What changed in this PR
This PR addresses two concurrency issues in the FFmpeg helpers shared by DALI’s CPU and GPU video decoders, supporting existing parallel decoding and planned video-reader improvements.
Changes:
- Use a local buffer for FFmpeg error strings to prevent concurrent calls from overwriting each other.
- Initialize FFmpeg’s log level through
std::call_once, shared by both decoder constructors.
| File | Description |
|---|---|
dali/operators/video/video_utils.cc |
Replaces the shared error-message buffer with per-call storage. |
dali/operators/video/frames_decoder_base.cc |
Adds synchronized, one-time log-level initialization for both constructors. |
Review effort: Balanced
[Medium impact] The PR appears safe to merge; no actionable new issues were found.
|
| static std::once_flag once; | ||
| std::call_once(once, [] { av_log_set_level(AV_LOG_ERROR); }); |
There was a problem hiding this comment.
This likely costs more than just calling av_log_set_level every time. The function is defined as
void av_log_set_level(int level)
{
av_log_level = level;
}and it prevents the value from changing, should we ever want to alter the log level while already running.
There was a problem hiding this comment.
Agreed, call_once costs more than the store it protects, and it would keep the level from being changed later. I removed it, and the level is now set once by a static initializer in this file, while the library is loaded and before any decoder exists (5b4cb1f). That also removes the concurrent writes from decoders created on several threads, and a level set later is no longer reset by every new decoder.
av_log_set_level only stores the level, so calling it every time costs less than call_once, and it keeps the level adjustable while running. Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
av_log_set_level stores the level in a plain global, so calling it from every decoder made decoders created on several threads write it concurrently. It is now set by a static initializer, before any decoder exists, and a level set later is kept. Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
|
!build |
|
CI MESSAGE: [77048268]: BUILD STARTED |
|
CI MESSAGE: [77048268]: BUILD PASSED |
Category:
Bug fix (non-breaking change which fixes an issue)
Description:
Two FFmpeg calls of the video decoders (
FramesDecoderBaseand its CPU and GPU implementations) were not safe from several threads at once:av_error_stringformatted into a function-static buffer. It turns an FFmpeg error code into the text of DALI's error messages (opening a file, setting up the codec, sending packets to the decoder, converting frames, ...). When two threads fail at the same time, one thread can overwrite or clear the buffer while the other is still copying it into itsstd::string. The error message then shows the other thread's error, or a truncated/empty text. This is a data race on the error path, exactly when the message matters most.av_log_set_level(AV_LOG_ERROR). FFmpeg's log level is a plain global (av_log_set_levelonly stores it), so decoders created on several threads wrote it concurrently, while FFmpeg's threads read it. The value is always the same, so the effect is benign, but it is still a data race, and it reset any level set in the meantime for every new decoder.This already matters on main:
fn.experimental.decoders.videocreates its decoders and decodes its samples in parallel on a thread pool, one task per sample, so both are reached from several threads at once. Several video readers or decoders in one process (e.g. several pipelines) share them as well. It becomes more relevant with the parallel decoding infn.experimental.readers.videoplanned in #6563 and #6564, where several decode lanes each create and use their own decoder in parallel.The fix:
av_error_stringformats into a local, zero-initialized buffer ofAV_ERROR_MAX_STRING_SIZEinstead of astaticone, so each call has its own storage.frames_decoder_base.ccthat runs while the library is loaded, before any decoder exists, instead of by every decoder.This PR is split out of #6545, which was too large to review as one change. It is based directly on main and does not depend on the other PRs of the split.
Additional information:
Affected modules and functionalities:
dali/operators/video/video_utils.cc:av_error_stringuses a local buffer instead of a function-static one.dali/operators/video/frames_decoder_base.cc: the log level is set by a static initializer (a lambda called once), no longer in the twoFramesDecoderBaseconstructors.Affects every operator built on these decoders:
fn.experimental.readers.video,fn.experimental.decoders.videoandfn.experimental.inputs.video, CPU and GPU.Key points relevant for the review:
AV_LOG_ERRORwhen DALI's operators library is loaded (e.g.import nvidia.dali), not when the first video decoder is created. This also applies to other FFmpeg users in the process (PyAV, torchvision, ...) from that point. A level set later is kept: DALI no longer resets it for every new decoder.Tests:
dali/test/python/decoder/test_video.py(reader and decoder tests, CPU and GPU, including tests that expect decoding errors)dali/operators/video/frames_decoder_test.ccChecklist
Documentation
DALI team only
Requirements
REQ IDs: N/A
JIRA TASK: DALI-4935