Skip to content

Commit 3c84463

Browse files
committed
Add SystemExit exception handler
If enabled, the signal "systemExitExceptionRaised" will be emitted. It gives application the opportunity to cleanup and terminate nicely.
1 parent a386dc6 commit 3c84463

File tree

2 files changed

+114
-15
lines changed

2 files changed

+114
-15
lines changed

src/PythonQt.cpp

+99-15
Original file line numberDiff line numberDiff line change
@@ -1121,6 +1121,7 @@ PythonQtPrivate::PythonQtPrivate()
11211121
_currentClassInfoForClassWrapperCreation = NULL;
11221122
_profilingCB = NULL;
11231123
_ErrorOccured = false;
1124+
_SystemExitExceptionHandlerEnabled = false;
11241125
}
11251126

11261127
void PythonQtPrivate::setupSharedLibrarySuffixes()
@@ -1217,26 +1218,93 @@ void PythonQtPrivate::removeSignalEmitter(QObject* obj)
12171218
_signalReceivers.remove(obj);
12181219
}
12191220

1221+
namespace
1222+
{
1223+
//! adapted from python source file "pythonrun.c", function "handle_system_exit"
1224+
//! return the exitcode instead of calling "Py_Exit".
1225+
//! it gives the application an opportunity to properly terminate.
1226+
int custom_system_exit_exception_handler()
1227+
{
1228+
PyObject *exception, *value, *tb;
1229+
int exitcode = 0;
1230+
1231+
// if (Py_InspectFlag)
1232+
// /* Don't exit if -i flag was given. This flag is set to 0
1233+
// * when entering interactive mode for inspecting. */
1234+
// return exitcode;
1235+
1236+
PyErr_Fetch(&exception, &value, &tb);
1237+
if (Py_FlushLine())
1238+
PyErr_Clear();
1239+
fflush(stdout);
1240+
if (value == NULL || value == Py_None)
1241+
goto done;
1242+
if (PyExceptionInstance_Check(value)) {
1243+
/* The error code should be in the `code' attribute. */
1244+
PyObject *code = PyObject_GetAttrString(value, "code");
1245+
if (code) {
1246+
Py_DECREF(value);
1247+
value = code;
1248+
if (value == Py_None)
1249+
goto done;
1250+
}
1251+
/* If we failed to dig out the 'code' attribute,
1252+
just let the else clause below print the error. */
1253+
}
1254+
if (PyInt_Check(value))
1255+
exitcode = (int)PyInt_AsLong(value);
1256+
else {
1257+
PyObject *sys_stderr = PySys_GetObject(const_cast<char*>("stderr"));
1258+
if (sys_stderr != NULL && sys_stderr != Py_None) {
1259+
PyFile_WriteObject(value, sys_stderr, Py_PRINT_RAW);
1260+
} else {
1261+
PyObject_Print(value, stderr, Py_PRINT_RAW);
1262+
fflush(stderr);
1263+
}
1264+
PySys_WriteStderr("\n");
1265+
exitcode = 1;
1266+
}
1267+
done:
1268+
/* Restore and clear the exception info, in order to properly decref
1269+
* the exception, value, and traceback. If we just exit instead,
1270+
* these leak, which confuses PYTHONDUMPREFS output, and may prevent
1271+
* some finalizers from running.
1272+
*/
1273+
PyErr_Restore(exception, value, tb);
1274+
PyErr_Clear();
1275+
return exitcode;
1276+
//Py_Exit(exitcode);
1277+
}
1278+
}
1279+
12201280
bool PythonQt::handleError()
12211281
{
12221282
bool flag = false;
12231283
if (PyErr_Occurred()) {
12241284

1225-
// currently we just print the error and the stderr handler parses the errors
1226-
PyErr_Print();
1227-
1228-
/*
1229-
// EXTRA: the format of the ptype and ptraceback is not really documented, so I use PyErr_Print() above
1230-
PyObject *ptype;
1231-
PyObject *pvalue;
1232-
PyObject *ptraceback;
1233-
PyErr_Fetch( &ptype, &pvalue, &ptraceback);
1234-
1235-
Py_XDECREF(ptype);
1236-
Py_XDECREF(pvalue);
1237-
Py_XDECREF(ptraceback);
1238-
*/
1239-
PyErr_Clear();
1285+
if (PythonQt::priv()->_SystemExitExceptionHandlerEnabled &&
1286+
PyErr_ExceptionMatches(PyExc_SystemExit)) {
1287+
int exitcode = custom_system_exit_exception_handler();
1288+
emit PythonQt::self()->systemExitExceptionRaised(exitcode);
1289+
}
1290+
else
1291+
{
1292+
// currently we just print the error and the stderr handler parses the errors
1293+
PyErr_Print();
1294+
1295+
/*
1296+
// EXTRA: the format of the ptype and ptraceback is not really documented, so I use PyErr_Print() above
1297+
PyObject *ptype;
1298+
PyObject *pvalue;
1299+
PyObject *ptraceback;
1300+
PyErr_Fetch( &ptype, &pvalue, &ptraceback);
1301+
1302+
Py_XDECREF(ptype);
1303+
Py_XDECREF(pvalue);
1304+
Py_XDECREF(ptraceback);
1305+
*/
1306+
PyErr_Clear();
1307+
}
12401308
flag = true;
12411309
}
12421310
PythonQt::priv()->_ErrorOccured = flag;
@@ -1256,6 +1324,22 @@ void PythonQt::resetErrorFlag()
12561324
}
12571325
}
12581326

