diff --git a/include/effectengine/Effect.h b/include/effectengine/Effect.h index 778f158b..bfd7f72e 100644 --- a/include/effectengine/Effect.h +++ b/include/effectengine/Effect.h @@ -68,7 +68,7 @@ signals: void setInputImage(int priority, const Image &image, int timeout_ms, bool clearEffect); private: - + void setModuleParameters(); void addImage(); Hyperion *_hyperion; diff --git a/include/python/PythonProgram.h b/include/python/PythonProgram.h new file mode 100644 index 00000000..b161dcd6 --- /dev/null +++ b/include/python/PythonProgram.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +// Python includes +// collide of qt slots macro +#undef slots +#include "Python.h" +#define slots + +class Logger; + +class PythonProgram +{ +public: + PythonProgram(const QString & name, Logger * log); + ~PythonProgram(); + + void execute(const QByteArray &python_code); + +private: + QString _name; + Logger* _log; + PyThreadState* _tstate; +}; diff --git a/libsrc/effectengine/Effect.cpp b/libsrc/effectengine/Effect.cpp index 3c02c96f..8598e029 100644 --- a/libsrc/effectengine/Effect.cpp +++ b/libsrc/effectengine/Effect.cpp @@ -21,6 +21,7 @@ #include // python utils/ global mainthread +#include #include //impl PyThreadState* mainThreadState; @@ -60,24 +61,8 @@ Effect::~Effect() _imageStack.clear(); } -void Effect::run() +void Effect::setModuleParameters() { - // we probably need to wait until mainThreadState is available - while(mainThreadState == nullptr){}; - - // get global lock - PyEval_RestoreThread(mainThreadState); - - // Initialize a new thread state - PyThreadState* tstate = Py_NewInterpreter(); - if(tstate == nullptr) - { - PyEval_ReleaseLock(); - Error(_log, "Failed to get thread state for %s",QSTRING_CSTR(_name)); - return; - } - PyThreadState_Swap(tstate); - // import the buildtin Hyperion module PyObject * module = PyImport_ImportModule("hyperion"); @@ -99,6 +84,13 @@ void Effect::run() // decref the module Py_XDECREF(module); +} + +void Effect::run() +{ + PythonProgram program(_name, _log); + + setModuleParameters(); // Set the end time if applicable if (_timeout > 0) @@ -121,126 +113,6 @@ void Effect::run() if (!python_code.isEmpty()) { - PyObject *main_module = PyImport_ImportModule("__main__"); // New Reference - PyObject *main_dict = PyModule_GetDict(main_module); // Borrowed reference - Py_INCREF(main_dict); // Incref "main_dict" to use it in PyRun_String(), because PyModule_GetDict() has decref "main_dict" - Py_DECREF(main_module); // // release "main_module" when done - PyObject *result = PyRun_String(python_code.constData(), Py_file_input, main_dict, main_dict); // New Reference - - if (!result) - { - if (PyErr_Occurred()) // Nothing needs to be done for a borrowed reference - { - Error(_log,"###### PYTHON EXCEPTION ######"); - Error(_log,"## In effect '%s'", QSTRING_CSTR(_name)); - /* Objects all initialized to NULL for Py_XDECREF */ - PyObject *errorType = NULL, *errorValue = NULL, *errorTraceback = NULL; - - PyErr_Fetch(&errorType, &errorValue, &errorTraceback); // New Reference or NULL - PyErr_NormalizeException(&errorType, &errorValue, &errorTraceback); - - // Extract exception message from "errorValue" - if(errorValue) - { - QString message; - if(PyObject_HasAttrString(errorValue, "__class__")) - { - PyObject *classPtr = PyObject_GetAttrString(errorValue, "__class__"); // New Reference - PyObject *class_name = NULL; /* Object "class_name" initialized to NULL for Py_XDECREF */ - class_name = PyObject_GetAttrString(classPtr, "__name__"); // New Reference or NULL - - if(class_name && PyUnicode_Check(class_name)) - message.append(PyUnicode_AsUTF8(class_name)); - - Py_DECREF(classPtr); // release "classPtr" when done - Py_XDECREF(class_name); // Use Py_XDECREF() to ignore NULL references - } - - // Object "class_name" initialized to NULL for Py_XDECREF - PyObject *valueString = NULL; - valueString = PyObject_Str(errorValue); // New Reference or NULL - - if(valueString && PyUnicode_Check(valueString)) - { - if(!message.isEmpty()) - message.append(": "); - - message.append(PyUnicode_AsUTF8(valueString)); - } - Py_XDECREF(valueString); // Use Py_XDECREF() to ignore NULL references - - Error(_log, "## %s", QSTRING_CSTR(message)); - } - - // Extract exception message from "errorTraceback" - if(errorTraceback) - { - // Object "tracebackList" initialized to NULL for Py_XDECREF - PyObject *tracebackModule = NULL, *methodName = NULL, *tracebackList = NULL; - QString tracebackMsg; - - tracebackModule = PyImport_ImportModule("traceback"); // New Reference or NULL - methodName = PyUnicode_FromString("format_exception"); // New Reference or NULL - tracebackList = PyObject_CallMethodObjArgs(tracebackModule, methodName, errorType, errorValue, errorTraceback, NULL); // New Reference or NULL - - if(tracebackList) - { - PyObject* iterator = PyObject_GetIter(tracebackList); // New Reference - - PyObject* item; - while( (item = PyIter_Next(iterator)) ) // New Reference - { - Error(_log, "## %s",QSTRING_CSTR(QString(PyUnicode_AsUTF8(item)).trimmed())); - Py_DECREF(item); // release "item" when done - } - Py_DECREF(iterator); // release "iterator" when done - } - - // Use Py_XDECREF() to ignore NULL references - Py_XDECREF(tracebackModule); - Py_XDECREF(methodName); - Py_XDECREF(tracebackList); - - // Give the exception back to python and print it to stderr in case anyone else wants it. - Py_XINCREF(errorType); - Py_XINCREF(errorValue); - Py_XINCREF(errorTraceback); - - PyErr_Restore(errorType, errorValue, errorTraceback); - //PyErr_PrintEx(0); // Remove this line to switch off stderr output - } - Error(_log,"###### EXCEPTION END ######"); - } - } - else - { - Py_DECREF(result); // release "result" when done - } - - Py_DECREF(main_dict); // release "main_dict" when done + program.execute(python_code); } - // stop sub threads if needed - for (PyThreadState* s = PyInterpreterState_ThreadHead(tstate->interp), *old = nullptr; s;) - { - if (s == tstate) - { - s = s->next; - continue; - } - if (old != s) - { - Debug(_log,"ID %s: Waiting on thread %u", QSTRING_CSTR(_name), s->thread_id); - old = s; - } - - Py_BEGIN_ALLOW_THREADS; - msleep(100); - Py_END_ALLOW_THREADS; - - s = PyInterpreterState_ThreadHead(tstate->interp); - } - - // Clean up the thread state - Py_EndInterpreter(tstate); - PyEval_ReleaseLock(); } diff --git a/libsrc/python/PythonProgram.cpp b/libsrc/python/PythonProgram.cpp new file mode 100644 index 00000000..678e4f72 --- /dev/null +++ b/libsrc/python/PythonProgram.cpp @@ -0,0 +1,161 @@ +#include +#include +#include + +#include + +PythonProgram::PythonProgram(const QString & name, Logger * log) : + _name(name), _log(log), _tstate(nullptr) +{ + // we probably need to wait until mainThreadState is available + while(mainThreadState == nullptr){}; + + // get global lock + PyEval_RestoreThread(mainThreadState); + + // Initialize a new thread state + _tstate = Py_NewInterpreter(); + if(_tstate == nullptr) + { + PyEval_ReleaseLock(); + Error(_log, "Failed to get thread state for %s",QSTRING_CSTR(_name)); + return; + } + PyThreadState_Swap(_tstate); +} + +PythonProgram::~PythonProgram() +{ + if (!_tstate) + return; + + // stop sub threads if needed + for (PyThreadState* s = PyInterpreterState_ThreadHead(_tstate->interp), *old = nullptr; s;) + { + if (s == _tstate) + { + s = s->next; + continue; + } + if (old != s) + { + Debug(_log,"ID %s: Waiting on thread %u", QSTRING_CSTR(_name), s->thread_id); + old = s; + } + + Py_BEGIN_ALLOW_THREADS; + QThread::msleep(100); + Py_END_ALLOW_THREADS; + + s = PyInterpreterState_ThreadHead(_tstate->interp); + } + + // Clean up the thread state + Py_EndInterpreter(_tstate); + PyEval_ReleaseLock(); +} + +void PythonProgram::execute(const QByteArray & python_code) +{ + if (!_tstate) + return; + + PyObject *main_module = PyImport_ImportModule("__main__"); // New Reference + PyObject *main_dict = PyModule_GetDict(main_module); // Borrowed reference + Py_INCREF(main_dict); // Incref "main_dict" to use it in PyRun_String(), because PyModule_GetDict() has decref "main_dict" + Py_DECREF(main_module); // // release "main_module" when done + PyObject *result = PyRun_String(python_code.constData(), Py_file_input, main_dict, main_dict); // New Reference + + if (!result) + { + if (PyErr_Occurred()) // Nothing needs to be done for a borrowed reference + { + Error(_log,"###### PYTHON EXCEPTION ######"); + Error(_log,"## In effect '%s'", QSTRING_CSTR(_name)); + /* Objects all initialized to NULL for Py_XDECREF */ + PyObject *errorType = NULL, *errorValue = NULL, *errorTraceback = NULL; + + PyErr_Fetch(&errorType, &errorValue, &errorTraceback); // New Reference or NULL + PyErr_NormalizeException(&errorType, &errorValue, &errorTraceback); + + // Extract exception message from "errorValue" + if(errorValue) + { + QString message; + if(PyObject_HasAttrString(errorValue, "__class__")) + { + PyObject *classPtr = PyObject_GetAttrString(errorValue, "__class__"); // New Reference + PyObject *class_name = NULL; /* Object "class_name" initialized to NULL for Py_XDECREF */ + class_name = PyObject_GetAttrString(classPtr, "__name__"); // New Reference or NULL + + if(class_name && PyUnicode_Check(class_name)) + message.append(PyUnicode_AsUTF8(class_name)); + + Py_DECREF(classPtr); // release "classPtr" when done + Py_XDECREF(class_name); // Use Py_XDECREF() to ignore NULL references + } + + // Object "class_name" initialized to NULL for Py_XDECREF + PyObject *valueString = NULL; + valueString = PyObject_Str(errorValue); // New Reference or NULL + + if(valueString && PyUnicode_Check(valueString)) + { + if(!message.isEmpty()) + message.append(": "); + + message.append(PyUnicode_AsUTF8(valueString)); + } + Py_XDECREF(valueString); // Use Py_XDECREF() to ignore NULL references + + Error(_log, "## %s", QSTRING_CSTR(message)); + } + + // Extract exception message from "errorTraceback" + if(errorTraceback) + { + // Object "tracebackList" initialized to NULL for Py_XDECREF + PyObject *tracebackModule = NULL, *methodName = NULL, *tracebackList = NULL; + QString tracebackMsg; + + tracebackModule = PyImport_ImportModule("traceback"); // New Reference or NULL + methodName = PyUnicode_FromString("format_exception"); // New Reference or NULL + tracebackList = PyObject_CallMethodObjArgs(tracebackModule, methodName, errorType, errorValue, errorTraceback, NULL); // New Reference or NULL + + if(tracebackList) + { + PyObject* iterator = PyObject_GetIter(tracebackList); // New Reference + + PyObject* item; + while( (item = PyIter_Next(iterator)) ) // New Reference + { + Error(_log, "## %s",QSTRING_CSTR(QString(PyUnicode_AsUTF8(item)).trimmed())); + Py_DECREF(item); // release "item" when done + } + Py_DECREF(iterator); // release "iterator" when done + } + + // Use Py_XDECREF() to ignore NULL references + Py_XDECREF(tracebackModule); + Py_XDECREF(methodName); + Py_XDECREF(tracebackList); + + // Give the exception back to python and print it to stderr in case anyone else wants it. + Py_XINCREF(errorType); + Py_XINCREF(errorValue); + Py_XINCREF(errorTraceback); + + PyErr_Restore(errorType, errorValue, errorTraceback); + //PyErr_PrintEx(0); // Remove this line to switch off stderr output + } + Error(_log,"###### EXCEPTION END ######"); + } + } + else + { + Py_DECREF(result); // release "result" when done + } + + Py_DECREF(main_dict); // release "main_dict" when done +} +