For a block invoke to another thread, safe and restore

* In future we could handle async exceptions by storing the exception
  information in a std::function derived object (instead of the separate
  ExceptionHandling that lives on the stack) and query it out in a new
  WaitForInvoke function maybe. Right now we just print the exception
  to the output log and abort the callback.
This commit is contained in:
baldurk
2017-08-03 16:20:28 +01:00
parent 1c93ee9441
commit 87ef595cce
6 changed files with 88 additions and 17 deletions
@@ -829,3 +829,18 @@ extern "C" void HandleException(PyObject *global_handle)
if(redirector->context)
emit redirector->context->exception(typeStr, valueStr, frames);
}
extern "C" bool IsThreadBlocking(PyObject *global_handle)
{
OutputRedirector *redirector = (OutputRedirector *)global_handle;
if(redirector && redirector->context)
return redirector->context->threadBlocking();
return false;
}
extern "C" void SetThreadBlocking(PyObject *global_handle, bool block)
{
OutputRedirector *redirector = (OutputRedirector *)global_handle;
if(redirector && redirector->context)
return redirector->context->setThreadBlocking(block);
}
@@ -89,6 +89,8 @@ public:
}
static QWidget *QWidgetFromPy(PyObject *widget);
void setThreadBlocking(bool block) { m_Block = block; }
bool threadBlocking() { return m_Block; }
void abort() { m_Abort = true; }
bool shouldAbort() { return m_Abort; }
QString currentFile() { return location.file; }
@@ -128,6 +130,8 @@ private:
bool m_Abort = false;
bool m_Block = false;
static PyObject *QtObjectToPython(PyObject *self, const char *typeName, QObject *object);
QTimer *outputTicker;
+44 -13
View File
@@ -800,19 +800,29 @@ PyObject *ConvertToPy(PyObject *self, const T &in)
// this is defined elsewhere for managing the opaque global_handle object
extern "C" PyThreadState *GetExecutingThreadState(PyObject *global_handle);
extern "C" void HandleException(PyObject *global_handle);
extern "C" bool IsThreadBlocking(PyObject *global_handle);
extern "C" void SetThreadBlocking(PyObject *global_handle, bool block);
struct ExceptionHandling
{
bool failFlag = false;
PyObject *exObj = NULL;
PyObject *valueObj = NULL;
PyObject *tracebackObj = NULL;
};
// this function handles failures in callback functions. If we're synchronously calling the callback
// from within an execute scope, then we can assign to failflag and let the error propagate upwards.
// If we're not, then the callback is being executed on another thread with no knowledge of python,
// so we need to use the global handle to try and emit the exception through the context. None of
// this is multi-threaded because we're inside the GIL at all times
inline void HandleCallbackFailure(PyObject *global_handle, bool &fail_flag)
inline void HandleCallbackFailure(PyObject *global_handle, ExceptionHandling &exHandle)
{
// if there's no global handle assume we are not running in the usual environment, so there are no
// external-to-python threads
if(!global_handle)
{
fail_flag = true;
exHandle.failFlag = true;
return;
}
@@ -822,7 +832,25 @@ inline void HandleCallbackFailure(PyObject *global_handle, bool &fail_flag)
// we are executing synchronously, set the flag and return
if(current == executing)
{
fail_flag = true;
exHandle.failFlag = true;
return;
}
// if we have the blocking flag set, then we may be on another thread but we can still propagate
// up the error
if(IsThreadBlocking(global_handle))
{
exHandle.failFlag = true;
// we need to rethrow the exception to that thread, so fetch (and clear it) on this thread.
//
// Note that the exception can only propagate up to one place. However since we know that python
// is inherently single threaded, so if we're doing this blocking funciton call on another
// thread then we *know* there isn't python further up the stack. Therefore we're safe to
// swallow the exception here (since there's nowhere for it to bubble up to anyway) and rethrow
// on the python thread.
PyErr_Fetch(&exHandle.exObj, &exHandle.valueObj, &exHandle.tracebackObj);
return;
}
@@ -832,7 +860,8 @@ inline void HandleCallbackFailure(PyObject *global_handle, bool &fail_flag)
}
template <typename T>
inline T get_return(const char *funcname, PyObject *result, PyObject *global_handle, bool &failflag)
inline T get_return(const char *funcname, PyObject *result, PyObject *global_handle,
ExceptionHandling &exHandle)
{
T val = T();
@@ -840,7 +869,7 @@ inline T get_return(const char *funcname, PyObject *result, PyObject *global_han
if(!SWIG_IsOK(res))
{
HandleCallbackFailure(global_handle, failflag);
HandleCallbackFailure(global_handle, exHandle);
PyErr_Format(PyExc_TypeError, "Expected a '%s' for return value of callback in %s",
TypeName<T>(), funcname);
@@ -852,7 +881,8 @@ inline T get_return(const char *funcname, PyObject *result, PyObject *global_han
}
template <>
inline void get_return(const char *funcname, PyObject *result, PyObject *global_handle, bool &failflag)
inline void get_return(const char *funcname, PyObject *result, PyObject *global_handle,
ExceptionHandling &exHandle)
{
Py_XDECREF(result);
}
@@ -897,22 +927,23 @@ struct varfunc
}
~varfunc() { Py_XDECREF(args); }
rettype call(const char *funcname, PyObject *func, PyObject *global_handle, bool &failflag)
rettype call(const char *funcname, PyObject *func, PyObject *global_handle,
ExceptionHandling &exHandle)
{
if(!func || func == Py_None || !PyCallable_Check(func) || !args)
{
HandleCallbackFailure(global_handle, failflag);
HandleCallbackFailure(global_handle, exHandle);
return rettype();
}
PyObject *result = PyObject_Call(func, args, 0);
if(result == NULL)
HandleCallbackFailure(global_handle, failflag);
HandleCallbackFailure(global_handle, exHandle);
Py_DECREF(args);
return get_return<rettype>(funcname, result, global_handle, failflag);
return get_return<rettype>(funcname, result, global_handle, exHandle);
}
int currentarg = 0;
@@ -939,7 +970,7 @@ struct ScopedFuncCall
};
template <typename funcType>
funcType ConvertFunc(PyObject *self, const char *funcname, PyObject *func, bool &failflag)
funcType ConvertFunc(PyObject *self, const char *funcname, PyObject *func, ExceptionHandling &exHandle)
{
// add a reference to the global object so it stays alive while we execute, in case this is an
// async call
@@ -949,11 +980,11 @@ funcType ConvertFunc(PyObject *self, const char *funcname, PyObject *func, bool
if(globals)
global_internal_handle = PyDict_GetItemString(globals, "_renderdoc_internal");
return [global_internal_handle, self, funcname, func, &failflag](auto... param) {
return [global_internal_handle, self, funcname, func, &exHandle](auto... param) {
ScopedFuncCall gil(global_internal_handle);
varfunc<typename funcType::result_type, decltype(param)...> f(self, funcname, param...);
return f.call(funcname, func, global_internal_handle, failflag);
return f.call(funcname, func, global_internal_handle, exHandle);
};
}
@@ -32,3 +32,12 @@ extern "C" PyThreadState *GetExecutingThreadState(PyObject *global_handle)
extern "C" void HandleException(PyObject *global_handle)
{
}
extern "C" bool IsThreadBlocking(PyObject *global_handle)
{
return false;
}
extern "C" void SetThreadBlocking(PyObject *global_handle, bool block)
{
}
+10
View File
@@ -77,9 +77,19 @@ CONTAINER_TYPEMAPS(QMap)
%extend IReplayManager {
void BlockInvoke(InvokeCallback m) {
PyObject *global_internal_handle = NULL;
PyObject *globals = PyEval_GetGlobals();
if(globals)
global_internal_handle = PyDict_GetItemString(globals, "_renderdoc_internal");
SetThreadBlocking(global_internal_handle, true);
Py_BEGIN_ALLOW_THREADS
$self->BlockInvoke(m);
Py_END_ALLOW_THREADS
SetThreadBlocking(global_internal_handle, false);
}
};
+6 -4
View File
@@ -53,12 +53,14 @@ CONTAINER_TYPEMAPS(rdctype::array)
%typemap(in, fragment="pyconvert") std::function {
PyObject *func = $input;
failed$argnum = false;
$1 = ConvertFunc<$1_ltype>(self, "$symname", func, failed$argnum);
$1 = ConvertFunc<$1_ltype>(self, "$symname", func, exHandle$argnum);
}
%typemap(argout) std::function (bool failed) {
if(failed) SWIG_fail;
%typemap(argout) std::function (ExceptionHandling exHandle) {
if(exHandle.failFlag) {
PyErr_Restore(exHandle.exObj, exHandle.valueObj, exHandle.tracebackObj);
SWIG_fail;
}
}
// ignore some operators SWIG doesn't have to worry about