Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 13 additions & 7 deletions io/io/src/TFile.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -4116,13 +4116,19 @@ TFile *TFile::Open(const char *url, Option_t *options, const char *ftitle,
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
if (len > 0) {
std::string xurl(len, 0);
if (getxattr(fileurl.GetFile(), "eos.url.xroot", &xurl[0], len) == len) {
if ((f = TFile::Open(xurl.c_str(), options, ftitle, compress, netopt))) {
if (!f->IsZombie()) {
return f;
} else {
delete f;
f = nullptr;
std::string fileNameFromUrl{fileurl.GetFile()};
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
// Sometimes the `getxattr` call may return an invalid URL due
// to the POSIX attribute not being yet completely filled by EOS.
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
if ((f = TFile::Open(xurl.c_str(), options, ftitle, compress, netopt))) {
if (!f->IsZombie()) {
return f;
} else {
delete f;
f = nullptr;
}
}
}
}
Expand Down
56 changes: 56 additions & 0 deletions tree/dataframe/src/RLoopManager.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,25 @@
#include "ROOT/RNTupleDS.hxx"
#endif

#ifdef R__UNIX
// Functions needed to perform EOS XRootD redirection in ChangeSpec
#include <optional>
#include "TEnv.h"
#include "TSystem.h"
#ifndef R__FBSD
#include <sys/xattr.h>
#else
#include <sys/extattr.h>
#endif
#ifdef R__MACOSX
/* On macOS getxattr takes two extra arguments that should be set to 0 */
#define getxattr(path, name, value, size) getxattr(path, name, value, size, 0u, 0)
#endif
#ifdef R__FBSD
#define getxattr(path, name, value, size) extattr_get_file(path, EXTATTR_NAMESPACE_USER, name, value, size)
#endif
#endif

#include <algorithm>
#include <atomic>
#include <cassert>
Expand Down Expand Up @@ -403,6 +422,38 @@ RLoopManager::RLoopManager(ROOT::RDF::Experimental::RDatasetSpec &&spec)
ChangeSpec(std::move(spec));
}

#ifdef R__UNIX
namespace {
std::optional<std::string> GetRedirectedSampleId(std::string_view path, std::string_view datasetName)
{
// Mimick the redirection done in TFile::Open to see if the path points to a FUSE-mounted EOS path.
// If so, we create a redirected sample ID with the full xroot URL.
TString expandedUrl(path.data());
gSystem->ExpandPathName(expandedUrl);
if (gEnv->GetValue("TFile.CrossProtocolRedirects", 1) == 1) {
TUrl fileurl(expandedUrl, /* default is file */ kTRUE);
if (strcmp(fileurl.GetProtocol(), "file") == 0) {
ssize_t len = getxattr(fileurl.GetFile(), "eos.url.xroot", nullptr, 0);
if (len > 0) {
std::string xurl(len, 0);
std::string fileNameFromUrl{fileurl.GetFile()};
if (getxattr(fileNameFromUrl.c_str(), "eos.url.xroot", &xurl[0], len) == len) {
// Sometimes the `getxattr` call may return an invalid URL due
// to the POSIX attribute not being yet completely filled by EOS.
if (auto baseName = fileNameFromUrl.substr(fileNameFromUrl.find_last_of("/") + 1);
std::equal(baseName.crbegin(), baseName.crend(), xurl.crbegin())) {
return xurl + '/' + datasetName.data();
}
}
}
}
}

return std::nullopt;
}
} // namespace
#endif

/**
* @brief Changes the internal TTree held by the RLoopManager.
*
Expand Down Expand Up @@ -441,6 +492,11 @@ void RLoopManager::ChangeSpec(ROOT::RDF::Experimental::RDatasetSpec &&spec)
// is exposed to users via RSampleInfo and DefinePerSample).
const auto sampleId = files[i] + '/' + trees[i];
fSampleMap.insert({sampleId, &sample});
#ifdef R__UNIX
// Also add redirected EOS xroot URL when available
if (auto redirectedSampleId = GetRedirectedSampleId(files[i], trees[i]))
fSampleMap.insert({redirectedSampleId.value(), &sample});
#endif
}
}
SetTree(std::move(chain));
Expand Down
13 changes: 12 additions & 1 deletion tree/tree/src/TChain.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -985,7 +985,18 @@ Long64_t TChain::GetEntries() const
return fProofChain->GetEntries();
}
if (fEntries == TTree::kMaxEntries) {
const_cast<TChain*>(this)->LoadTree(TTree::kMaxEntries-1);
// If the following is true, we are within a recursion about friend,
// and `LoadTree` will be no-op.
if (kLoadTree & fFriendLockStatus)
return fEntries;
const auto readEntry = fReadEntry;
auto *thisChain = const_cast<TChain *>(this);
thisChain->LoadTree(TTree::kMaxEntries - 1);
thisChain->InvalidateCurrentTree();
if (readEntry >= 0)
thisChain->LoadTree(readEntry);
else
thisChain->fReadEntry = readEntry;
}
return fEntries;
}
Expand Down
2 changes: 1 addition & 1 deletion tree/treeplayer/src/TTreeIndex.cxx
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ Long64_t TTreeIndex::GetEntryNumberFriend(const TTree *parent)
if (!parent) return -3;
// We reached the end of the parent tree
Long64_t pentry = parent->GetReadEntry();
if (pentry >= parent->GetEntries())
if (pentry >= parent->GetEntriesFast())
return -2;
GetMajorFormulaParent(parent);
GetMinorFormulaParent(parent);
Expand Down
2 changes: 2 additions & 0 deletions tree/treeplayer/test/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,5 @@ endif()
ROOT_ADD_GTEST(ttreeindex_clone ttreeindex_clone.cxx LIBRARIES TreePlayer)

ROOT_ADD_GTEST(ttreereader_friends ttreereader_friends.cxx LIBRARIES TreePlayer)

ROOT_ADD_GTEST(ttreeindex_getlistoffriends ttreeindex_getlistoffriends.cxx LIBRARIES TreePlayer)
114 changes: 114 additions & 0 deletions tree/treeplayer/test/ttreeindex_getlistoffriends.cxx
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#include "TBranch.h"
#include "TChain.h"
#include "TCollection.h" // TRangeDynCast
#include "TFile.h"
#include "TFriendElement.h"
#include "TObjArray.h"
#include "TTree.h"

#include "gtest/gtest.h"

#include <string>
#include <string_view>
#include <vector>

void write_data(std::string_view treename, std::string_view filename)
{
TFile f{filename.data(), "update"};
TTree t{treename.data(), treename.data()};
int runNumber{};
int eventNumber{};
float val{};
t.Branch("runNumber", &runNumber, "runNumber/I");
t.Branch("eventNumber", &eventNumber, "eventNumber/I");
t.Branch("val", &val, "val/F");
if (treename == "main") {
for (auto rn = 0; rn < 3; rn++) {
runNumber = rn;
for (auto en = 0; en < 5; en++) {
eventNumber = en;
val = en * rn;
t.Fill();
}
}
} else {
for (auto rn = 0; rn < 3; rn++) {
runNumber = rn;
for (auto en = 4; en >= 0; en--) {
eventNumber = en;
val = en * rn;
t.Fill();
}
}
}

f.Write();
}

struct TTreeIndexGH_17820 : public ::testing::Test {
constexpr static auto fFileName{"ttreeindex_getlistoffriends_gh_17820.root"};
constexpr static auto fMainName{"main"};
constexpr static auto fFriendName{"friend"};

static void SetUpTestCase()
{
write_data(fMainName, fFileName);
write_data(fFriendName, fFileName);
}

static void TearDownTestCase() { std::remove(fFileName); }
};

void expect_branch_names(const TObjArray *branches, const std::vector<std::string> &branchNames)
{
auto nBranchNames = branchNames.size();
decltype(nBranchNames) nBranches{};
for (const auto *br : TRangeDynCast<TBranch>(branches)) {
EXPECT_STREQ(br->GetName(), branchNames[nBranches].c_str());
nBranches++;
}
EXPECT_EQ(nBranches, nBranchNames);
}

// Regression test for https://github.com/root-project/root/issues/17820
TEST_F(TTreeIndexGH_17820, RunTest)
{
TChain mainChain{fMainName};
mainChain.AddFile(fFileName);

TChain friendChain{fFriendName};
friendChain.AddFile(fFileName);
friendChain.BuildIndex("runNumber", "eventNumber");

mainChain.AddFriend(&friendChain);

// Calling GetEntries used to mess with the fTree data member of the main
// chain, not connecting it to the friend chain and thus losing the list
// of friends. This in turn corrupted the list of branches.
mainChain.GetEntries();

const auto *listOfBranches = mainChain.GetListOfBranches();
ASSERT_TRUE(listOfBranches);

const std::vector<std::string> expectedNames{"runNumber", "eventNumber", "val"};

expect_branch_names(listOfBranches, expectedNames);

const auto *curTree = mainChain.GetTree();
ASSERT_TRUE(curTree);

const auto *listOfFriends = mainChain.GetTree()->GetListOfFriends();
ASSERT_TRUE(listOfFriends);
EXPECT_EQ(listOfFriends->GetEntries(), 1);

auto *friendTree = dynamic_cast<TTree *>(dynamic_cast<TFriendElement *>(listOfFriends->At(0))->GetTree());
ASSERT_TRUE(friendTree);

EXPECT_STREQ(friendTree->GetName(), fFriendName);
const auto *friendFile = friendTree->GetCurrentFile();
ASSERT_TRUE(friendFile);
EXPECT_STREQ(friendFile->GetName(), fFileName);

const auto *friendBranches = friendTree->GetListOfBranches();
expect_branch_names(friendBranches, expectedNames);
}
Loading