Repository navigation
[io] Fix data loss in nested directories during batched TFileMerger - #23791
JainAyush-01 wants to merge 4 commits into
Conversation
When TFileMerger merges files with SetMaxOpenedFiles (e.g. hadd -n),
merging is performed incrementally across batches with kIncremental.
In TFileMerger::MergeOne, the cleanup condition previously checked:
if (ownobj && (!(type & kIncremental) || dirobj->GetFile() != target))
For nested subdirectories at depth >= 2, `target` is the parent
TDirectoryFile, whereas `dirobj->GetFile()` returns the root TFile
(`fOutputFile`). Consequently, `dirobj->GetFile() != target` always
evaluated to true for nested directories, prematurely deleting the
in-memory directory before fOutputFile->Write() could flush it,
causing silent data loss of objects merged in subsequent batches.
Comparing `dirobj->GetFile() != fOutputFile` properly ensures that
directories belonging to the output file are not prematurely deleted
during incremental merges.
Fixes root-project#23666. Added regression test NestedDirMaxOpenedFiles in
TFileMergerTests.cxx.
* Allocate TFile and TTree on the heap (to avoid corner-case issues with DeleteChangesMemory) * Clarify intent of the test: it's not to use `kIncremental` but to exercise the `-n` option in hadd, using SetMaxOpenedFiles in TFileMerger * Also check correct TTree entry values
629d49d to
d901d78
Compare
3438441 to
65ae899
Compare
Test Results 24 files 24 suites 3d 23h 28m 7s ⏱️ Results for commit 971a552. ♻️ This comment has been updated with latest results. |
|
Thanks @vepadulano and @enirolf for improving the test and adding the comprehensive roottest coverage! I noticed in the CI run for commit 65ae899 that the Alma10 build failed in |
|
Hi @JainAyush-01, thank you for the proposed fix. I've changed the test to also run correctly without object auto-registration, let's see if the CI is happy now. |
|
Hi @JainAyush-01, thank you again for pointing us in the right direction for the fix. |
This Pull request:
Fixes silent data loss when merging ROOT files containing nested subdirectories (depth >= 2) with chunking / batching enabled (
TFileMerger::SetMaxOpenedFilesorhadd -n).Changes or fixes:
Root Cause:
When
TFileMergermerges files incrementally in batches (type & kIncremental),TFileMerger::MergeOnecleans up in-memory directory objects at lines 830–834:For top-level subdirectories (depth 1),
targetis theTFile*itself, sodirobj->GetFile() == targetevaluates to true anddirobjis preserved across batches until the finalfOutputFile->Write()flushes all accumulated objects.However, for nested subdirectories at depth$\ge 2$ (e.g.,
nested/dir):targetis the parentTDirectoryFile*(nested)dirobj->GetFile()returns the rootTFile*(fOutputFile)Because a
TDirectoryFile*does not equal aTFile*,dirobj->GetFile() != targetalways evaluated totrue. This prematurely deleted the in-memory subdirectorydirobjbefore subsequent batches could write and flush into it, causing silent loss of objects merged in later batches.Fix:
Change
dirobj->GetFile() != targettodirobj->GetFile() != fOutputFile. This correctly checks whetherdirobjbelongs to the output file being accumulated across incremental batches, ensuring nested directories and their contents remain intact in memory until written.Testing:
TEST(TFileMerger, NestedDirMaxOpenedFiles)inio/io/test/TFileMergerTests.cxx.-n 2,-n 3).gtest-io-io-TFileMergertest suite viactest.Checklist:
This PR fixes #23666
AI-assisted coding disclosure: I used an AI assistant to help navigate the codebase, structure the unit test, and format this PR description. I have personally reviewed, debugged, and tested the C++ changes locally and take full responsibility for them.