Repository navigation
Make the video reader's frame index cache thread-safe - #6558
Open
jantonguirao wants to merge 2 commits into
Open
jantonguirao wants to merge 2 commits into
jantonguirao wants to merge 2 commits into
Conversation
FrameIndexCache::find returned an iterator after releasing its lock and callers copied the index through it without the lock, while insert() could overwrite the same entry (two readers indexing the same file) or rehash the map. The cache moves to its own files. GetOrBuild builds every index once per file, outside the lock (a shared_mutex, so lookups run in parallel), and returns an immutable shared_ptr; Find never builds nor waits. Indices are kept for the lifetime of the process, as before, and an index is built again when the file's size or modification time changed, taken before the file is opened. instance() is defined in a .cc file, so that there is one cache per process. Each reader holds the indices of its files and decodes with those, without cache lookups after setup, so an index cannot change under a reader. Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
This was referenced Oct 27, 2026
There was a problem hiding this comment.
🟡 Changes recommended
Repeating the new tests in one process reuses cached entries, causing assertion failures and an indefinite wait.
1 open issue
What changed in this PR
This PR makes the experimental video reader’s process-wide frame index cache thread-safe and adds file-change invalidation, supporting concurrent readers and future parallel decoding.
Changes:
- Introduces a shared cache with immutable indices, coordinated builds, and retry after failures.
- Keeps setup-time indices in each reader for reuse during decoding.
- Adds tests for concurrency, index lifetime, failure handling, and invalidation.
| File | Description |
|---|---|
dali/operators/video/reader/video_reader_decoder_op.cc |
Integrates the new cache and retains each reader’s indices. |
dali/operators/video/reader/frame_index_cache.h |
Defines cache interfaces, ownership, and coordinated builds. |
dali/operators/video/reader/frame_index_cache.cc |
Implements file stamps, synchronized lookups, and publication. |
dali/operators/video/reader/frame_index_cache_test.cc |
Tests concurrency, retention, retries, and file-change invalidation. |
Review effort: Balanced
Contributor
[Medium impact] The PR appears safe to merge; the previous test gap is addressed and no new actionable issues were found.
|
Keys get a per-run prefix, so that --gtest_repeat does not find the entries of the previous run in the process-wide cache. A new test checks that a build which finishes (or fails) after its entry was replaced leaves the replacement in place. Signed-off-by: Joaquin Anton Guirao <janton@nvidia.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Category:
Bug fix (non-breaking change which fixes an issue)
Description:
fn.experimental.readers.video(CPU and GPU) keeps a process-wide cache of frame indices, keyed by file name, so that a file is indexed once per process. That cache had a data race:FrameIndexCache::findtook a shared lock, looked the file up and returned an iterator after releasing the lock. Callers then copied theFrameIndexthrough that iterator without holding any lock.insert()(under the exclusive lock) could overwrite the very same entry, when two readers index the same file, or insert a new entry and rehash the map, which invalidates every outstanding iterator.This happens whenever more than one video reader runs in the same process and they set up or switch files at the same time: several pipelines with a video reader over the same (or overlapping) file lists, pipelines built from different threads, or two readers in one pipeline. The result is undefined behavior, which can show up as a torn or corrupted copy of the index (wrong frame count, timestamps or keyframe flags, and so wrong frames decoded) or as a crash. Fixing it is also a prerequisite for decoding a batch with several decoders in one reader (#6563, #6564), where the decoders of a single reader get the indices of the files they open concurrently.
A second, smaller problem: a cached index was never invalidated. If a file was rewritten during the lifetime of the process, every later reader reused the index of the old contents.
This PR fixes both:
frame_index_cache.{h,cc}.FrameIndexCache::instance()is defined in the.ccfile, so that there is one cache per process.GetOrBuild(filename, stamp, build)returns the index of a file, building it withbuild()if it is not cached. Each index is built once: other callers asking for the same file wait for that build (a shared future) instead of building it again. The build runs outside the lock, and lookups take ashared_mutexin shared mode, so lookups and the indexing of different files run in parallel. If a build throws, the exception is rethrown to every caller waiting for it and the entry is removed, so that a later call can try again.std::shared_ptr<const FrameIndex>, which stays valid whatever happens to the map afterwards.FileStamp) changed. The stamp is taken before the decoder opens the file, so that an index of a file rewritten in the meantime is not cached as that of the new version.Find(filename)returns a cached, ready index ornullptr; it never builds and never waits.VideoLoaderDecoder::indices_), obtained inPrepareMetadataImpl, and uses them when it opens a file during decoding (FileIndex), without cache lookups after setup. So an index cannot change under a reader, even if the file is rewritten and another reader rebuilds its index.The debug statistics printed by the old cache's destructor are removed with it.
Split out of #6545. Independent of the other PRs of the split; based directly on
main.Additional information:
Affected modules and functionalities:
dali/operators/video/reader/frame_index_cache.h,frame_index_cache.cc(new):FrameIndexCachewithGetOrBuild,Find,FileStamp.dali/operators/video/reader/video_reader_decoder_op.cc:FrameIndexCacheclass is removed;VideoLoaderDecoder::PrepareMetadataImplgets each file's index withGetOrBuildand stores it inindices_;VideoLoaderDecoder::FileIndex/VideoReaderDecoder::FileIndexgive the reader the index it was set up with;VideoReaderDecoder::Prefetchsets the decoder's index fromFileIndexinstead of looking up (or building) it in the cache.dali/operators/video/reader/frame_index_cache_test.cc(new).Only
fn.experimental.readers.videouses this cache;fn.experimental.decoders.videois not affected.Key points relevant for the review:
GetOrBuild: the double lookup (shared lock, then exclusive lock) and theidthat tells an entry from a later one of the same file, so thatFinishdoes not publish (or erase) an entry that was replaced, because the file changed, while it was being built.FileStampis taken inPrepareMetadataImplbefore the decoder opens the file. A file that cannot bestat-ed gets the stamp{-1, -1}, so its index is still cached and reused.Prefetch, a file the reader opens must have an index from setup; a missing one is reported as an internal error (DALI_ENFORCE).Tests:
decoder/test_video.py(fn.experimental.readers.videotests),checkpointing/test_dali_checkpointing.py: test_experimental_video_reader*frame_index_cache_test.cc:FrameIndexCacheTest.BuildsEachIndexOnceUnderConcurrency: 16 threads callGetOrBuildover 64 keys; each index is built exactly once and every caller gets the right index.FrameIndexCacheTest.IndexStaysValidAfterOtherInsertions: an index stays valid while 999 more entries force rehashing.FrameIndexCacheTest.FailedBuildIsReportedAndRetriedFrameIndexCacheTest.FindDoesNotBuildOrWaitFrameIndexCacheTest.IndexIsKeptAfterItsLastUserFrameIndexCacheTest.IndexIsRebuiltWhenTheFileChangesChecklist
Documentation
DALI team only
Requirements
REQ IDs: N/A
JIRA TASK: DALI-4935