Skip to content

Commit 6c75a77

Browse files
committed
[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 cursor on the chain is brought back to its previous entry, or at least 0 if it was in an invalid state before. TChain::LoadTree is also modified so that if the current tree is not connected to the list of friends, the connection is refreshed.
1 parent f33985d commit 6c75a77

File tree

3 files changed

+98
-0
lines changed

3 files changed

+98
-0
lines changed

tree/tree/src/TChain.cxx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -985,7 +985,9 @@ Long64_t TChain::GetEntries() const
985985
return fProofChain->GetEntries();
986986
}
987987
if (fEntries == TTree::kMaxEntries) {
988+
const auto readEntry = GetReadEntry();
988989
const_cast<TChain*>(this)->LoadTree(TTree::kMaxEntries-1);
990+
const_cast<TChain *>(this)->LoadTree(std::max(readEntry, 0ll));
989991
}
990992
return fEntries;
991993
}
@@ -1373,13 +1375,21 @@ Long64_t TChain::LoadTree(Long64_t entry)
13731375
TIter next(fFriends);
13741376
TFriendLock lock(this, kLoadTree);
13751377
TFriendElement* fe = nullptr;
1378+
const bool reconstructLocalTreeListOfFriends = fTree->GetListOfFriends() ? false : true;
13761379
while ((fe = (TFriendElement*) next())) {
13771380
TTree* at = fe->GetTree();
13781381
// If the tree is a
13791382
// direct friend of the chain, it should be scanned
13801383
// used the chain entry number and NOT the tree entry
13811384
// number (treeReadEntry) hence we do:
13821385
at->LoadTreeFriend(entry, this);
1386+
if (reconstructLocalTreeListOfFriends) {
1387+
TTree *friend_t = at->GetTree();
1388+
if (friend_t) {
1389+
auto localfe = fTree->AddFriend(at, fe->GetName());
1390+
localfe->SetBit(TFriendElement::kFromChain);
1391+
}
1392+
}
13831393
}
13841394
bool needUpdate = false;
13851395
if (fTree->GetListOfFriends()) {

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)