Skip to content

Commit 5fbb4b1

Browse files
vepadulanopcanal
andcommitted
[tree] Fix interaction between GetEntries and GetListOfFriends
Fixes #17820 Full analysis of the issue follows: 1. TChain::GetEntries calls TChain::LoadTree(TTree::kMaxEntries - 1) 2. Internally, this will set fReadEntry == -2, due to how LoadTree works and the fact it's trying to read beyond the last entry in the chain 3. This has an effect here https://github.com/root-project/root/blob/f33985dca2d6bc505e83498f7b4561ddd969be17/tree/tree/src/TChain.cxx#L1675-L1679 where `t` is the current friend being traversed in the list of friends of the *chain*. We call `t->LoadTree(TTree::kMaxEntries - 1)`. 4. Inside the call to LoadTree on the friend, we reach this line https://github.com/root-project/root/blob/f33985dca2d6bc505e83498f7b4561ddd969be17/tree/tree/src/TChain.cxx#L1484 where the fTree data member of the friend is invalidated. 5. Back to the LoadTree of the main chain, these lines https://github.com/root-project/root/blob/f33985dca2d6bc505e83498f7b4561ddd969be17/tree/tree/src/TChain.cxx#L1681-L1684 would normally connect the fTree data member of the main chain to the current friend, so the list of friends is properly populated. But due to 4., `friend->GetTree()` will return nullptr so the connection won't happen. This commit changes the implementation of TChain::GetEntries, so that after computing the total number of entries the chain is brought back to an invalid but recoverable state, and if the previous read entry was at least zero, the cursor is moved to it. Co-authored-by: Philippe Canal <[email protected]>
1 parent f33985d commit 5fbb4b1

File tree

3 files changed

+93
-0
lines changed

3 files changed

+93
-0
lines changed

tree/tree/src/TChain.cxx

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,12 @@ Long64_t TChain::GetEntries() const
985985
return fProofChain->GetEntries();
986986
}
987987
if (fEntries == TTree::kMaxEntries) {
988+
auto readEntry = fReadEntry;
988989
const_cast<TChain*>(this)->LoadTree(TTree::kMaxEntries-1);
990+
const_cast<TChain*>(this)->fTreeNumber = -1;
991+
const_cast<TChain*>(this)->fReadEntry = -1;
992+
if (readEntry >= 0)
993+
const_cast<TChain*>(this)->LoadTree(readEntry);
989994
}
990995
return fEntries;
991996
}

tree/treeplayer/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,3 +23,5 @@ endif()
2323
ROOT_ADD_GTEST(ttreeindex_clone ttreeindex_clone.cxx LIBRARIES TreePlayer)
2424

2525
ROOT_ADD_GTEST(ttreereader_friends ttreereader_friends.cxx LIBRARIES TreePlayer)
26+
27+
ROOT_ADD_GTEST(ttreeindex_getlistoffriends ttreeindex_getlistoffriends.cxx LIBRARIES TreePlayer)
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
#include "TChain.h"
2+
#include "TFile.h"
3+
#include "TFriendElement.h"
4+
#include "TTree.h"
5+
6+
#include "gtest/gtest.h"
7+
8+
#include <string_view>
9+
10+
void write_data(std::string_view treename, std::string_view filename)
11+
{
12+
TFile f{filename.data(), "update"};
13+
TTree t{treename.data(), treename.data()};
14+
int runNumber{};
15+
int eventNumber{};
16+
float val{};
17+
t.Branch("runNumber", &runNumber, "runNumber/I");
18+
t.Branch("eventNumber", &eventNumber, "eventNumber/I");
19+
t.Branch("val", &val, "val/F");
20+
if (treename == "main") {
21+
for (auto rn = 0; rn < 3; rn++) {
22+
runNumber = rn;
23+
for (auto en = 0; en < 5; en++) {
24+
eventNumber = en;
25+
val = en * rn;
26+
t.Fill();
27+
}
28+
}
29+
} else {
30+
for (auto rn = 0; rn < 3; rn++) {
31+
runNumber = rn;
32+
for (auto en = 4; en >= 0; en--) {
33+
eventNumber = en;
34+
val = en * rn;
35+
t.Fill();
36+
}
37+
}
38+
}
39+
40+
f.Write();
41+
}
42+
43+
struct TTreeIndexGH_17820 : public ::testing::Test {
44+
constexpr static auto fFileName{"ttreeindex_getlistoffriends_gh_17820.root"};
45+
constexpr static auto fMainName{"main"};
46+
constexpr static auto fFriendName{"friend"};
47+
48+
static void SetUpTestCase()
49+
{
50+
write_data(fMainName, fFileName);
51+
write_data(fFriendName, fFileName);
52+
}
53+
54+
static void TearDownTestCase() { std::remove(fFileName); }
55+
};
56+
57+
// Regression test for https://github.com/root-project/root/issues/17820
58+
TEST_F(TTreeIndexGH_17820, RunTest)
59+
{
60+
TChain mainChain{fMainName};
61+
mainChain.AddFile(fFileName);
62+
63+
TChain friendChain{fFriendName};
64+
friendChain.AddFile(fFileName);
65+
friendChain.BuildIndex("runNumber", "eventNumber");
66+
67+
mainChain.AddFriend(&friendChain);
68+
69+
// Calling GetEntries used to mess with the fTree data member of the main
70+
// chain, not connecting it to the friend chain and thus losing the list
71+
// of friends
72+
mainChain.GetEntries();
73+
74+
const auto *listOfFriends = mainChain.GetTree()->GetListOfFriends();
75+
76+
ASSERT_TRUE(listOfFriends);
77+
EXPECT_EQ(listOfFriends->GetEntries(), 1);
78+
79+
const auto *friendTree = dynamic_cast<TTree *>(dynamic_cast<TFriendElement *>(listOfFriends->At(0))->GetTree());
80+
ASSERT_TRUE(friendTree);
81+
82+
EXPECT_STREQ(friendTree->GetName(), fFriendName);
83+
const auto *friendFile = friendTree->GetCurrentFile();
84+
ASSERT_TRUE(friendFile);
85+
EXPECT_STREQ(friendFile->GetName(), fFileName);
86+
}

0 commit comments

Comments
 (0)