Skip to main content

guarded

Attribute Macro guarded 

Source
#[guarded]
Expand description

Instruments a struct with lock-class registration, KCell/KOnceCell field wrapping, and type-safe lock guard accessors.

§Attributes

  • #[mutex] / #[mutex(CustomClass, flags = ...)]: Marks a KMutex, KSpinlock, or KMonitoredSpinlock field and generates a lock class and lock_<name>() / guard_<name>() methods.
  • #[brwlock] / #[brwlock(flags = ...)]: Marks a BrwLockPi field and generates a lock class and read_<name>() / write_<name>() / guard_read_<name>() / guard_write_<name>() methods.
  • #[guarded_by(lock1)] or #[guarded_by(lock1, lock2, ...)]: Wraps a field in KCell (or parameterizes a KOnceCell<T> field) with the lock class (or tuple of lock classes) of the specified lock(s).
    • Single-lock (#[guarded_by(mu1)]): Holding mu1 grants both shared (&T) and exclusive (&mut T) access via guard.field(), guard.as_mut().field_mut(), and guard.as_mut().fields_mut().field.
    • Multi-lock (#[guarded_by(mu1, mu2, ...)]): Holding any one of the guarding locks grants shared read access (&T) via guard.field(), guard.fields().field, and guard.as_mut().fields_mut().field (which projects the field as &T rather than &mut T). Mutating the field requires holding all guarding locks simultaneously and passing the remaining lock guards or tokens to guard.as_mut().field_mut(&mut other_guard, ...).

§Generated Code Example (Multi-Lock Field)

Given the following input struct:

ⓘ
#[guarded]
struct Example {
    #[mutex]
    mu1: KMutex,
    #[mutex]
    mu2: KMutex,
    #[guarded_by(mu1)]
    single: u32,
    #[guarded_by(mu1, mu2)]
    shared: u64,
}

#[guarded] expands to the following (simplified for mu1; mu2 is generated symmetrically):

ⓘ
#[derive(Default, Debug, Clone, Copy)]
struct ExampleMu1Class;
#[derive(Default, Debug, Clone, Copy)]
struct ExampleMu2Class;

#[::pin_init::pin_data]
struct Example {
    #[pin]
    mu1: KMutex<ExampleMu1Class, ::ksync::RawMutex>,
    #[pin]
    mu2: KMutex<ExampleMu2Class, ::ksync::RawMutex>,
    single: ::ksync::KCell<u32, ExampleMu1Class>,
    shared: ::ksync::KCell<u64, (ExampleMu1Class, ExampleMu2Class)>,
}

// Read-only projection (`g1.fields()`): all fields guarded by `mu1` are `&'b T`.
struct ExampleMu1Fields<'b> {
    single: &'b u32,
    shared: &'b u64,
    _marker: ::core::marker::PhantomData<(&'b (),)>,
}

// Mutable projection (`g1.as_mut().fields_mut()`):
// - Single-lock `single` is projected mutably (`&'b mut u32`).
// - Multi-lock `shared` is projected immutably (`&'b u64`) because holding only `mu1`
//   only suffices to read `shared`.
struct ExampleMu1FieldsMut<'b> {
    single: &'b mut u32,
    shared: &'b u64,
    _marker: ::core::marker::PhantomData<(&'b (),)>,
}

impl<'a, P: ::ksync::ExclusiveLockPolicy<::ksync::RawMutex>> ExampleMu1Guard<'a, P> {
    fn single(&self) -> &u32 {
        unsafe { self.parent.single.get(self.inner.token()) }
    }

    fn single_mut<'f>(self: ::core::pin::Pin<&'f mut Self>) -> &'f mut u32 {
        let me = unsafe { self.get_unchecked_mut() };
        let inner_pin = unsafe { ::core::pin::Pin::new_unchecked(&mut me.inner) };
        let self_token = inner_pin.token_mut();
        let token = self_token;
        unsafe { me.parent.single.get_mut(token) }
    }

    // Holding `mu1` alone is sufficient to read `shared`:
    fn shared(&self) -> &u64 {
        unsafe { self.parent.shared.get(self.inner.token()) }
    }

    // Mutating `shared` requires `self` (`mu1`) plus an exclusive token/guard for `mu2`:
    fn shared_mut<'f>(
        self: ::core::pin::Pin<&'f mut Self>,
        mu2_token: &'f mut impl ::ksync::ExclusiveLockToken<Example, ExampleMu2Class>,
    ) -> &'f mut u64 {
        let me = unsafe { self.get_unchecked_mut() };
        let inner_pin = unsafe { ::core::pin::Pin::new_unchecked(&mut me.inner) };
        let self_token = inner_pin.token_mut();
        let token = {
            // Runtime check that `mu2_token` belongs to the same `Example` instance
            // (`me.parent`). When both guards are locked on the same `Example` reference in
            // an inlined scope, the compiler constant-folds this pointer comparison away.
            assert!(
                ::core::ptr::eq(
                    ::core::ptr::from_ref(me.parent),
                    ::ksync::ExclusiveLockToken::parent(mu2_token),
                ),
                "Lock guard belongs to a different struct instance",
            );
            self_token.join(::ksync::ExclusiveLockToken::lock_token_mut(mu2_token))
        };
        unsafe { me.parent.shared.get_mut(token) }
    }
}