Segfault on creating canvases and pads in a loop with pyroot

Dear Experts,

I am trying to make a few ratio plots for histograms stored in several root files. I use a loop to open the files for updating and draw the canvases sequentially. I am unfortunately getting a segfault. Here is a very minimal example. What am I missing?

test.root can be any root file.

import ROOT
import numpy as np

def createCanvasPads():
    c = ROOT.TCanvas("c_"+str(np.random.rand()), "canvas", 800, 800)
    c.cd()
    pad1 = ROOT.TPad("pad1_"+str(np.random.rand()), "pad1", 0, 0.3, 1, 1.0)
    pad1.Draw()
    c.cd()
    pad2 = ROOT.TPad("pad2_"+str(np.random.rand()), "pad2", 0, 0.05, 1, 0.3)
    pad2.Draw()

    return c, pad1, pad2

for i in range(10):

    print(i)

    f_in = ROOT.TFile.Open( "test.root", "UPDATE" )
    c, pad1, pad2 = createCanvasPads()
    pad1.cd()
    pad2.cd()
    
    f_in.Close()

The result is

0
1
2

 *** Break *** segmentation violation

===========================================================
There was a crash.
This is the entire stack trace of all threads:
===========================================================
#0  0x00007f2687dfc549 in waitpid () from /lib64/libc.so.6
#1  0x00007f2687d79f62 in do_system () from /lib64/libc.so.6
...

Maybe @etejedor can take a look

Hi,

This can be due to the fact that c, pad1, pad2 are rebound at every iteration (and therefore the previous canvas/pads are garbage collected by Python).

Can you try storing your canvas/pads in a list at every iteration?

This seems to have fixed this example, thank you. I will try to use this method to fix some other similar problems with the full script. @etejedor why is it a problem if my canvases get garbage collected at the end of each iteration? There are not used anywhere else. Does root keep track of them and attempts to delete them in a different iteration?

It’s not completely clear to me either, could you share the rest of the stack trace?

Perhaps @couet has an idea (e.g. internal global state that is kept in ROOT)

Sure, I never knew these lines were actually helpful to anyone :smile:

0
1
2

 *** Break *** segmentation violation
===========================================================
There was a crash.
This is the entire stack trace of all threads:
===========================================================
#0  0x00007f10a6fa2549 in waitpid () from /lib64/libc.so.6
#1  0x00007f10a6f1ff62 in do_system () from /lib64/libc.so.6
#2  0x00007f10a6f20311 in system () from /lib64/libc.so.6
#3  0x00007f109e91f0c4 in TUnixSystem::StackTrace() () from /cern_root/lib/libCore.so.6.18
#4  0x00007f109e9217fc in TUnixSystem::DispatchSignals(ESignals) () from /cern_root/lib/libCore.so.6.18
#5  <signal handler called>
#6  0x00007f10a72a47b8 in main_arena () from /lib64/libc.so.6
#7  0x00007f109e8c87d1 in TClass::Destructor(void*, bool) () from /cern_root/lib/libCore.so.6.18
#8  0x00007f10a04317ef in PyROOT::op_dealloc_nofree(PyROOT::ObjectProxy*) () from /cern_root/lib/libPyROOT.so
#9  0x00007f10a0431949 in PyROOT::(anonymous namespace)::op_dealloc(PyROOT::ObjectProxy*) () from /cern_root/lib/libPyROOT.so
#10 0x00007f10a7c6e1b3 in subtype_dealloc () from /lib64/libpython2.7.so.1.0
#11 0x00007f10a7ce50db in collect () from /lib64/libpython2.7.so.1.0
#12 0x00007f10a7ce5887 in _PyObject_GC_Malloc () from /lib64/libpython2.7.so.1.0
#13 0x00007f10a7ce58cd in _PyObject_GC_New () from /lib64/libpython2.7.so.1.0
#14 0x00007f10a7c7a386 in new_weakref () from /lib64/libpython2.7.so.1.0
#15 0x00007f10a7c7d381 in PyWeakref_NewRef () from /lib64/libpython2.7.so.1.0
#16 0x00007f10a044c501 in PyROOT::TMemoryRegulator::RegisterObject(PyROOT::ObjectProxy*, TObject*) () from /cern_root/lib/libPyROOT.so
#17 0x00007f10a0449bf8 in PyROOT::TConstructorHolder::Call(PyROOT::ObjectProxy*&, _object*, _object*, PyROOT::TCallContext*) () from /cern_root/lib/libPyROOT.so
#18 0x00007f10a042d2a3 in PyROOT::(anonymous namespace)::mp_call(PyROOT::MethodProxy*, _object*, _object*) () from /cern_root/lib/libPyROOT.so
#19 0x00007f10a7c1c073 in PyObject_Call () from /lib64/libpython2.7.so.1.0
#20 0x00007f10a7c73097 in slot_tp_init () from /lib64/libpython2.7.so.1.0
#21 0x00007f10a7c71daf in type_call () from /lib64/libpython2.7.so.1.0
#22 0x00007f10a7c1c073 in PyObject_Call () from /lib64/libpython2.7.so.1.0
#23 0x00007f10a7cb0846 in PyEval_EvalFrameEx () from /lib64/libpython2.7.so.1.0
#24 0x00007f10a7cb4ccd in PyEval_EvalFrameEx () from /lib64/libpython2.7.so.1.0
#25 0x00007f10a7cb764d in PyEval_EvalCodeEx () from /lib64/libpython2.7.so.1.0
#26 0x00007f10a7cb7752 in PyEval_EvalCode () from /lib64/libpython2.7.so.1.0
#27 0x00007f10a7cd0b8f in run_mod () from /lib64/libpython2.7.so.1.0
#28 0x00007f10a7cd1d5e in PyRun_FileExFlags () from /lib64/libpython2.7.so.1.0
#29 0x00007f10a7cd2fe9 in PyRun_SimpleFileExFlags () from /lib64/libpython2.7.so.1.0
#30 0x00007f10a7ce419f in Py_Main () from /lib64/libpython2.7.so.1.0
#31 0x00007f10a6eff555 in __libc_start_main () from /lib64/libc.so.6
#32 0x000000000040068e in _start ()
===========================================================


