TClonesArray caused mysterious crash in TTree I/O

Dear ROOT devs,

I’ve found an odd problem regarding TClonesArray and ROOT I/O.

So I have a class containing pointer members of TClonesArray which looks like:

class ClassA: public TObject {

public:
    ClassA() {
        m_array = new TClonesArray("ClassB", 10000); 
    }
   ~ClassA() {
        delete m_array;
    }
    ...

  ClassDef(ClassA,1)

private:
    TClonesArray* m_array;
    ...
};

If TClonesArray is intialized in the constructor, when saving ClassA objects to TTree, the process would sometimes crash.

  tree = new TTree(name, title);
  tree->Branch(treename, "ClassA", &addr, 32000, 99);

The crash happens quite randomly, usually after a large number of processed events. This is the screenshot of the crash:

I also found that if I didn’t initialize the TClonesArray in the constructer, the crashes went away. But then the TClonesArray member becomes unbrowse-able in the TBrowser. As shown below, only the TClonesArray member initialized in the constructor is a sub-branch, and if you double-click other members, ROOT would complain about unimplemented method (they are initialized somewhere else).

So I’m just wondering what I might have done wrong, and if there are ways to walk around.

Many thanks,
Teng


Please read tips for efficient and successful posting and posting code

ROOT Version: 6.22
Platform: SLC6
Compiler: gcc


@pcanal can perhaps give a hand? Thanks!

This is unexpected behavior. I can not see what the portion of code that you shown so far what could be the problem.

Could you either provide a complete runnning (and failing) example or run the failing example under valgrind (valgrind --suppresions=$ROOTSYS/etc/valgrind-root.supp myexecutable myargs) ?

@pcanal Thanks for your reply.

The origin code I have is too big to show. But this is the piece of code I just tested (on my MacBook with ROOT 6.22/06)

#include "TObject.h"
#include "TClonesArray.h"
#include "TFile.h"
#include "TTree.h"

class ClassB: public TObject {

  public:
    ClassB() {
      m_mem1 = 0.0;
      m_mem2 = 1.0;
      m_mem3 = 2.0;
      m_mem4 = 3.0;
      m_mem5 = 4.0;
    }

  double m_mem1;
  double m_mem2;
  double m_mem3;
  double m_mem4;
  double m_mem5;
  
  ClassDef(ClassB,1)
};

class ClassA: public TObject {

public:
    ClassA() {
        m_array = new TClonesArray("ClassB", 1000000); 
    }
   ~ClassA() {
        delete m_array;
    }

  ClassDef(ClassA,1)

private:
    TClonesArray* m_array;
};


void fun() {
ClassA* addr = 0;

TFile* tfile = new TFile("test.root","recreate");
TTree* tree = new TTree("test Tree","test Tree");
tree->Branch("test Branch", "ClassA", &addr, 32000, 99);

for (int i = 0; i < 1000000; ++i) {

  addr = new ClassA();
  tree->Fill();
  delete addr;
  addr = 0;

}
tree->Write();
tfile->Close();
}

This is the output of executing it in the root session:

