_ROOT Version:_6.16/00 Platform: macosx64 Compiler: Not Provided
Hi,
I am trying to create multiple TPad on on TCanvas and encountered some problems. If I am doing this multiple times I have to delete the pads manually or I get a crash. This is a minimal reproducer:
from ROOT import TCanvas,TPad, TH1D
from ROOT import gROOT
gROOT.SetBatch(True)
th1 = TH1D()
for i in range(1000):
print(i)
c = TCanvas(str(i), str(i), 800, 600)
p1 = TPad(str(i)+"p1", str(i)+"p1",
0.,0.,0.5,0.5)
p1.Draw()
c.cd()
p2 = TPad(str(i)+"p2", str(i)+"p2",
0.5,0.5,1.,1.)
p2.Draw()
p1.cd()
th1.Draw()
p2.cd()
th1.Draw()
# p1.Delete()
# p2.Delete()
The crash does not occur if I call p1.Delete() or p2.Delete() or if I do not call p1.cd() or p2.cd().
This is the stacktrace I get:
*** Break *** segmentation violation
[/usr/lib/system/libsystem_platform.dylib] _sigtramp (no debug info)
[/Users/Christian/work/root/root_v6_16/lib/libPyROOT.so] PyROOT::(anonymous namespace)::op_dealloc(PyROOT::ObjectProxy*) (no debug info)
[/Users/Christian/work/root/root_v6_16/lib/libPyROOT.so] PyROOT::(anonymous namespace)::op_dealloc(PyROOT::ObjectProxy*) (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] _PyObject_GC_Malloc (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] _PyObject_GC_New (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyWeakref_NewRef (no debug info)
[/Users/Christian/work/root/root_v6_16/lib/libPyROOT.so] PyROOT::TMemoryRegulator::RegisterObject(PyROOT::ObjectProxy*, TObject*) (no debug info)
[/Users/Christian/work/root/root_v6_16/lib/libPyROOT.so] PyROOT::TConstructorHolder::Call(PyROOT::ObjectProxy*&, _object*, _object*, PyROOT::TCallContext*) (no debug info)
[/Users/Christian/work/root/root_v6_16/lib/libPyROOT.so] PyROOT::(anonymous namespace)::mp_call(PyROOT::MethodProxy*, _object*, _object*) (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyObject_Call (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyObject_Call (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyEval_EvalFrameEx (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyEval_EvalCodeEx (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyEval_EvalCode (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyRun_FileExFlags (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] PyRun_SimpleFileExFlags (no debug info)
[/System/Library/Frameworks/Python.framework/Versions/2.7/Python] Py_Main (no debug info)
[/usr/lib/system/libdyld.dylib] start (no debug info)
[<unknown binary>] (no debug info)
This looks like a bug, and it is due to a double delete: a canvas is deleting a pad on the C++ side, and Python is attempting to delete the same pad afterwards.
This is a simpler reproducer:
from ROOT import TCanvas,TPad,gROOT
gROOT.SetBatch(True)
for i in range(1000):
print(i)
c = TCanvas(str(i), str(i), 800, 600)
p1 = TPad(str(i)+"p1", str(i)+"p1",0.,0.,0.5,0.5)
p1.Draw()
c.cd()
p1.cd()
I found that both the p1.Draw and the cd to c and p1 are necessary to reproduce. @couet any clue of what relationship between the canvas and the pad in C++ is triggering this?
For completeness, the stack that I get is:
#10 0x00007f0a09425bf9 in ROOT::delete_TPad (p=0x39008b0) at /home/etejedor/root/fork/build_oldpyroot_py2/graf2d/gpad/G__Gpad.cxx:1314
#11 0x00007f0a19903387 in TClass::Destructor (this=0x3754a40, obj=0x39008b0, dtorOnly=false) at /home/etejedor/root/fork/root/core/meta/src/TClass.cxx:5208
#12 0x00007f0a1bad3b00 in Cppyy::Destruct (type=22, instance=0x39008b0) at /home/etejedor/root/fork/root/bindings/pyroot/src/Cppyy.cxx:274
#13 0x00007f0a1baeff82 in PyROOT::op_dealloc_nofree (pyobj=0x7f0a224d79b0) at /home/etejedor/root/fork/root/bindings/pyroot/src/ObjectProxy.cxx:59
#14 0x00007f0a1baf06e9 in PyROOT::(anonymous namespace)::op_dealloc (pyobj=0x7f0a224d79b0) at /home/etejedor/root/fork/root/bindings/pyroot/src/ObjectProxy.cxx:212
#15 0x00007f0a23499438 in subtype_dealloc (self=0x7f0a224d79b0) at /mnt/build/jenkins/workspace/lcg_rootext_build/BUILDTYPE/Release/COMPILER/gcc7/LABEL/centos7/build/externals/Python-2.7.13/src/Python/2.7.13/Objects/typeobject.c:1050
#16 0x00007f0a235291ab in delete_garbage (old=0x7f0a237cb4d0 <generations+48>, collectable=0x7fff44251c80) at /mnt/build/jenkins/workspace/lcg_rootext_build/BUILDTYPE/Release/COMPILER/gcc7/LABEL/centos7/build/externals/Python-2.7.13/src/Python/2.7.13/Modules/gcmodule.c:821
Apparently not, it seems that there is some corner case that is not covered. It is really the relationship between the canvas and the pad in C++. If I put all the canvases in a Python list, so that they are never removed by Python, the error of the double delete does not show (the pads are cleaned by Python safely).
@couet this is a reproducer in C++ of the same issue:
gROOT->SetBatch(true);
auto c = new TCanvas("0", "0", 800, 600);
auto p1 = new TPad("0p1", "0p1",0.,0.,0.5,0.5);
p1->Draw();
c->cd();
p1->cd();
delete c;
delete p1;
It triggers the same behaviour as in Python: the canvas is deleted before the pad, when the pad gets deleted the program segfaults. It is just that in Python this can eventually happen, depending on the order that the garbage collector removes objects, here in C++ I am forcing the order that triggers the issue.
If there is no Draw of the pad, the error does not happen. If deleting first the pad and then the canvas, the error does not happen either. So it seems it has to do with the relationship between these two.
I can confirm this has been fixed in the experimental PyROOT by this commit:
@cwiel we are now working on a new PyROOT (there is a prototype available that you can use if you build ROOT with -Dpyroot_experimental=ON). We target ROOT 6.20 to make it the default. Can you live for now with the workaround of using Delete?