Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move Python related code to Python module #946

Merged
merged 1 commit into from
Aug 31, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion include/effectengine/Effect.h
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ class Effect : public QThread
void setInputImage(const int priority, const Image<ColorRgb> &image, const int timeout_ms, const bool &clearEffect);

private:

void setModuleParameters();
void addImage();

Hyperion *_hyperion;
Expand Down
26 changes: 26 additions & 0 deletions include/python/PythonProgram.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
#pragma once

#include <QByteArray>
#include <QString>

// 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;
};
148 changes: 10 additions & 138 deletions libsrc/effectengine/Effect.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
#include <hyperion/Hyperion.h>

// python utils/ global mainthread
#include <python/PythonProgram.h>
#include <python/PythonUtils.h>
//impl
PyThreadState* mainThreadState;
Expand Down Expand Up @@ -60,24 +61,8 @@ Effect::~Effect()
_imageStack.clear();
}

void Effect::run()
void Effect::setModuleParameters()
m-seker marked this conversation as resolved.
Show resolved Hide resolved
{
// 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");

Expand All @@ -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)
Expand All @@ -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();
}
161 changes: 161 additions & 0 deletions libsrc/python/PythonProgram.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
#include <python/PythonProgram.h>
#include <python/PythonUtils.h>
#include <utils/Logger.h>

#include <QThread>

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
}