blob: 3879f26de27a696b63a06dabfc85daed6f570672 [file] [view] [edit]
# 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
```rust
#[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)
```rust
// 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),
})
}
}
```