1327+
void PythonQt::setSystemExitExceptionHandlerEnabled(bool value)
1328+
{
1329+
if (PythonQt::self())
1330+
{
1331+
PythonQt::priv()->_SystemExitExceptionHandlerEnabled = value;
1332+
}
1333+
}
1334+
1335+
bool PythonQt::systemExitExceptionHandlerEnabled()const
1336+
{
1337+
if (PythonQt::self())
1338+
{
1339+
return PythonQt::priv()->_SystemExitExceptionHandlerEnabled;
1340+
}
1341+
}
1342+
12591343
void PythonQt::addSysPath(const QString& path)
12601344
{
12611345
PythonQtObjectPtr sys;

src/PythonQt.h

+15
Original file line numberDiff line numberDiff line change
@@ -488,6 +488,15 @@ class PYTHONQT_EXPORT PythonQt : public QObject {
488488
//! \sa PythonQt::errorOccured()
489489
void resetErrorFlag();
490490

491+
//! if set to True, signal will be emitted if exception SystemExit is caught
492+
//! \sa PythonQt::handleError(), PythonQt::
493+
void setSystemExitExceptionHandlerEnabled(bool value);
494+
495+
//! return \a True if SystemExit exception is handled by PythonQt
496+
//! \sa setSystemExitExceptionHandlerEnabled()
497+
bool systemExitExceptionHandlerEnabled()const;
498+
499+
491500
//! set a callback that is called when a QObject with parent == NULL is wrapped by pythonqt
492501
void setQObjectWrappedCallback(PythonQtQObjectWrappedCB* cb);
493502
//! 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 {
517526
//! emitted when help() is called on a PythonQt object and \c ExternalHelp is enabled
518527
void pythonHelpRequest(const QByteArray& cppClassName);
519528

529+
//! emitted when both custom SystemExit exception handler is enabled and a SystemExit
530+
//! exception is raised.
531+
//! \sa setSystemExitExceptionHandlerEnabled(bool)
532+
void systemExitExceptionRaised(int exitCode);
533+
520534
private:
521535
void initPythonQtModule(bool redirectStdOut, const QByteArray& pythonQtModuleName);
522536

@@ -716,6 +730,7 @@ class PYTHONQT_EXPORT PythonQtPrivate : public QObject {
716730
int _PythonQtObjectPtr_metaId;
717731

718732
bool _ErrorOccured;
733+
bool _SystemExitExceptionHandlerEnabled;
719734

720735
friend class PythonQt;
721736
};

0 commit comments

Comments
 (0)