KSync: Technical Design Document

This document covers the technical architecture, safety invariants, and macro code generation details of the ksync crate.

1. Technical Architecture

ksync implements a token-based synchronization pattern (also known as the “Ghost Token” pattern). Instead of encapsulating the protected data inside the mutex or reader-writer lock struct itself (like std::sync::Mutex<T>), it separates the lock state (KMutex, BrwLockPi) from the actual data storage (KCell).

+-------------------------------------------------------------+
|                        Parent Struct                        |
|                                                             |
|   +-------------------+        +------------------------+   |
|   | KMutex/BrwLockPi  |        |  KCell<T, Class>       |   |
|   |  (Raw Lock State) |        |  (UnsafeCell wrapper)  |   |
|   +--------+----------+        +-----------+------------+   |
+------------|-------------------------------|----------------+
             | lock() / read_lock()          | get(token)
             v                               v
    +--------+------------+         +--------+-------+
    |  Lock Guard         |         |  &T / &mut T   |
    |                     |-------->|  (Safe Access) |
    |  - LockToken        |         +----------------+
    +---------------------+

The Core Types

  1. LockToken<'a, Class>: A zero-sized type (ZST) that serves as compile-time proof that the lock for Class is currently held by the current thread. It has a lifetime 'a bound to the active lock guard.
  2. KMutex<Class>: The mutual exclusion lock state representation. Locking it acquires the raw lock and returns a KMutexGuard holding the LockToken.
  3. BrwLockPi<Class>: The priority-inheriting reader-writer lock state representation. Locking it returns a BrwLockPiReadGuard or BrwLockPiWriteGuard holding the LockToken.
  4. KCell<T, Class>: A wrapper around core::cell::UnsafeCell<T>. It provides token-gated accessors (get(&self, token) and get_mut(&self, token)).

Guard Types & The Instance-Bound Soundness Model

While the LockToken and KCell share a compile-time Class type parameter to prevent mixing locks of different types, this type-level association is insufficient to guarantee memory safety.

To provide safety, the macro generates custom guard structures (e.g. MyStructMuGuard, MyStructLockReadGuard, MyStructLockWriteGuard) that wrap standard lock guards and hold a reference to the parent structure instance. These custom guards expose safe, compiler-checked projection accessors (such as .field() or .fields_mut()). This guarantees safe access to guarded fields without requiring any unsafe block or risking cross-instance token mixing.

2. Safe Exclusive Access

If a thread has unique exclusive access to the KCell container itself (either by holding ownership self or an exclusive borrow &mut self), it doesn't need a runtime lock token to access the data safely. The Rust borrow checker already guarantees compile-time thread exclusivity:

  • get_inner_mut(&mut self) -> &mut T: Accesses the inner value mutably via UnsafeCell::get_mut(). This is safe because the &mut self borrow ensures no other borrows of the cell are active.
  • into_inner(self) -> T: Consumes the KCell container and returns the inner value T safely via UnsafeCell::into_inner().

3. Macro Code Generation

The #[guarded] attribute proc-macro parses a struct definition, rewrites its fields, and generates the lock acquisition methods.

3.1 Input Struct

#[guarded]
pub struct MyStruct {
    #[mutex]
    pub mu: KMutex,

    #[brwlock]
    pub lock: BrwLockPi,

    #[guarded_by(mu)]
    pub data1: u32,

    #[guarded_by(lock)]
    pub data2: i32,
}

3.2 Expanded Code Output (Simplified)

// 1. Unique Lock Class marker structs generated automatically
pub struct MyStructMuClass;
pub struct MyStructLockClass;

// 2. Struct fields rewritten to KMutex/BrwLockPi and KCell
pub struct MyStruct {
    pub mu: ::ksync::KMutex<MyStructMuClass>,
    pub lock: ::ksync::BrwLockPi<MyStructLockClass>,
    pub data1: ::ksync::KCell<u32, MyStructMuClass>,
    pub data2: ::ksync::KCell<i32, MyStructLockClass>,
}

// 3. Custom projection guard structures (stack-pinned)
#[pin_data(PinnedDrop)]
pub struct MyStructMuGuard<'a> {
    parent: &'a MyStruct,
    #[pin]
    inner: ::ksync::KMutexGuard<'a, MyStructMuClass>,
}

#[pin_data(PinnedDrop)]
pub struct MyStructLockReadGuard<'a> {
    parent: &'a MyStruct,
    #[pin]
    inner: ::ksync::BrwLockPiReadGuard<'a, MyStructLockClass>,
}

#[pin_data(PinnedDrop)]
pub struct MyStructLockWriteGuard<'a> {
    parent: &'a MyStruct,
    #[pin]
    inner: ::ksync::BrwLockPiWriteGuard<'a, MyStructLockClass>,
}

// 4. Safe projection accessors and field projection structs
pub struct MyStructLockReadFields<'b> {
    pub data2: &'b i32,
    _marker: ::core::marker::PhantomData<&'b ()>,
}

pub struct MyStructLockWriteFields<'b> {
    pub data2: &'b mut i32,
    _marker: ::core::marker::PhantomData<&'b ()>,
}

impl<'a> MyStructMuGuard<'a> {
    pub fn data1(&self) -> &u32 {
        unsafe { self.parent.data1.get(self.inner.token()) }
    }
    pub fn data1_mut(self: Pin<&mut Self>) -> &mut u32 { ... }
    pub fn fields<'b>(&'b self) -> MyStructMuFields<'b> { ... }
    pub fn fields_mut<'b>(self: Pin<&'b mut Self>) -> MyStructMuFieldsMut<'b> { ... }
}

impl<'a> MyStructLockReadGuard<'a> {
    pub fn data2(&self) -> &i32 {
        unsafe { self.parent.data2.get(self.inner.token()) }
    }
    pub fn fields<'b>(&'b self) -> MyStructLockReadFields<'b> { ... }
}

impl<'a> MyStructLockWriteGuard<'a> {
    pub fn data2(&self) -> &i32 {
        unsafe { self.parent.data2.get(self.inner.token()) }
    }
    pub fn data2_mut(self: Pin<&mut Self>) -> &mut i32 { ... }
    pub fn fields<'b>(&'b self) -> MyStructLockReadFields<'b> { ... }
    pub fn fields_mut<'b>(self: Pin<&'b mut Self>) -> MyStructLockWriteFields<'b> { ... }
}

// 5. Lock methods implemented on the parent struct returning PinInit blocks
impl MyStruct {
    #[inline]
    pub fn lock_mu(&self) -> impl pin_init::PinInit<MyStructMuGuard<'_>, ::core::convert::Infallible> {
        pin_init::pin_init!(MyStructMuGuard {
            parent: self,
            inner <- ::ksync::KMutexGuard::new(&self.mu),
        })
    }

    #[inline]
    pub fn read_lock(&self) -> impl pin_init::PinInit<MyStructLockReadGuard<'_>, ::core::convert::Infallible> {
        pin_init::pin_init!(MyStructLockReadGuard {
            parent: self,
            inner <- ::ksync::BrwLockPiReadGuard::new(&self.lock),
        })
    }

    #[inline]
    pub fn write_lock(&self) -> impl pin_init::PinInit<MyStructLockWriteGuard<'_>, ::core::convert::Infallible> {
        pin_init::pin_init!(MyStructLockWriteGuard {
            parent: self,
            inner <- ::ksync::BrwLockPiWriteGuard::new(&self.lock),
        })
    }
}