Skip to content

Commit

Permalink
Add is_destroyed method to CxxQtThread
Browse files Browse the repository at this point in the history
- Implement is_destroyed method for CxxQtThread to check if associated QObject has been destroyed
- Add cxxQtThreadIsDestroyed function in C++ header
- Update Rust trait and implementation for Threading
- Add documentation explaining usage and potential race conditions
- Modify code generation to include new method in FFI layer
  • Loading branch information
akiselev authored Oct 4, 2024
1 parent f366fcb commit 85526ac
Show file tree
Hide file tree
Showing 5 changed files with 84 additions and 0 deletions.
25 changes: 25 additions & 0 deletions crates/cxx-qt-gen/src/generator/rust/threading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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 =
Expand Down Expand Up @@ -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! {
Expand All @@ -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<F>(cxx_qt_thread: &#module_ident::#cxx_qt_thread_ident, f: F) -> std::result::Result<(), cxx::Exception>
where
Expand Down Expand Up @@ -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;
}
},
);
Expand All @@ -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<F>(cxx_qt_thread: &qobject::MyObjectCxxQtThread, f: F) -> std::result::Result<(), cxx::Exception>
where
Expand Down
10 changes: 10 additions & 0 deletions crates/cxx-qt-gen/test_outputs/invokables.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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"]
Expand Down Expand Up @@ -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<F>(
cxx_qt_thread: &ffi::MyObjectCxxQtThread,
f: F,
Expand Down
13 changes: 13 additions & 0 deletions crates/cxx-qt/include/thread.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ class CxxQtThread final
CxxQtThread(const CxxQtThread<T>& other) = default;
CxxQtThread(CxxQtThread<T>&& other) = default;

bool isDestroyed() const
{
const auto guard = ::std::shared_lock(m_obj->mutex);
return m_obj->ptr == nullptr;
}

template<typename A>
void queue(::rust::Fn<void(T& self, ::rust::Box<A> arg)> func,
::rust::Box<A> arg) const
Expand Down Expand Up @@ -107,6 +113,13 @@ cxxQtThreadQueue(const CxxQtThread<T>& cxxQtThread,
cxxQtThread.queue(::std::move(func), ::std::move(arg));
}

template<typename T>
bool
cxxQtThreadIsDestroyed(const CxxQtThread<T>& cxxQtThread)
{
return cxxQtThread.isDestroyed();
}

} // namespace cxxqt1
} // namespace rust

Expand Down
3 changes: 3 additions & 0 deletions crates/cxx-qt/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Self>;

#[doc(hidden)]
fn is_destroyed(cxx_qt_thread: &CxxQtThread<Self>) -> bool;

#[doc(hidden)]
fn queue<F>(cxx_qt_thread: &CxxQtThread<Self>, f: F) -> Result<(), cxx::Exception>
where
Expand Down
33 changes: 33 additions & 0 deletions crates/cxx-qt/src/threading.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
}
}

0 comments on commit 85526ac

Please sign in to comment.