diff --git a/crates/cxx-qt-gen/src/generator/rust/threading.rs b/crates/cxx-qt-gen/src/generator/rust/threading.rs index 210693320..e6f3f9a74 100644 --- a/crates/cxx-qt-gen/src/generator/rust/threading.rs +++ b/crates/cxx-qt-gen/src/generator/rust/threading.rs @@ -42,6 +42,10 @@ pub fn generate( .into_cxx_parts(); let (thread_fn_name, thread_fn_attrs, thread_fn_qualified) = qobject_names.cxx_qt_ffi_method("qtThread").into_cxx_parts(); + let (thread_is_destroyed_name, thread_is_destroyed_attrs, thread_is_destroyed_qualified) = + qobject_names + .cxx_qt_ffi_method("cxxQtThreadIsDestroyed") + .into_cxx_parts(); let namespace_internals = &namespace_ident.internal; let cxx_qt_thread_ident_type_id_str = @@ -85,6 +89,10 @@ pub fn generate( #[doc(hidden)] #(#thread_drop_attrs)* fn #thread_drop_name(cxx_qt_thread: &mut #cxx_qt_thread_ident); + + #[doc(hidden)] + #(#thread_is_destroyed_attrs)* + fn #thread_is_destroyed_name(cxx_qt_thread: &#cxx_qt_thread_ident) -> bool; } }, quote! { @@ -105,6 +113,12 @@ pub fn generate( #thread_fn_qualified(self) } + #[doc(hidden)] + fn is_destroyed(cxx_qt_thread: &#module_ident::#cxx_qt_thread_ident) -> bool + { + #thread_is_destroyed_qualified(cxx_qt_thread) + } + #[doc(hidden)] fn queue(cxx_qt_thread: &#module_ident::#cxx_qt_thread_ident, f: F) -> std::result::Result<(), cxx::Exception> where @@ -216,6 +230,12 @@ mod tests { #[cxx_name = "cxxQtThreadDrop"] #[namespace = "rust::cxxqt1"] fn cxx_qt_ffi_my_object_cxx_qt_thread_drop(cxx_qt_thread: &mut MyObjectCxxQtThread); + + #[doc(hidden)] + #[cxx_name = "cxxQtThreadIsDestroyed"] + #[namespace = "rust::cxxqt1"] + fn cxx_qt_ffi_my_object_cxx_qt_thread_is_destroyed(cxx_qt_thread: &MyObjectCxxQtThread) + -> bool; } }, ); @@ -242,6 +262,11 @@ mod tests { qobject::cxx_qt_ffi_my_object_qt_thread(self) } + #[doc(hidden)] + fn is_destroyed(cxx_qt_thread: &qobject::MyObjectCxxQtThread) -> bool { + qobject::cxx_qt_ffi_my_object_cxx_qt_thread_is_destroyed(cxx_qt_thread) + } + #[doc(hidden)] fn queue(cxx_qt_thread: &qobject::MyObjectCxxQtThread, f: F) -> std::result::Result<(), cxx::Exception> where diff --git a/crates/cxx-qt-gen/test_outputs/invokables.rs b/crates/cxx-qt-gen/test_outputs/invokables.rs index e61f793d2..60c564d8d 100644 --- a/crates/cxx-qt-gen/test_outputs/invokables.rs +++ b/crates/cxx-qt-gen/test_outputs/invokables.rs @@ -135,6 +135,12 @@ mod ffi { #[cxx_name = "cxxQtThreadDrop"] #[namespace = "rust::cxxqt1"] fn cxx_qt_ffi_my_object_cxx_qt_thread_drop(cxx_qt_thread: &mut MyObjectCxxQtThread); + #[doc(hidden)] + #[cxx_name = "cxxQtThreadIsDestroyed"] + #[namespace = "rust::cxxqt1"] + fn cxx_qt_ffi_my_object_cxx_qt_thread_is_destroyed( + cxx_qt_thread: &MyObjectCxxQtThread, + ) -> bool; } extern "Rust" { #[namespace = "cxx_qt::my_object::cxx_qt_my_object"] @@ -251,6 +257,10 @@ impl cxx_qt::Threading for ffi::MyObject { ffi::cxx_qt_ffi_my_object_qt_thread(self) } #[doc(hidden)] + fn is_destroyed(cxx_qt_thread: &ffi::MyObjectCxxQtThread) -> bool { + ffi::cxx_qt_ffi_my_object_cxx_qt_thread_is_destroyed(cxx_qt_thread) + } + #[doc(hidden)] fn queue( cxx_qt_thread: &ffi::MyObjectCxxQtThread, f: F, diff --git a/crates/cxx-qt/include/thread.h b/crates/cxx-qt/include/thread.h index d3acceed4..1d285f323 100644 --- a/crates/cxx-qt/include/thread.h +++ b/crates/cxx-qt/include/thread.h @@ -45,6 +45,12 @@ class CxxQtThread final CxxQtThread(const CxxQtThread& other) = default; CxxQtThread(CxxQtThread&& other) = default; + bool isDestroyed() const + { + const auto guard = ::std::shared_lock(m_obj->mutex); + return m_obj->ptr == nullptr; + } + template void queue(::rust::Fn arg)> func, ::rust::Box arg) const @@ -107,6 +113,13 @@ cxxQtThreadQueue(const CxxQtThread& cxxQtThread, cxxQtThread.queue(::std::move(func), ::std::move(arg)); } +template +bool +cxxQtThreadIsDestroyed(const CxxQtThread& cxxQtThread) +{ + return cxxQtThread.isDestroyed(); +} + } // namespace cxxqt1 } // namespace rust diff --git a/crates/cxx-qt/src/lib.rs b/crates/cxx-qt/src/lib.rs index e345cd698..dee18cafe 100644 --- a/crates/cxx-qt/src/lib.rs +++ b/crates/cxx-qt/src/lib.rs @@ -105,6 +105,9 @@ pub trait Threading: Sized { /// This allows for queueing closures onto the Qt event loop from a background thread. fn qt_thread(&self) -> CxxQtThread; + #[doc(hidden)] + fn is_destroyed(cxx_qt_thread: &CxxQtThread) -> bool; + #[doc(hidden)] fn queue(cxx_qt_thread: &CxxQtThread, f: F) -> Result<(), cxx::Exception> where diff --git a/crates/cxx-qt/src/threading.rs b/crates/cxx-qt/src/threading.rs index 63970568b..79fc4d3ab 100644 --- a/crates/cxx-qt/src/threading.rs +++ b/crates/cxx-qt/src/threading.rs @@ -80,4 +80,37 @@ where { T::queue(self, f) } + + /// Checks whether the associated `QObject` has been destroyed. + /// + /// This method only confirms if the `QObject` has already been destroyed. + /// It does not guarantee that the `QObject` remains alive for any + /// subsequent operations. There is a potential race condition when using + /// `is_destroyed()` before calling `queue`. Specifically, the `QObject` may + /// be destroyed after the check but before the `queue` call. + /// + /// For example: + /// ```rust,ignore + /// if !thread.is_destroyed() { + /// thread.queue(/*...*/).unwrap(); + /// } + /// ``` + /// In this scenario, the `QObject` might be destroyed between the + /// `is_destroyed` check and the `queue` invocation, resulting in a panic. + /// + /// To handle such cases safely, it is recommended to call `queue(...).ok()` + /// directly without checking `is_destroyed()`. This approach allows you to + /// handle the potential failure gracefully without risking a panic. + /// + /// However, `is_destroyed()` can still be useful in scenarios where you + /// need to control loops or perform cleanup operations based on the + /// destruction status of the `QObject`. For instance: + /// ```rust,ignore + /// while !thread.is_destroyed() { + /// thread.queue(/*...*/).ok(); + /// } + /// ``` + pub fn is_destroyed(&self) -> bool { + T::is_destroyed(self) + } }