Skip to main content

mono_rt/
guard.rs

1//! RAII guard for Mono thread attachment.
2//!
3//! Mono requires every thread that interacts with managed objects to register itself with the
4//! runtime via `mono_thread_attach` before making any API calls, and to unregister via
5//! `mono_thread_detach` before the thread exits. Failing to detach leaks the thread's entry in
6//! Mono's internal thread table and prevents the garbage collector from reclaiming associated
7//! resources.
8//!
9//! [`MonoThreadGuard`] encapsulates this lifecycle: constructing it attaches the current thread,
10//! and dropping it detaches it. Because [`MonoThreadGuard`] is [`!Send`][std::marker::Send], the
11//! compiler guarantees that drop happens on the same thread as construction, which is required by
12//! Mono's attach/detach contract.
13
14use std::ffi::c_void;
15use std::marker::PhantomData;
16
17use super::{Result, api};
18
19/// A RAII guard that keeps the current thread attached to the Mono runtime.
20///
21/// Constructing this guard calls `mono_thread_attach`, registering the thread with the Mono GC
22/// and enabling the use of all Mono handle types. Dropping it calls `mono_thread_detach`,
23/// releasing the thread's entry in the runtime.
24///
25/// # `!Send + !Sync`
26///
27/// This type deliberately does not implement [`Send`] or [`Sync`]. `mono_thread_detach` must be
28/// called from the same operating-system thread that called `mono_thread_attach`. If the guard
29/// were moved to another thread and dropped there, it would detach the wrong thread, leaving the
30/// original thread permanently registered and causing the detaching thread to corrupt Mono's
31/// internal bookkeeping.
32///
33/// The `!Send` bound also creates a natural pairing with the handle types in this crate: since
34/// all Mono handles are `!Send + !Sync`, they cannot escape the thread on which they were
35/// obtained, and that thread is guaranteed to hold a live guard.
36///
37/// # Panics
38///
39/// The `Drop` implementation does not panic. If the Mono API is unavailable at drop time
40/// (which cannot happen under normal use since the guard can only be constructed after a
41/// successful [`init`](crate::init)), a warning is emitted via [`tracing`] and the detach is
42/// skipped.
43#[must_use = "dropping this guard immediately detaches the thread from Mono"]
44pub struct MonoThreadGuard {
45    thread_ptr: *mut c_void,
46    // makes the type !Send + !Sync, enforcing that drop happens on the attaching thread
47    _marker: PhantomData<*mut ()>,
48}
49
50impl MonoThreadGuard {
51    /// Attaches the current thread to the Mono runtime.
52    ///
53    /// This registers the thread with Mono's garbage collector. Any Mono handle type obtained
54    /// after this call is safe to use on the current thread for as long as this guard is live.
55    ///
56    /// Prefer creating one guard per thread at the top of the thread's entry point and keeping it
57    /// alive for the entire duration of Mono usage on that thread.
58    ///
59    /// # Errors
60    ///
61    /// Returns [`MonoError::Uninitialized`](crate::MonoError::Uninitialized) if [`init`](crate::init)
62    /// has not been called yet.
63    ///
64    /// # Safety
65    ///
66    /// The returned guard must be dropped on the same thread that called `attach`. Dropping it on
67    /// a different thread would call `mono_thread_detach` for the wrong thread, corrupting Mono's
68    /// internal thread registry.
69    pub unsafe fn attach() -> Result<Self> {
70        let domain = api()?.get_root_domain();
71        let thread_ptr = api()?.thread_attach(domain);
72
73        Ok(Self {
74            thread_ptr,
75            _marker: PhantomData,
76        })
77    }
78}
79
80impl Drop for MonoThreadGuard {
81    fn drop(&mut self) {
82        match api() {
83            Ok(api) => api.thread_detach(self.thread_ptr),
84            Err(e) => tracing::warn!("could not detach Mono thread on drop: {e}"),
85        }
86    }
87}