Skip to content

Commit

Permalink
Scripting: fix python memory leaks in event handling
Browse files Browse the repository at this point in the history
  • Loading branch information
Felk committed Dec 18, 2022
1 parent c48006d commit 26cdedc
Showing 1 changed file with 23 additions and 2 deletions.
25 changes: 23 additions & 2 deletions Source/Core/Scripting/Python/Modules/eventmodule.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
#include "Scripting/Python/Utils/convert.h"
#include "Scripting/Python/Utils/invoke.h"
#include "Scripting/Python/Utils/module.h"
#include "Scripting/Python/Utils/object_wrapper.h"
#include "Scripting/Python/PyScriptingBackend.h"

namespace PyScripting
Expand Down Expand Up @@ -85,6 +86,24 @@ struct PyEvent<MappingFunc<TEvent, TsArgs...>, TFunc>
PyEval_SaveThread();
};
}

static void DecrefPyObjectsInArgs(const std::tuple<TsArgs...> args) {
std::apply(
[&](auto&&... arg) {
// ad-hoc immediately executed lambda because this must be an expression
(([&] {
if constexpr (std::is_same_v<
std::remove_const_t<std::remove_reference_t<decltype(arg)>>,
PyObject*>)
{
Py_XDECREF(arg);
}
}()),
...);
},
args);
}

static void Listener(const Py::Object module, const TEvent& event)
{
// We make the following assumption here:
Expand All @@ -109,6 +128,7 @@ struct PyEvent<MappingFunc<TEvent, TsArgs...>, TFunc>
}
if (PyCoro_CheckExact(result))
HandleNewCoroutine(module, Py::Wrap(result));
DecrefPyObjectsInArgs(args);
}
static PyObject* SetCallback(PyObject* module, PyObject* newCallback)
{
Expand Down Expand Up @@ -141,13 +161,14 @@ struct PyEvent<MappingFunc<TEvent, TsArgs...>, TFunc>
const Py::Object coro = awaiting_coroutines.front();
awaiting_coroutines.pop_front();
const std::tuple<TsArgs...> args = TFunc(event);
PyObject* args_tuple = Py::BuildValueTuple(args);
PyObject* newAsyncEventTuple = Py::CallMethod(coro, "send", args_tuple);
Py::Object args_tuple = Py::Wrap(Py::BuildValueTuple(args));
PyObject* newAsyncEventTuple = Py::CallMethod(coro, "send", args_tuple.Lend());
if (newAsyncEventTuple != nullptr)
HandleCoroutine(module, coro, Py::Wrap(newAsyncEventTuple));
else if (!PyErr_ExceptionMatches(PyExc_StopIteration))
// coroutines signal completion by raising StopIteration
PyErr_Print();
DecrefPyObjectsInArgs(args);
}
}
static void Clear(EventModuleState* state)
Expand Down

0 comments on commit 26cdedc

Please sign in to comment.