Object ownership/memory leak with ttree

I am trying to fill a TTree with a user-defined class in pyROOT, but ROOT does not seem to be calling the destructor on the class in order to free-up memory. Running this code, the program will continue to allocate memory in RAM until it crashes or finishes.

I have condensed my code in order to reproduce the problem into the following files:

ScopeEvent.C

#include <TGraph.h>

class ScopeEvent
{
public:
  ScopeEvent() : fID(-1), fC1(0), fC2(0) { };
  ScopeEvent(int id, TGraph *c1, TGraph *c2) : fID(id), fC1(c1), fC2(c2) { };
  virtual ~ScopeEvent() { delete fC1; delete fC2; };

  int GetID() const { return fID; };
  TGraph *GetC1() const { return fC1; };
  TGraph *GetC2() const { return fC2; };

  void SetID(int id) { fID = id; };
  void SetC1(TGraph *c1) { fC1 = c1; };
  void SetC2(TGraph *c2) { fC2 = c2; };

  void Draw() { fC1->Draw("AL"); fC2->Draw("L SAME"); };

  ClassDef(ScopeEvent, 1)
protected:
  int fID;
  TGraph *fC1, *fC2;
};
ClassImp(ScopeEvent)

test.py

import numpy
import ROOT

ROOT.gROOT.ProcessLine(".L ScopeEvent.C+")

f = ROOT.TFile("test.root", 'recreate')
T = ROOT.TTree('T', 'Scope Events')
event = ROOT.ScopeEvent()
T.Branch('event', 'ScopeEvent', event)

for i in range(10000):
    x1 = numpy.arange(10000)
    y1 = numpy.arange(10000)
    x2 = numpy.arange(10000)
    y2 = numpy.arange(10000)

    c1 = ROOT.TGraph(len(x1), x1, y1)
    c2 = ROOT.TGraph(len(x2), x2, y2)

    event.SetID(0)
    event.SetC1(c1)
    event.SetC2(c2)
    T.Fill()

f.cd()
T.Write()
f.Close()

I tried everything suggested at http://cern.ch/wlav/pyroot/memory.html, without any luck.

The only solution I found was to add a Clear() function to the ScopeEvent class which deletes the memory from within the object itself:

void Clear() { delete fC1; delete fC2;}

and call this function after every Tree::Fill() command, but unfortunately, all of our analysis software requires that the ScopeEvent class remain unchanged.

I am using ROOT v5.26

Any ideas?

Hi,

what happens is that the moment that SetC1() and SetC2() are called, the ownership is transferred from python to C++. This is eminently reasonable, b/c as is clear from the ScopeEvent dtor, the code takes ownership of the two graphs. Iow., in order to be consistent with the dtor, SetC1() and SetC2() should delete the old data member. Since they don’t, there’s a bug there. (That is, the C++ equivalent code would leak, too.)

To work around, either take back ownership after SetC1()/SetC2(), or set their _mempolicy data member to ROOT.kMemoryStrict once, and make sure that the data members of the ScopeEvent is reset to 0 before the dtor is run.

HTH,
Wim

Thanks a lot!

For some odd reason, I had the impression that the Tree::Fill() command would implicitly call the destructor on the object.