The lines below might hint at the cause of the crash.
You may get help by asking at the ROOT forum http://root.cern.ch/forum
Only if you are really convinced it is a bug in ROOT then please submit a
report at http://root.cern.ch/bugs Please post the ENTIRE stack trace
from above as an attachment in addition to anything else
that might help us fixing this issue.
===========================================================
#6  0x00007f10a72a47b8 in main_arena () from /lib64/libc.so.6
#7  0x00007f109e8c87d1 in TClass::Destructor(void*, bool) () from /cern_root/lib/libCore.so.6.18
#8  0x00007f10a04317ef in PyROOT::op_dealloc_nofree(PyROOT::ObjectProxy*) () from /cern_root/lib/libPyROOT.so
#9  0x00007f10a0431949 in PyROOT::(anonymous namespace)::op_dealloc(PyROOT::ObjectProxy*) () from /cern_root/lib/libPyROOT.so
#10 0x00007f10a7c6e1b3 in subtype_dealloc () from /lib64/libpython2.7.so.1.0
#11 0x00007f10a7ce50db in collect () from /lib64/libpython2.7.so.1.0
#12 0x00007f10a7ce5887 in _PyObject_GC_Malloc () from /lib64/libpython2.7.so.1.0
#13 0x00007f10a7ce58cd in _PyObject_GC_New () from /lib64/libpython2.7.so.1.0
#14 0x00007f10a7c7a386 in new_weakref () from /lib64/libpython2.7.so.1.0
#15 0x00007f10a7c7d381 in PyWeakref_NewRef () from /lib64/libpython2.7.so.1.0
#16 0x00007f10a044c501 in PyROOT::TMemoryRegulator::RegisterObject(PyROOT::ObjectProxy*, TObject*) () from /cern_root/lib/libPyROOT.so
#17 0x00007f10a0449bf8 in PyROOT::TConstructorHolder::Call(PyROOT::ObjectProxy*&, _object*, _object*, PyROOT::TCallContext*) () from /cern_root/lib/libPyROOT.so
#18 0x00007f10a042d2a3 in PyROOT::(anonymous namespace)::mp_call(PyROOT::MethodProxy*, _object*, _object*) () from /cern_root/lib/libPyROOT.so
#19 0x00007f10a7c1c073 in PyObject_Call () from /lib64/libpython2.7.so.1.0
#20 0x00007f10a7c73097 in slot_tp_init () from /lib64/libpython2.7.so.1.0
#21 0x00007f10a7c71daf in type_call () from /lib64/libpython2.7.so.1.0
#22 0x00007f10a7c1c073 in PyObject_Call () from /lib64/libpython2.7.so.1.0
#23 0x00007f10a7cb0846 in PyEval_EvalFrameEx () from /lib64/libpython2.7.so.1.0
#24 0x00007f10a7cb4ccd in PyEval_EvalFrameEx () from /lib64/libpython2.7.so.1.0
#25 0x00007f10a7cb764d in PyEval_EvalCodeEx () from /lib64/libpython2.7.so.1.0
#26 0x00007f10a7cb7752 in PyEval_EvalCode () from /lib64/libpython2.7.so.1.0
#27 0x00007f10a7cd0b8f in run_mod () from /lib64/libpython2.7.so.1.0
#28 0x00007f10a7cd1d5e in PyRun_FileExFlags () from /lib64/libpython2.7.so.1.0
#29 0x00007f10a7cd2fe9 in PyRun_SimpleFileExFlags () from /lib64/libpython2.7.so.1.0
#30 0x00007f10a7ce419f in Py_Main () from /lib64/libpython2.7.so.1.0
#31 0x00007f10a6eff555 in __libc_start_main () from /lib64/libc.so.6
#32 0x000000000040068e in _start ()
===========================================================

It’s indeed linked to the destruction of those objects. From the stack trace, I see you are using the old PyROOT (ROOT < 6.22). I just tried your reproducer with the current PyROOT (in particular with current master) and I don’t see the crash. You have a workaround, but it might be worth at some point to try the new PyROOT.

How can I print the version of PyROOT? Is it the same as the ROOT version?

Upgrading pretty much solved everything btw. Thanks for the idea!

Yes, same as ROOT’s!

Glad to hear it works better with the new PyROOT!

I had similar problems while working with canvases/pads inside loops. I have a native Python trick that works if you’re stuck in an older version of pyROOT like I am (for other reasons) that can be put into your code that shouldn’t affect anything else:

import gc

# Collect previous objects and disable circular garbage collector (reference counter still works)
gc.collect()
gc.disable()

for i in range(10):
   doSomethingWithCanvasPads()

# Re-enable circular garbage collector after the loop
gc.enable()

The uncollected canvases, pads, and whatever else you made in the loop are then ignored by the garbage collector until the end of the program, at which point they are then handled properly and you don’t get a seg fault.

Hope this helps anyone else in the future.

1 Like