Follow-up to #676 (PR #1432).
#676 / PR #1432 fixed the garbage-collection use-after-free for C++ readers
(ReadFunctor<T>) subscribing to either a C++ Message<T> or a C Msg_C source: the
subscription now keeps the Python source object alive via an opaque keep-alive bridge whose
release is driven by ReadFunctor's C++ destructor.
That mechanism does not cover the other direction: a C-module input message
(Msg_C reader) subscribing to a stand-alone source. For example a C module's dataInMsg:
mod = cModuleTemplate.cModuleTemplate()
inMsg = messaging.CModuleTemplateMsg().write(payload) # created in a helper scope
mod.dataInMsg.subscribeTo(inMsg) # mod.dataInMsg is a Msg_C reader
# inMsg can still be garbage collected here -> mod reads freed memory
Why it was deferred: Msg_C is a plain C struct (msg_C.h.in) with no destructor and no
reader/writer distinction — the "reader" state is raw pointers stored in the struct itself.
There is no C++ destructor to hook a Py_DECREF onto, and SWIG only deletes a Msg_C when
Python owns it (module-embedded Msg_C is never deleted by SWIG), so a struct-stored release
callback would either never fire (leak) or fire at the wrong time.
Current workaround (still required for this case): retain the source message in a
persistent Python scope (e.g. on the simulation object), as documented in bskKnownIssues.rst
and previously in #1107.
Suggested approach for a fix: a side-table keyed by the Msg_C* (e.g. a C++
std::unordered_map<void*, PyObject*>) populated by the SWIG subscribeTo wrapper for
Msg_C, with the reference dropped by *_C_unsubscribe and by a SWIG __del__/tp_dealloc
extension on the Msg_C proxy. This needs its own design pass; out of scope for #1432.
Relevant files: src/architecture/messaging/cMsgCInterface/msg_C.h.in, msg_C.cpp.in,
src/architecture/messaging/msgAutoSource/cMsgCInterfacePy.i.in.
Follow-up to #676 (PR #1432).
#676 / PR #1432 fixed the garbage-collection use-after-free for C++ readers
(
ReadFunctor<T>) subscribing to either a C++Message<T>or a CMsg_Csource: thesubscription now keeps the Python source object alive via an opaque keep-alive bridge whose
release is driven by
ReadFunctor's C++ destructor.That mechanism does not cover the other direction: a C-module input message
(
Msg_Creader) subscribing to a stand-alone source. For example a C module'sdataInMsg:Why it was deferred:
Msg_Cis a plain C struct (msg_C.h.in) with no destructor and noreader/writer distinction — the "reader" state is raw pointers stored in the struct itself.
There is no C++ destructor to hook a
Py_DECREFonto, and SWIG only deletes aMsg_CwhenPython owns it (module-embedded
Msg_Cis never deleted by SWIG), so a struct-stored releasecallback would either never fire (leak) or fire at the wrong time.
Current workaround (still required for this case): retain the source message in a
persistent Python scope (e.g. on the simulation object), as documented in
bskKnownIssues.rstand previously in #1107.
Suggested approach for a fix: a side-table keyed by the
Msg_C*(e.g. a C++std::unordered_map<void*, PyObject*>) populated by the SWIGsubscribeTowrapper forMsg_C, with the reference dropped by*_C_unsubscribeand by a SWIG__del__/tp_deallocextension on the
Msg_Cproxy. This needs its own design pass; out of scope for #1432.Relevant files:
src/architecture/messaging/cMsgCInterface/msg_C.h.in,msg_C.cpp.in,src/architecture/messaging/msgAutoSource/cMsgCInterfacePy.i.in.