Python doesn't exit after main function

I am having a weird problem. I have some code (bpaste.net/show/75094/ ) where the main function is supposed to exit after doing some print statements. The print statements execute just fine, but then python just sits there spinning in the CPU, making my laptop fan turn on. I tried putting in exception-raising statements, but it doesn’t raise them. I tried putting return, exit(), but it just hangs after the print statements.

Any ideas? Is it ROOT garbage-collection-related?

Jean-François

I put in an IPython.embed() before the function return and poked around. Apparently I needed to manually .Close() some ROOT.TFiles and some ROOT.TCanvases that I had put into python lists. After manually removing them (in the script), the function returns fine and python quits. Is this some problem in the ROOT memory management? I thought I didn’t have to Close/Delete any objects if I didn’t explicitly take ownership of them.

Jean-Françoi

Jean-François,

PyROOT runs an atexit() that closes all ROOT files, so I can see how this can make a difference. However, there’s nothing intensive (supposed to be) going on wrt. the garbage collector. At least, nothing that would not happen w/o a Close() call.

Do you know (e.g. by attaching gdb and looking at the call stack) whether the spinning happens purely in C++, or in Python, or in both?

Thanks,
Wim

I don’t have a python with debugging symbols, so I couldn’t follow the usual tutorials to get a stack trace. Is there another simple way to do this?

I usually just run my script with "python FOMplots.py ".

Jean-François

Jean-François,

should still be fine, even w/o debugging symbols, as the function calls (available as linker symbols) should still show up. Assuming this is Linux or Mac, one way is, when the process hangs, to use “ps -u | grep python” to find the process id (pid) of the process, then run "gdb -p " and on the gdb prompt, type “where”, which should give the trace of what it was doing at the point gdb attached.

If it does not look like it’s in a loop, type “cont”, then hit ^C at some later point which brings you back to the gdb prompt, and then try “where” again. That should allow for quickly finding where it is spinning.

Cheers,
Wim

Here is the output after following your procedure:

#0  0x000000010f9c6e3b in TClass::InheritsFrom ()
#1  0x000000010f941a41 in TObject::InheritsFrom ()
#2  0x0000000111c6a660 in THStack::RecursiveRemove ()
#3  0x000000010f9a3360 in TList::RecursiveRemove ()
#4  0x0000000114afe7f1 in TPad::RecursiveRemove ()
#5  0x000000010f9a3360 in TList::RecursiveRemove ()
#6  0x000000010f9a0eb9 in THashList::RecursiveRemove ()
#7  0x000000010f941259 in TObject::~TObject ()
#8  0x000000010f9a222f in TList::~TList ()
#9  0x0000000111c684bf in THStack::~THStack ()
#10 0x0000000111c6841f in THStack::~THStack ()
#11 0x000000010f9c2286 in TClass::Destructor ()
#12 0x000000010f356444 in PyROOT::(anonymous namespace)::op_dealloc ()
#13 0x000000010c875b85 in subtype_dealloc ()
#14 0x000000010c83bf3b in frame_dealloc ()
#15 0x000000010c8a80b1 in PyEval_EvalCodeEx ()
#16 0x000000010c8af68d in fast_function ()
#17 0x000000010c8aa838 in PyEval_EvalFrameEx ()
#18 0x000000010c8a8096 in PyEval_EvalCodeEx ()
#19 0x000000010c8a78c6 in PyEval_EvalCode ()
#20 0x000000010c8ceaee in PyRun_FileExFlags ()
#21 0x000000010c8ce659 in PyRun_SimpleFileExFlags ()
#22 0x000000010c8e2378 in Py_Main ()
#23 0x000000010c80af24 in _mh_execute_header ()

I then continued and tried again and got this similar trace:

#0  0x000000010f9c3ba6 in TClass::GetBaseClass ()
#1  0x000000010f9c6f5f in TClass::InheritsFrom ()
#2  0x000000010f941a41 in TObject::InheritsFrom ()
#3  0x0000000111c6a660 in THStack::RecursiveRemove ()
#4  0x000000010f9a3360 in TList::RecursiveRemove ()
#5  0x0000000114afe7f1 in TPad::RecursiveRemove ()
#6  0x000000010f9a3360 in TList::RecursiveRemove ()
#7  0x000000010f9a0eb9 in THashList::RecursiveRemove ()
#8  0x000000010f941259 in TObject::~TObject ()
#9  0x000000010f9a222f in TList::~TList ()
#10 0x0000000111c684bf in THStack::~THStack ()
#11 0x0000000111c6841f in THStack::~THStack ()
#12 0x000000010f9c2286 in TClass::Destructor ()
#13 0x000000010f356444 in PyROOT::(anonymous namespace)::op_dealloc ()
#14 0x000000010c875b85 in subtype_dealloc ()
#15 0x000000010c83bf3b in frame_dealloc ()
#16 0x000000010c8a80b1 in PyEval_EvalCodeEx ()
#17 0x000000010c8af68d in fast_function ()
#18 0x000000010c8aa838 in PyEval_EvalFrameEx ()
#19 0x000000010c8a8096 in PyEval_EvalCodeEx ()
#20 0x000000010c8a78c6 in PyEval_EvalCode ()
#21 0x000000010c8ceaee in PyRun_FileExFlags ()
#22 0x000000010c8ce659 in PyRun_SimpleFileExFlags ()
#23 0x000000010c8e2378 in Py_Main ()
#24 0x000000010c80af24 in _mh_execute_header ()

It’s in the recursive remove function calls, I guess? Could I have a recursion loop?

Jean-François

Jean-François,

every TObject derived class can notify registered handlers that it’s going dodo-bird (see the TObject dtor). The RecursiveRemove() function of all these registered handlers is then called. This communication prevents dangling pointers, as container-like objects get a chance to nullify their held pointers.

If there are many objects that do this in their dtors and many objects that listen (the open TFile’s and TCanvas’s that you mentioned all register a handler), then this can be rather slow (basically NxM, with N the number of objects, and M the number of handlers), but it should not hang forever.

I don’t think it’s recursing ad infinitum, as the trace would not end up back in the python interpreter.

Cheers,
Wim