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}