BaseSpinLock and BaseSpinLockGuard
Relevant source files
Purpose and Scope
This document provides detailed technical analysis of the core spinlock implementation in the kspin crate. The BaseSpinLock<G, T> struct and its companion BaseSpinLockGuard<G, T> form the foundational implementation that underlies all public spinlock types in the crate. This generic implementation enables different protection behaviors through type parameterization while providing efficient RAII-based lock management.
For information about the public spinlock APIs that users interact with, see Spinlock Types and Public API. For details about the trait system that enables different protection behaviors, see BaseGuard Trait System.
Architecture Overview
The core implementation consists of two primary components that work together to provide safe, efficient spinlock functionality:
Core Implementation Structure
flowchart TD
subgraph subGraph3["BaseSpinLock Architecture"]
BaseSpinLock["BaseSpinLock<G, T>Generic spinlock container"]
BaseSpinLockGuard["BaseSpinLockGuard<G, T>RAII access guard"]
subgraph subGraph2["Core Operations"]
AcquireLock["G::acquire() + atomic CAS"]
ReleaseLock["atomic store + G::release()"]
DataAccess["Deref/DerefMut traits"]
end
subgraph subGraph1["BaseSpinLockGuard Fields"]
PhantomRef["_phantom: &PhantomData<G>Lifetime-bound guard marker"]
IrqState["irq_state: G::StateSaved protection state"]
DataPtr["data: *mut TDirect data access pointer"]
LockRef["lock: &AtomicBool(SMP only)"]
end
subgraph subGraph0["BaseSpinLock Fields"]
PhantomG["_phantom: PhantomData<G>Zero-cost guard type marker"]
LockState["lock: AtomicBool(SMP only)"]
Data["data: UnsafeCell<T>Protected data storage"]
end
end
AcquireLock --> BaseSpinLockGuard
BaseSpinLock --> AcquireLock
BaseSpinLock --> Data
BaseSpinLock --> LockState
BaseSpinLock --> PhantomG
BaseSpinLockGuard --> DataAccess
BaseSpinLockGuard --> DataPtr
BaseSpinLockGuard --> IrqState
BaseSpinLockGuard --> LockRef
BaseSpinLockGuard --> PhantomRef
BaseSpinLockGuard --> ReleaseLock
Sources: src/base.rs(L27 - L43) src/base.rs(L49 - L101) src/base.rs(L218 - L227)
Method Implementation Map
flowchart TD
subgraph subGraph2["SMP-Conditional Logic"]
CompareExchange["compare_exchange_weakline 85"]
SpinLoop["spin_loop()line 90"]
AtomicStore["store(false)line 224"]
end
subgraph subGraph1["BaseSpinLockGuard Traits"]
Deref["Deref::deref()line 198"]
DerefMut["DerefMut::deref_mut()line 206"]
Drop["Drop::drop()line 222"]
Debug["Debug::fmt()line 213"]
end
subgraph subGraph0["BaseSpinLock Methods"]
New["new(data: T)line 52"]
Lock["lock()line 77"]
TryLock["try_lock()line 122"]
IsLocked["is_locked()line 110"]
ForceUnlock["force_unlock()line 159"]
GetMut["get_mut()line 170"]
IntoInner["into_inner()line 63"]
end
BaseSpinLockGuard["BaseSpinLockGuard"]
BaseSpinLockGuard --> Deref
BaseSpinLockGuard --> DerefMut
BaseSpinLockGuard --> Drop
Drop --> AtomicStore
Lock --> CompareExchange
Lock --> SpinLoop
TryLock --> CompareExchange
Sources: src/base.rs(L49 - L175) src/base.rs(L195 - L227)
BaseSpinLock Structure Analysis
Field Organization and Purpose
The BaseSpinLock<G, T> struct is carefully designed to minimize memory overhead while supporting both SMP and single-core environments:
| Field | Type | Purpose | Conditional |
|---|---|---|---|
| _phantom | PhantomData | Zero-cost guard type marker | Always present |
| lock | AtomicBool | Lock state for atomic operations | SMP feature only |
| data | UnsafeCell | Protected data storage | Always present |
The conditional compilation using #[cfg(feature = "smp")] allows the lock state to be completely eliminated in single-core builds, reducing memory overhead and eliminating unnecessary atomic operations.
Sources: src/base.rs(L27 - L32)
Constructor and Data Access Methods
The new() constructor provides compile-time initialization with zero runtime cost:
// Simplified view of the new() method
pub const fn new(data: T) -> Self {
Self {
_phantom: PhantomData,
data: UnsafeCell::new(data),
#[cfg(feature = "smp")]
lock: AtomicBool::new(false),
}
}
The into_inner() and get_mut() methods provide safe data extraction when exclusive access is statically guaranteed, eliminating the need for runtime locking.
Sources: src/base.rs(L52 - L59) src/base.rs(L63 - L68) src/base.rs(L170 - L175)
BaseSpinLockGuard RAII Implementation
Guard Structure and Lifetime Management
The BaseSpinLockGuard<'a, G, T> implements the RAII pattern to ensure automatic lock release:
flowchart TD
subgraph subGraph2["Guard Lifecycle"]
Create["Guard Creationlock() or try_lock()"]
Access["Data AccessDeref/DerefMut"]
Drop["Automatic CleanupDrop trait"]
subgraph subGraph1["Drop Steps"]
AtomicRelease["lock.store(false)(SMP only)"]
GuardRelease["G::release(irq_state)Restore protection"]
end
subgraph subGraph0["Creation Steps"]
Acquire["G::acquire()Get protection state"]
CAS["compare_exchange(SMP only)"]
ConstructGuard["Construct guard withdata pointer & state"]
end
end
Access --> Drop
Acquire --> CAS
AtomicRelease --> GuardRelease
CAS --> ConstructGuard
ConstructGuard --> Access
Create --> Acquire
Drop --> AtomicRelease
Sources: src/base.rs(L77 - L101) src/base.rs(L218 - L227)
Memory Safety Through Type System
The guard uses several mechanisms to ensure memory safety:
- Lifetime binding: The
'alifetime ensures the guard cannot outlive the lock - Raw pointer storage: Direct
*mut Taccess eliminates borrowing conflicts - Phantom reference:
&'a PhantomData<G>ties the guard lifetime to the lock - Exclusive data access: The guard provides exclusive access through
Deref/DerefMut
Sources: src/base.rs(L37 - L43) src/base.rs(L195 - L210)
Core Locking Operations
Lock Acquisition Algorithm
The lock() method implements a two-phase spinning algorithm optimized for different contention scenarios:
The algorithm uses compare_exchange_weak for potential efficiency gains on some architectures, falling back to spinning until the lock appears available before retrying.
Sources: src/base.rs(L77 - L101)
Try-Lock Implementation
The try_lock() method provides non-blocking acquisition using a strong compare-exchange operation:
flowchart TD
subgraph subGraph1["Single-Core Path"]
AlwaysSuccess["Always succeedsis_unlocked = true"]
CreateGuard["Create guard"]
end
subgraph subGraph0["SMP Path"]
StrongCAS["compare_exchange(strong)"]
Success["Create guard"]
Failure["Return None"]
end
TryLock["try_lock()"]
Acquire["G::acquire()"]
Acquire --> AlwaysSuccess
Acquire --> StrongCAS
AlwaysSuccess --> CreateGuard
StrongCAS --> Failure
StrongCAS --> Success
TryLock --> Acquire
The use of strong compare-exchange is specifically chosen over weak to avoid unnecessary retry loops in the try-lock scenario.
Sources: src/base.rs(L122 - L149)
SMP vs Single-Core Optimization
Conditional Compilation Strategy
The crate uses cfg_if! macros to provide dramatically different implementations based on the smp feature:
| Operation | SMP Implementation | Single-Core Implementation |
|---|---|---|
| Lock state | AtomicBoolfield | No field (zero bytes) |
| lock() | Atomic CAS + spinning | Immediate success |
| try_lock() | Atomic CAS | Always succeeds |
| is_locked() | load(Relaxed) | Always returnsfalse |
| Guard drop | store(false, Release) | No atomic operation |
This approach allows single-core builds to have zero overhead for lock operations while maintaining identical APIs.
Sources: src/base.rs(L13 - L14) src/base.rs(L29 - L30) src/base.rs(L111 - L118) src/base.rs(L125 - L136)
Memory Ordering Semantics
The SMP implementation uses carefully chosen memory orderings:
- Acquire ordering on successful CAS: Ensures all subsequent reads see writes from the previous critical section
- Relaxed ordering on failed CAS: No synchronization needed for failed attempts
- Release ordering on unlock: Ensures all writes in critical section are visible before lock release
- Relaxed ordering for
is_locked(): Only a hint, no synchronization guarantees
Sources: src/base.rs(L85) src/base.rs(L131) src/base.rs(L224) src/base.rs(L113)
Thread Safety and Sync Implementation
Safety Trait Implementations
The crate provides the same safety guarantees as std::sync::Mutex:
unsafe impl<G: BaseGuard, T: ?Sized + Send> Sync for BaseSpinLock<G, T> {}
unsafe impl<G: BaseGuard, T: ?Sized + Send> Send for BaseSpinLock<G, T> {}
These implementations are safe because:
- The lock ensures exclusive access to the data
- The guard system prevents concurrent access
- The
BaseGuardtrait handles interrupt/preemption safety
Sources: src/base.rs(L46 - L47)
Debug and Display Support
Both BaseSpinLock and BaseSpinLockGuard implement Debug to aid in development:
- The lock's debug implementation attempts
try_lock()to safely display data - The guard's debug implementation directly displays the protected data
- Locked spinlocks display
"SpinLock { <locked> }"to avoid blocking