Issue 10582: PyErr_PrintEx exits silently when passed SystemExit exception (original) (raw)
I discovered this bug working with panda3d. I've attached a short python script which demonstrates the problem. After installing panda3d, run the script, and then hit q in the window which appears. You'll see that none of the cleanup code after the 'run()' main loop runs.
The underlying cause is in how panda3d uses the C API. It's got code like this (in src/pipeline/thread.cxx):
// Call the user's function.
result = PyObject_Call(function, args, NULL);
if (result == (PyObject *)NULL && PyErr_Occurred()) {
// We got an exception. Move the exception from the current
// thread into the main thread, so it can be handled there.
PyObject *exc, *val, *tb;
PyErr_Fetch(&exc, &val, &tb);
thread_cat.error()
<< "Exception occurred within " << *this << "\n";
// Temporarily restore the exception state so we can print a
// callback on-the-spot.
Py_XINCREF(exc);
Py_XINCREF(val);
Py_XINCREF(tb);
PyErr_Restore(exc, val, tb);
PyErr_Print();
...
If the code being called (generally a panda3d task function or event handler) calls sys.exit, the PyErr_Print() never returns. This is surprising, as the the documentation never mentions that a function with an innocuous name like "Print" might never return.
However, I don't think this is a panda3d bug; I think that the function as documented is the way it should behave. Otherwise, the vast majority of calls to this method will need to look like
if (!PyErr_ExceptionMatches(PyExc_SystemExit)) {
PyErr_PrintEx(1);
}
which seems like a poor abstraction.
Another unexpected side effect is when python is used with the -i flag. Because of the way this alters the normal exit behavior, the code runs the way I'd expect, and the cleanup code in the test application does run. It seems rather strange to me that cleanup code should run or not run based on the -i flag.
I believe the fix for all this is that PyErr_PrintEx be changed to behave as documented, not call handle_system_exit() when passed SystemExit, and instead in the places where the interpreter main loop is run, followed by PyErr_PrintEx(), that SystemExit is special cased in those specific locations as needed.