Fix Python callback reference counting and shutdown cleanup

Clear the argument tuple after calling a Python callback so varfunc's
destructor cannot decrement it a second time. This prevents repeated
zero-argument callbacks from losing references to the shared empty tuple.

Skip callback reference cleanup after Python has shut down, since Qt can
destroy pending callback closures during QApplication teardown.

Validation:
- Regression harness using this header and bundled Python 3.6.4: 20,000
  callbacks with balanced references, nonempty arguments, exception paths,
  deferred release, and callback destruction after Py_Finalize.
- Windows x64 Release build: no warnings or errors.
- MI 8 remote replay: 1.rdc -> 2.rdc -> 1.rdc -> 2.rdc; normal exit with
  a pending callback, exit code 0.

Build and device checks used the existing development checkout, which also
contains separate local changes excluded from this commit.
This commit is contained in:
Wang, Xiong authored and Baldur Karlsson committed 2026-10-09 21:32:37 +09:00
1 parent 37c2395138
commit c3548cd16a
1 file changed
+5 -1
@@ -184,6 +184,10 @@ struct PyObjectRefCounter
PyObjectRefCounter(const PyObjectRefCounter &o) { obj = Py_NewRef(o.obj); }
~PyObjectRefCounter()
{
// Qt can release pending callbacks after Python has shut down.
if(!Py_IsInitialized())
return;
// it may not be safe at the point this is destroyed to decref the object. For example if a
// python lambda is passed into a C++ invoke function, we will be holding the only reference to
// that lambda here when the async invoke completes and destroyed the std::function wrapping it.
@@ -249,7 +253,7 @@ struct varfunc
PyObject *result = DoFunctionCall(func, args);
Py_DECREF(args);
Py_CLEAR(args);
if(result == NULL)
{