From c3548cd16a7859401cf575b95002d14d87e7174b Mon Sep 17 00:00:00 2001 From: "Wang, Xiong" Date: Fri, 9 Oct 2026 18:34:18 +0800 Subject: [PATCH] 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. --- qrenderdoc/Code/pyrenderdoc/function_conversion.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/qrenderdoc/Code/pyrenderdoc/function_conversion.h b/qrenderdoc/Code/pyrenderdoc/function_conversion.h index 33344c865..b55ac8cd2 100644 --- a/qrenderdoc/Code/pyrenderdoc/function_conversion.h +++ b/qrenderdoc/Code/pyrenderdoc/function_conversion.h @@ -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) {