root [0] .L TClonesArray.C 
root [1] fun()

 *** Break *** bus error
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] TUnixSystem::DispatchSignals(ESignals) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] SigHandler(ESignals) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] sighandler(int) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] textinput::TerminalConfigUnix::HandleSignal(int) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] (anonymous namespace)::TerminalConfigUnix__handleSignal(int) (no debug info)
[/usr/lib/system/libsystem_platform.dylib] _sigtramp (no debug info)
[<unknown binary>] (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libTree.so] TBranch::FillImpl(ROOT::Internal::TBranchIMTHelper*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libTree.so] TBranchElement::FillImpl(ROOT::Internal::TBranchIMTHelper*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libTree.so] TBranchElement::FillImpl(ROOT::Internal::TBranchIMTHelper*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libTree.so] TBranchElement::FillImpl(ROOT::Internal::TBranchIMTHelper*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libTree.so] TTree::Fill() (no debug info)
[<unknown binary>] (no debug info)
[<unknown binary>] (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCling.so] cling::IncrementalExecutor::executeWrapper(llvm::StringRef, cling::Value*) const (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCling.so] cling::Interpreter::RunFunction(clang::FunctionDecl const*, cling::Value*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCling.so] cling::Interpreter::EvaluateInternal(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cling::CompilationOptions, cling::Value*, cling::Transaction**, unsigned long) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCling.so] cling::Interpreter::process(std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > const&, cling::Value*, cling::Transaction**, bool) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCling.so] cling::MetaProcessor::process(llvm::StringRef, cling::Interpreter::CompilationResult&, cling::Value*, bool) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCling.so] HandleInterpreterException(cling::MetaProcessor*, char const*, cling::Interpreter::CompilationResult&, cling::Value*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCling.so] TCling::ProcessLine(char const*, TInterpreter::EErrorCode*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] TApplication::ProcessLine(char const*, bool, int*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libRint.so] TRint::ProcessLineNr(char const*, char const*, int*) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libRint.so] TRint::HandleTermInput() (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libRint.so] TTermInputHandler::Notify() (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libRint.so] TTermInputHandler::ReadNotify() (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] TUnixSystem::CheckDescriptors() (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] TMacOSXSystem::DispatchOneEvent(bool) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] TSystem::InnerLoop() (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] TSystem::Run() (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libCore.so] TApplication::Run(bool) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/lib/root/libRint.so] TRint::Run(bool) (no debug info)
[/usr/local/Cellar/root/6.22.06_1/bin/root.exe] main (no debug info)
[/usr/lib/system/libdyld.dylib] start (no debug info)
Root > .q

And by the way, if the problem is hard to solve, is there a way to browse TClonesArray in TBrowser without having to initialize them in the default constructor (which causes memory leak according to root documentation)?

Thanks,
Teng

Do you really plan on having 1 millions element in the TClonesArray?

TL;DR use the loop

for (int i = 0; i < 1000000; ++i) {
  addr->m_array->Clear(); // You need add an interface to allow that directly or indirectly ;)
  tree->Fill();
}

So here is what is happening:

(a) the branch address is set to the pointer addr
(b) at each iteration the object pointed to by addr is deleted and recreated
© Since not much is happening between the deletion and recreation the object is create in the same place
(d) Same for the nested TClonesArray
(e) At some point (for me it is iteration 39900), even though the object is reallocated in the same place, the nested TClonesArray is reallocated in a different place
(f) TBranchElement is setup to automatically detecting when the object is deleted and reallocated … by monitoring just the top level object address.
(g) So at the broken iteration, TBranchElement does NOT detect that the object changed and thus that is should refresh the address of the clonesArray
(h) it fails by using the old (deleted) TClonesArray object.

There is at least 2 ways to solve the problem.

(1) Tell the branch that the object changed:

  addr = new ClassA();
  tree->SetBranchAddress("test Branch", &addr);
  tree->Fill();
  delete addr;
  addr = 0;

(2) Save a lot of runtime and re-use the object rather than reallocate it:

for (int i = 0; i < 1000000; ++i) {
  addr->m_array->Clear(); // You need add an interface to allow that directly or indirectly ;)
  tree->Fill();
}

@pcanal
Thanks for your solutions. I’ll have a try! Btw, having 1m elements in the TClonesArray is just to cause the crash quickly.

Also, will initializing TClonesArray in the default constructor cause memory leak during input?

Thanks,
Teng

Right now, I don’t quite remember the circumstances when it happened. I think it is likely to be fine.

Also if you are going to allocate it in the constructor, you can use:

private:
    TClonesArray* m_array;  //->    This comments is a "promise" to the I/O that m_array will never be nullptr.

in which case the I/O will also reuse the array rather than reallocate it at every event read.

Cheers,
Philippe.

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