From b24c8a7d3ad30645311cd516bc8f896226e69424 Mon Sep 17 00:00:00 2001 From: Alexey Nadtochiy Date: Tue, 10 Feb 2026 12:07:59 +0300 Subject: [PATCH 1/5] Fix segfault in python object destruction --- src/pb_stub.cc | 13 +++++++++++++ src/pb_stub.h | 5 +++++ 2 files changed, 18 insertions(+) diff --git a/src/pb_stub.cc b/src/pb_stub.cc index 56048d78..207cd594 100644 --- a/src/pb_stub.cc +++ b/src/pb_stub.cc @@ -1490,6 +1490,17 @@ Stub::GetCUDAMemoryPoolAddress(std::unique_ptr& ipc_message) #endif } +void +Stub::DestroyPythonObjects() { + // Ensure the interpreter is active before trying to clean up. + if (Py_IsInitialized()) { + py::gil_scoped_acquire acquire; + py::object async_event_loop_local(std::move(async_event_loop_)); + py::object background_futures_local(std::move(background_futures_)); + py::object model_instance_local(std::move(model_instance_)); + } +} + void Stub::ProcessBLSResponseDecoupled(std::unique_ptr& ipc_message) { @@ -2077,6 +2088,7 @@ main(int argc, char** argv) non_graceful_exit = true; // Destroy stub and exit. + stub->DestroyPythonObjects(); logger.reset(); stub.reset(); exit(1); @@ -2110,6 +2122,7 @@ main(int argc, char** argv) // objects. If the scoped_interpreter is destroyed before the stub object, // this process will no longer hold the GIL lock and destruction of the stub // will result in segfault. + stub->DestroyPythonObjects(); logger.reset(); stub.reset(); diff --git a/src/pb_stub.h b/src/pb_stub.h index 942ecd98..e2ed2ecd 100644 --- a/src/pb_stub.h +++ b/src/pb_stub.h @@ -265,6 +265,11 @@ class Stub { /// Get the CUDA memory pool address from the parent process. void GetCUDAMemoryPoolAddress(std::unique_ptr& ipc_message); + /// Cleans up Python objects and must be called before the destructor. + /// This prevents problems that occur when Python object destructors + /// call Stub::GetOrCreate. + void DestroyPythonObjects(); + private: bi::interprocess_mutex* stub_mutex_; bi::interprocess_condition* stub_cond_; From 1547ae8446f4cfcb2a3a9e6c9cef5c3ca48965c7 Mon Sep 17 00:00:00 2001 From: Alexey Nadtochiy Date: Fri, 13 Feb 2026 14:08:16 +0300 Subject: [PATCH 2/5] Style fix --- src/pb_stub.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pb_stub.cc b/src/pb_stub.cc index 207cd594..36a92773 100644 --- a/src/pb_stub.cc +++ b/src/pb_stub.cc @@ -1491,7 +1491,8 @@ Stub::GetCUDAMemoryPoolAddress(std::unique_ptr& ipc_message) } void -Stub::DestroyPythonObjects() { +Stub::DestroyPythonObjects() +{ // Ensure the interpreter is active before trying to clean up. if (Py_IsInitialized()) { py::gil_scoped_acquire acquire; From a7d9cb87a875f7e9b4e3fbed8d54b6023426e0cb Mon Sep 17 00:00:00 2001 From: Alexey Nadtochiy Date: Mon, 13 Apr 2026 12:44:24 +0300 Subject: [PATCH 3/5] Move Stub::DestroyPythonObjects call into Stub::DestroyInstance --- src/pb_stub.cc | 7 +++++-- src/pb_stub.h | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/pb_stub.cc b/src/pb_stub.cc index 39d147e1..c4e17be3 100644 --- a/src/pb_stub.cc +++ b/src/pb_stub.cc @@ -1088,6 +1088,11 @@ Stub::GetOrCreateInstance() void Stub::DestroyInstance() { + if (!stub_instance) { + return; + } + + stub_instance->DestroyPythonObjects(); stub_instance.reset(); } @@ -2197,7 +2202,6 @@ main(int argc, char** argv) non_graceful_exit = true; // Destroy stub and exit. - stub->DestroyPythonObjects(); logger.reset(); Stub::DestroyInstance(); exit(1); @@ -2231,7 +2235,6 @@ main(int argc, char** argv) // objects. If the scoped_interpreter is destroyed before the stub object, // this process will no longer hold the GIL lock and destruction of the stub // will result in segfault. - stub->DestroyPythonObjects(); logger.reset(); Stub::DestroyInstance(); diff --git a/src/pb_stub.h b/src/pb_stub.h index 942a651b..b2a97178 100644 --- a/src/pb_stub.h +++ b/src/pb_stub.h @@ -269,7 +269,7 @@ class Stub { /// Cleans up Python objects and must be called before the destructor. /// This prevents problems that occur when Python object destructors - /// call Stub::GetOrCreate. + /// call Stub::GetOrCreateInstance. void DestroyPythonObjects(); /// Calls the user's is_ready() Python method and returns its response From cda7ced0362f31efca671c7f4bb4f7d19d01ceb1 Mon Sep 17 00:00:00 2001 From: Alexey Nadtochiy Date: Mon, 13 Apr 2026 16:39:41 +0300 Subject: [PATCH 4/5] Explicitly destroy deserialize_bytes_local & serialize_bytes_local --- src/pb_stub.cc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/pb_stub.cc b/src/pb_stub.cc index c4e17be3..511cf246 100644 --- a/src/pb_stub.cc +++ b/src/pb_stub.cc @@ -1065,6 +1065,8 @@ Stub::~Stub() py::object async_event_loop_local(std::move(async_event_loop_)); py::object background_futures_local(std::move(background_futures_)); py::object model_instance_local(std::move(model_instance_)); + py::object deserialize_bytes_local(std::move(deserialize_bytes_)); + py::object serialize_bytes_local(std::move(serialize_bytes_)); } stub_message_queue_.reset(); @@ -1517,6 +1519,8 @@ Stub::DestroyPythonObjects() py::object async_event_loop_local(std::move(async_event_loop_)); py::object background_futures_local(std::move(background_futures_)); py::object model_instance_local(std::move(model_instance_)); + py::object deserialize_bytes_local(std::move(deserialize_bytes_)); + py::object serialize_bytes_local(std::move(serialize_bytes_)); } } From e967f7c80b3333ffb48dafc08295318c03daea08 Mon Sep 17 00:00:00 2001 From: Alexey Nadtochiy Date: Wed, 15 Apr 2026 15:43:58 +0300 Subject: [PATCH 5/5] Add DestroyPythonObjects call into destructor --- src/pb_stub.cc | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/src/pb_stub.cc b/src/pb_stub.cc index 511cf246..8d87ab1f 100644 --- a/src/pb_stub.cc +++ b/src/pb_stub.cc @@ -1059,15 +1059,7 @@ Stub::~Stub() } #endif - // Ensure the interpreter is active before trying to clean up. - if (Py_IsInitialized()) { - py::gil_scoped_acquire acquire; - py::object async_event_loop_local(std::move(async_event_loop_)); - py::object background_futures_local(std::move(background_futures_)); - py::object model_instance_local(std::move(model_instance_)); - py::object deserialize_bytes_local(std::move(deserialize_bytes_)); - py::object serialize_bytes_local(std::move(serialize_bytes_)); - } + DestroyPythonObjects(); stub_message_queue_.reset(); parent_message_queue_.reset();