From db15ccffc9c5518fb43b8e3c414b6b296024d74b Mon Sep 17 00:00:00 2001 From: JainAyush-01 Date: Thu, 22 Oct 2026 05:47:28 +0000 Subject: [PATCH 1/3] [io] Fix data loss in nested directories during batched TFileMerger 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 #23666. Added regression test NestedDirMaxOpenedFiles in TFileMergerTests.cxx. --- io/io/src/TFileMerger.cxx | 2 +- io/io/test/TFileMergerTests.cxx | 86 +++++++++++++++++++++++++++++++++ 2 files changed, 87 insertions(+), 1 deletion(-) diff --git a/io/io/src/TFileMerger.cxx b/io/io/src/TFileMerger.cxx index 727bc530701a0..2ba0dd9f038f4 100644 --- a/io/io/src/TFileMerger.cxx +++ b/io/io/src/TFileMerger.cxx @@ -828,7 +828,7 @@ Bool_t TFileMerger::MergeOne(TDirectory *target, TList *sourcelist, Int_t type, // and we are in incremental mode (because it will be reused // and has not been written to disk (for performance reason). // coverity[var_deref_model] the IsA()->InheritsFrom guarantees that the dynamic_cast will succeed. - if (ownobj && (!(type & kIncremental) || dirobj->GetFile() != target)) { + if (ownobj && (!(type & kIncremental) || dirobj->GetFile() != fOutputFile)) { dirobj->ResetBit(kMustCleanup); delete dirobj; } diff --git a/io/io/test/TFileMergerTests.cxx b/io/io/test/TFileMergerTests.cxx index 137bf1f89bc34..3ac955ed9f24a 100644 --- a/io/io/test/TFileMergerTests.cxx +++ b/io/io/test/TFileMergerTests.cxx @@ -478,3 +478,89 @@ TEST(TFileMerger, TypeMismatchErrorTObjectWithNonTObject) fm.PartialMerge(); } } + +// https://github.com/root-project/root/issues/23666 +TEST(TFileMerger, NestedDirMaxOpenedFiles) +{ + struct CleanupRAII { + std::vector fFiles; + ~CleanupRAII() + { + for (const auto &file : fFiles) { + gSystem->Unlink(file.c_str()); + } + } + } cleanup; + + const std::string file1 = "test_nested_merge_in1.root"; + const std::string file2 = "test_nested_merge_in2.root"; + const std::string file3 = "test_nested_merge_in3.root"; + const std::string fileOut = "test_nested_merge_out.root"; + + cleanup.fFiles.push_back(file1); + cleanup.fFiles.push_back(file2); + cleanup.fFiles.push_back(file3); + cleanup.fFiles.push_back(fileOut); + + const std::vector> inputs = { + {file1, 5}, + {file2, 7}, + {file3, 11} + }; + + // Create 3 input files with objects inside a nested directory (depth >= 2) + for (const auto &input : inputs) { + TFile f(input.first.c_str(), "RECREATE"); + ASSERT_FALSE(f.IsZombie()); + auto topDir = f.mkdir("nested"); + ASSERT_NE(topDir, nullptr); + auto subDir = topDir->mkdir("dir"); + ASSERT_NE(subDir, nullptr); + subDir->cd(); + + TTree tree("tree", "Test Tree"); + int val = 0; + tree.Branch("val", &val, "val/I"); + for (int i = 0; i < input.second; ++i) { + val = i; + tree.Fill(); + } + + TH1F hist("hist", "Test Hist", 10, 0, 10); + for (int i = 0; i < input.second; ++i) { + hist.Fill(i % 10); + } + + f.Write(); + } + + // Merge using TFileMerger with SetMaxOpenedFiles(3) to force incremental batching across multiple batches + { + TFileMerger merger(false); + EXPECT_TRUE(merger.OutputFile(fileOut.c_str())); + merger.SetMaxOpenedFiles(3); + merger.AddFile(file1.c_str(), false); + merger.AddFile(file2.c_str(), false); + merger.AddFile(file3.c_str(), false); + EXPECT_TRUE(merger.Merge()); + } + + // Verify that all entries from all files (including the final incremental batch) are present + { + TFile fOut(fileOut.c_str(), "READ"); + ASSERT_FALSE(fOut.IsZombie()); + + auto topDir = fOut.Get("nested"); + ASSERT_NE(topDir, nullptr); + auto subDir = topDir->Get("dir"); + ASSERT_NE(subDir, nullptr); + + auto tree = subDir->Get("tree"); + ASSERT_NE(tree, nullptr); + EXPECT_EQ(tree->GetEntries(), 5 + 7 + 11); + + auto hist = subDir->Get("hist"); + ASSERT_NE(hist, nullptr); + EXPECT_EQ(hist->GetEntries(), 5 + 7 + 11); + } +} From d901d781e9d4c04ace7e829a30d77b9953d94f8c Mon Sep 17 00:00:00 2001 From: Vincenzo Eduardo Padulano Date: Fri, 23 Oct 2026 17:34:07 +0200 Subject: [PATCH 2/3] Improve test * 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 --- io/io/test/TFileMergerTests.cxx | 52 +++++++++++++++++++-------------- 1 file changed, 30 insertions(+), 22 deletions(-) diff --git a/io/io/test/TFileMergerTests.cxx b/io/io/test/TFileMergerTests.cxx index 3ac955ed9f24a..b6241c37a0fef 100644 --- a/io/io/test/TFileMergerTests.cxx +++ b/io/io/test/TFileMergerTests.cxx @@ -479,7 +479,7 @@ TEST(TFileMerger, TypeMismatchErrorTObjectWithNonTObject) } } -// https://github.com/root-project/root/issues/23666 +// Regression test for https://github.com/root-project/root/issues/23666 TEST(TFileMerger, NestedDirMaxOpenedFiles) { struct CleanupRAII { @@ -502,28 +502,24 @@ TEST(TFileMerger, NestedDirMaxOpenedFiles) cleanup.fFiles.push_back(file3); cleanup.fFiles.push_back(fileOut); - const std::vector> inputs = { - {file1, 5}, - {file2, 7}, - {file3, 11} - }; + const std::vector> inputs = {{file1, 5}, {file2, 7}, {file3, 11}}; // Create 3 input files with objects inside a nested directory (depth >= 2) for (const auto &input : inputs) { - TFile f(input.first.c_str(), "RECREATE"); - ASSERT_FALSE(f.IsZombie()); - auto topDir = f.mkdir("nested"); + auto f = std::make_unique(input.first.c_str(), "RECREATE"); + ASSERT_FALSE(f->IsZombie()); + auto topDir = f->mkdir("nested"); ASSERT_NE(topDir, nullptr); auto subDir = topDir->mkdir("dir"); ASSERT_NE(subDir, nullptr); subDir->cd(); - TTree tree("tree", "Test Tree"); + auto tree = std::make_unique("tree", "Test Tree"); int val = 0; - tree.Branch("val", &val, "val/I"); + tree->Branch("val", &val, "val/I"); for (int i = 0; i < input.second; ++i) { val = i; - tree.Fill(); + tree->Fill(); } TH1F hist("hist", "Test Hist", 10, 0, 10); @@ -531,26 +527,28 @@ TEST(TFileMerger, NestedDirMaxOpenedFiles) hist.Fill(i % 10); } - f.Write(); + f->Write(); } - // Merge using TFileMerger with SetMaxOpenedFiles(3) to force incremental batching across multiple batches + // Merge the input files into the output file. The original issue was triggered by the use of the `-n` parameter in + // `hadd`, which opens at most `N` files at once, set to the same number of input files (3). The merger used to only + // merge data from the first two files leaving out data from the third. { - TFileMerger merger(false); + TFileMerger merger{false}; EXPECT_TRUE(merger.OutputFile(fileOut.c_str())); merger.SetMaxOpenedFiles(3); - merger.AddFile(file1.c_str(), false); - merger.AddFile(file2.c_str(), false); - merger.AddFile(file3.c_str(), false); + merger.AddFile(file1.c_str(), /*cpProgress*/ false); + merger.AddFile(file2.c_str(), /*cpProgress*/ false); + merger.AddFile(file3.c_str(), /*cpProgress*/ false); EXPECT_TRUE(merger.Merge()); } - // Verify that all entries from all files (including the final incremental batch) are present + // Verify correct values for merged objects in the output file { - TFile fOut(fileOut.c_str(), "READ"); - ASSERT_FALSE(fOut.IsZombie()); + auto fOut = std::make_unique(fileOut.c_str(), "READ"); + ASSERT_FALSE(fOut->IsZombie()); - auto topDir = fOut.Get("nested"); + auto topDir = fOut->Get("nested"); ASSERT_NE(topDir, nullptr); auto subDir = topDir->Get("dir"); ASSERT_NE(subDir, nullptr); @@ -558,6 +556,16 @@ TEST(TFileMerger, NestedDirMaxOpenedFiles) auto tree = subDir->Get("tree"); ASSERT_NE(tree, nullptr); EXPECT_EQ(tree->GetEntries(), 5 + 7 + 11); + int val = 0; + tree->SetBranchAddress("val", &val); + int entryCursor{}; + for (const auto &input : inputs) { + for (int i = 0; i < input.second; ++i) { + tree->GetEntry(entryCursor); + EXPECT_EQ(val, i); + entryCursor++; + } + } auto hist = subDir->Get("hist"); ASSERT_NE(hist, nullptr); From 65ae899ddf25c9b94d98edbfe336e60ee2ea7967 Mon Sep 17 00:00:00 2001 From: Florine de Geus Date: Fri, 23 Oct 2026 18:31:03 +0200 Subject: [PATCH 3/3] [io] Add regression tests for #23666 --- roottest/root/io/hadd/CMakeLists.txt | 23 ++++ ...hadd_check_nested_objects_128files_23666.C | 12 ++ .../hadd_check_nested_objects_argn_23666.C | 12 ++ ...est_hadd_nested_objects_128files_23666.txt | 128 ++++++++++++++++++ .../hadd/test_hadd_nested_objects_23666.root | Bin 0 -> 5617 bytes 5 files changed, 175 insertions(+) create mode 100644 roottest/root/io/hadd/hadd_check_nested_objects_128files_23666.C create mode 100644 roottest/root/io/hadd/hadd_check_nested_objects_argn_23666.C create mode 100644 roottest/root/io/hadd/test_hadd_nested_objects_128files_23666.txt create mode 100644 roottest/root/io/hadd/test_hadd_nested_objects_23666.root diff --git a/roottest/root/io/hadd/CMakeLists.txt b/roottest/root/io/hadd/CMakeLists.txt index af76895b12106..163b575a709ba 100644 --- a/roottest/root/io/hadd/CMakeLists.txt +++ b/roottest/root/io/hadd/CMakeLists.txt @@ -284,3 +284,26 @@ ROOTTEST_ADD_TEST(test_hadd_merge_rntuple_634, COMMAND ${ROOT_hadd_CMD} -f test_hadd_merge_rntuple_634_merged.root test_hadd_merge_rntuple_634_1.root test_hadd_merge_rntuple_634_2.root POSTCMD ${ROOT_root_CMD} -q check_merge_rntuple_634.C PASSRC 0) + + +# Regression test for https://github.com/root-project/root/issues/23666 +configure_file(test_hadd_nested_objects_23666.root . COPYONLY) +configure_file(hadd_check_nested_objects_argn_23666.C . COPYONLY) +ROOTTEST_ADD_TEST(test_hadd_nested_objects_argn_23666, + PRECMD ${CMAKE_COMMAND} -E rm -f test_hadd_nested_objects_argn_23666_merged.root + COMMAND ${ROOT_hadd_CMD} -f -n 3 test_hadd_nested_objects_argn_23666_merged.root test_hadd_nested_objects_23666.root test_hadd_nested_objects_23666.root test_hadd_nested_objects_23666.root + POSTCMD ${ROOT_root_CMD} -q hadd_check_nested_objects_argn_23666.C + PASSRC 0) + +configure_file(hadd_check_nested_objects_128files_23666.C . COPYONLY) +configure_file(test_hadd_nested_objects_128files_23666.txt . COPYONLY) +ROOTTEST_ADD_TEST(test_hadd_nested_objects_128files_23666, + PRECMD ${CMAKE_COMMAND} -E rm -f test_hadd_nested_objects_128files_23666_merged.root + COMMAND ${ROOT_hadd_CMD} -f test_hadd_nested_objects_128files_23666_merged.root @test_hadd_nested_objects_128files_23666.txt + POSTCMD ${ROOT_root_CMD} -q hadd_check_nested_objects_128files_23666.C + PASSRC 0) +ROOTTEST_ADD_TEST(test_hadd_nested_objects_128files_n16_23666, + PRECMD ${CMAKE_COMMAND} -E rm -f test_hadd_nested_objects_128files_23666_merged.root + COMMAND ${ROOT_hadd_CMD} -f -n 16 test_hadd_nested_objects_128files_23666_merged.root @test_hadd_nested_objects_128files_23666.txt + POSTCMD ${ROOT_root_CMD} -q hadd_check_nested_objects_128files_23666.C + PASSRC 0) diff --git a/roottest/root/io/hadd/hadd_check_nested_objects_128files_23666.C b/roottest/root/io/hadd/hadd_check_nested_objects_128files_23666.C new file mode 100644 index 0000000000000..a3a04ef4d46b8 --- /dev/null +++ b/roottest/root/io/hadd/hadd_check_nested_objects_128files_23666.C @@ -0,0 +1,12 @@ +int hadd_check_nested_objects_argn_23666() +{ + auto file = std::unique_ptr(TFile::Open("test_hadd_nested_objects_128files_23666_merged.root")); + auto tree = std::unique_ptr(file->Get("A/B/tree")); + + if (auto nentries = tree->GetEntries(); nentries != 128) { + std::cerr << "Number of entries is " << nentries << " (expected 128) \n"; + return 1; + } + + return 0; +} diff --git a/roottest/root/io/hadd/hadd_check_nested_objects_argn_23666.C b/roottest/root/io/hadd/hadd_check_nested_objects_argn_23666.C new file mode 100644 index 0000000000000..10f0c49e2904f --- /dev/null +++ b/roottest/root/io/hadd/hadd_check_nested_objects_argn_23666.C @@ -0,0 +1,12 @@ +int hadd_check_nested_objects_argn_23666() +{ + auto file = std::unique_ptr(TFile::Open("test_hadd_nested_objects_argn_23666_merged.root")); + auto tree = std::unique_ptr(file->Get("A/B/tree")); + + if (auto nentries = tree->GetEntries(); nentries != 3) { + std::cerr << "Number of entries is " << nentries << " (expected 3) \n"; + return 1; + } + + return 0; +} diff --git a/roottest/root/io/hadd/test_hadd_nested_objects_128files_23666.txt b/roottest/root/io/hadd/test_hadd_nested_objects_128files_23666.txt new file mode 100644 index 0000000000000..2e06063af6390 --- /dev/null +++ b/roottest/root/io/hadd/test_hadd_nested_objects_128files_23666.txt @@ -0,0 +1,128 @@ +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root +test_hadd_nested_objects_23666.root diff --git a/roottest/root/io/hadd/test_hadd_nested_objects_23666.root b/roottest/root/io/hadd/test_hadd_nested_objects_23666.root new file mode 100644 index 0000000000000000000000000000000000000000..3cd791859889e2d286ae443114f4a88c935c4fcc GIT binary patch literal 5617 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 LM0ViHn*jU^k_XaU literal 0 HcmV?d00001