From 3c84463d3fc4a99c94207c1116ba33d7a412a95f Mon Sep 17 00:00:00 2001 From: Jean-Christophe Fillion-Robin Date: Thu, 8 Nov 2012 17:28:47 -0500 Subject: [PATCH] Add SystemExit exception handler If enabled, the signal "systemExitExceptionRaised" will be emitted. It gives application the opportunity to cleanup and terminate nicely. --- src/PythonQt.cpp | 114 ++++++++++++++++++++++++++++++++++++++++------- src/PythonQt.h | 15 +++++++ 2 files changed, 114 insertions(+), 15 deletions(-) diff --git a/src/PythonQt.cpp b/src/PythonQt.cpp index a01e7746..1ecdff75 100644 --- a/src/PythonQt.cpp +++ b/src/PythonQt.cpp @@ -1121,6 +1121,7 @@ PythonQtPrivate::PythonQtPrivate() _currentClassInfoForClassWrapperCreation = NULL; _profilingCB = NULL; _ErrorOccured = false; + _SystemExitExceptionHandlerEnabled = false; } void PythonQtPrivate::setupSharedLibrarySuffixes() @@ -1217,26 +1218,93 @@ void PythonQtPrivate::removeSignalEmitter(QObject* obj) _signalReceivers.remove(obj); } +namespace +{ +//! adapted from python source file "pythonrun.c", function "handle_system_exit" +//! return the exitcode instead of calling "Py_Exit". +//! it gives the application an opportunity to properly terminate. +int custom_system_exit_exception_handler() +{ + PyObject *exception, *value, *tb; + int exitcode = 0; + +// if (Py_InspectFlag) +// /* Don't exit if -i flag was given. This flag is set to 0 +// * when entering interactive mode for inspecting. */ +// return exitcode; + + PyErr_Fetch(&exception, &value, &tb); + if (Py_FlushLine()) + PyErr_Clear(); + fflush(stdout); + if (value == NULL || value == Py_None) + goto done; + if (PyExceptionInstance_Check(value)) { + /* The error code should be in the `code' attribute. */ + PyObject *code = PyObject_GetAttrString(value, "code"); + if (code) { + Py_DECREF(value); + value = code; + if (value == Py_None) + goto done; + } + /* If we failed to dig out the 'code' attribute, + just let the else clause below print the error. */ + } + if (PyInt_Check(value)) + exitcode = (int)PyInt_AsLong(value); + else { + PyObject *sys_stderr = PySys_GetObject(const_cast("stderr")); + if (sys_stderr != NULL && sys_stderr != Py_None) { + PyFile_WriteObject(value, sys_stderr, Py_PRINT_RAW); + } else { + PyObject_Print(value, stderr, Py_PRINT_RAW); + fflush(stderr); + } + PySys_WriteStderr("\n"); + exitcode = 1; + } + done: + /* Restore and clear the exception info, in order to properly decref + * the exception, value, and traceback. If we just exit instead, + * these leak, which confuses PYTHONDUMPREFS output, and may prevent + * some finalizers from running. + */ + PyErr_Restore(exception, value, tb); + PyErr_Clear(); + return exitcode; + //Py_Exit(exitcode); +} +} + bool PythonQt::handleError() { bool flag = false; if (PyErr_Occurred()) { - // currently we just print the error and the stderr handler parses the errors - PyErr_Print(); - - /* - // EXTRA: the format of the ptype and ptraceback is not really documented, so I use PyErr_Print() above - PyObject *ptype; - PyObject *pvalue; - PyObject *ptraceback; - PyErr_Fetch( &ptype, &pvalue, &ptraceback); - - Py_XDECREF(ptype); - Py_XDECREF(pvalue); - Py_XDECREF(ptraceback); - */ - PyErr_Clear(); + if (PythonQt::priv()->_SystemExitExceptionHandlerEnabled && + PyErr_ExceptionMatches(PyExc_SystemExit)) { + int exitcode = custom_system_exit_exception_handler(); + emit PythonQt::self()->systemExitExceptionRaised(exitcode); + } + else + { + // currently we just print the error and the stderr handler parses the errors + PyErr_Print(); + + /* + // EXTRA: the format of the ptype and ptraceback is not really documented, so I use PyErr_Print() above + PyObject *ptype; + PyObject *pvalue; + PyObject *ptraceback; + PyErr_Fetch( &ptype, &pvalue, &ptraceback); + + Py_XDECREF(ptype); + Py_XDECREF(pvalue); + Py_XDECREF(ptraceback); + */ + PyErr_Clear(); + } flag = true; } PythonQt::priv()->_ErrorOccured = flag; @@ -1256,6 +1324,22 @@ void PythonQt::resetErrorFlag() } } +void PythonQt::setSystemExitExceptionHandlerEnabled(bool value) +{ + if (PythonQt::self()) + { + PythonQt::priv()->_SystemExitExceptionHandlerEnabled = value; + } +} + +bool PythonQt::systemExitExceptionHandlerEnabled()const +{ + if (PythonQt::self()) + { + return PythonQt::priv()->_SystemExitExceptionHandlerEnabled; + } +} + void PythonQt::addSysPath(const QString& path) { PythonQtObjectPtr sys; diff --git a/src/PythonQt.h b/src/PythonQt.h index b5f6bc48..43a317f6 100644 --- a/src/PythonQt.h +++ b/src/PythonQt.h @@ -488,6 +488,15 @@ class PYTHONQT_EXPORT PythonQt : public QObject { //! \sa PythonQt::errorOccured() void resetErrorFlag(); + //! if set to True, signal will be emitted if exception SystemExit is caught + //! \sa PythonQt::handleError(), PythonQt:: + void setSystemExitExceptionHandlerEnabled(bool value); + + //! return \a True if SystemExit exception is handled by PythonQt + //! \sa setSystemExitExceptionHandlerEnabled() + bool systemExitExceptionHandlerEnabled()const; + + //! set a callback that is called when a QObject with parent == NULL is wrapped by pythonqt void setQObjectWrappedCallback(PythonQtQObjectWrappedCB* cb); //! set a callback that is called when a QObject with parent == NULL is no longer wrapped by pythonqt @@ -517,6 +526,11 @@ class PYTHONQT_EXPORT PythonQt : public QObject { //! emitted when help() is called on a PythonQt object and \c ExternalHelp is enabled void pythonHelpRequest(const QByteArray& cppClassName); + //! emitted when both custom SystemExit exception handler is enabled and a SystemExit + //! exception is raised. + //! \sa setSystemExitExceptionHandlerEnabled(bool) + void systemExitExceptionRaised(int exitCode); + private: void initPythonQtModule(bool redirectStdOut, const QByteArray& pythonQtModuleName); @@ -716,6 +730,7 @@ class PYTHONQT_EXPORT PythonQtPrivate : public QObject { int _PythonQtObjectPtr_metaId; bool _ErrorOccured; + bool _SystemExitExceptionHandlerEnabled; friend class PythonQt; };