Crashes when reading a ttree with a friend

Dear ROOTers

I am unable to properly work with a TTree written out to a file with a friend. Both ROOT and PyROOT simply crash on TFile::Close(). What am I doing wrong? I attach a PyROOT script that creates the trees, saves them then reads and attempts to close the file.


ROOT Version: 6.24.00
Platform: Fedora 34
test_friends.py (605 Bytes)

Compiler: Not Provided


Hi @LeWhoo ,
I can reproduce the problem but I’m not sure how to fix it, we need @etejedor 's help.

Cheers,
Enrico

IIUC this is more for @pcanal? It crashes both in Python and in C++, right?

It’s just a normal usage of friends so I’d expect a C++ version to work.

Perhaps @LeWhoo can clarify, he said Both ROOT and PyROOT simply crash on TFile::Close()

Yes, it crashes also in C++. Give me a second to prepare a C++ working example.

This is how you would do that in C++ (seems to work):

#include <TTree.h>
#include <TFile.h>
#include <iostream>

int main() {
  auto f = TFile("test.root", "recreate");

  auto t1 = TTree("t1", "t1");
  auto *id1 = new int(1);
  t1.Branch("id", id1, "id/I");
  t1.Fill();
  t1.Write();

  auto t2 = TTree("t2", "t2");
  auto *id2 = new int(2);
  t2.Branch("id", id2, "id/I");
  t2.Fill();
  t2.Write();

  *id1 = 3;
  t1.Fill();
  t1.Write();

  *id2 = 4;
  t2.Fill();
  t2.AddFriend(&t1);
  t2.Write();

  *id1 = -1;
  *id2 = -1;

  f.Close();

  auto f2 = TFile("test.root", "read");
  auto *t2r = f2.Get<TTree>("t2");
  t2r->SetBranchAddress("t1.id", id1);
  t2r->SetBranchAddress("id", id2);
  for (int i = 0; i < t2r->GetEntries(); ++i) {
    t2r->GetEntry(i);
    std::cout << *id1 << " " << *id2 << '\n';
  }

  std::cout << "Closing\n";
  f2.Close();
}

I couldn’t find a working Python translation (but I don’t know how the TTree pythonization works exactly).

Here is a C++ code that crash. The code from eguiraud doesn’t crash here, so I guess now it should be simple - just finding the difference.
test_friends.C (618 Bytes)

OK, I could make the code from @eguiraud crash. It is enough to load the t1 TTree for reading. Adding:
auto *t1r = f2.Get(“t1”);
around line 35.

I think it’s an order-of-deletion issue:

  • TFile::Close deletes t2
  • TFile::Close deletes t1
  • ~TTree for t1 tries to tell t2 that it needs to remove t1 from its friends, but t2 was already deleted

The simplest fix is simply to not read t1 from file, you don’t need it (see my example). Another workaround is to delete t1 manually before calling f1.Close().

You might be able to do something similar in Python.

Cheers,
Enrico

Indeed the manual deletion of t2 helps the C++ code. However, not reading of t1 would be a huge loss of python capabilities. When you call t2.GetEntry() its friends t1 branches are also loaded and in Python you can access them with t1.something (as you can see in my code). Without loading t1, one needs to set all the branches addresses manually and thus lose this function from python. Also, it is a big mess to read one tree with t2.something1, and other with variable something2 that was passed to t1.SetBranchAddress().

In python simply del t2 didn’t work. Maybe there is another way.

Adding t2.RemoveFriend(t1) before f.Close() should fix it.

t2.Delete() fixed the code in python. Could you confirm, that it is a proper way to do it? I am not sure if it is equivalent to delete t2 in C++.

And, I guess, it is still kind of a bug, both in C++ and PyROOT, although affecting mainly the PyROOT workflow.

That works too. Perhaps better than t2.Delete(). Thanks!

I’d suggest t2.RemoveFriend(t1) rather than t2.Delete() to avoid messing with C++ object lifetimes from Python.

Feel free to open a bug report on GitHub, I agree that ideally this should just work, but we really need Philippe’s opinion on that matter.

Cheers,
Enrico

Thanks. I’ve created a ticket:

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.