I was trying to implement my own custom SpinLock, but the SpinLock seems to misbehave.
I have two files, main.rs
and safe.rs
.
The test was done in Ubuntu 22.04.3LTS and the system specs are 4GB RAM, 64bit processor, AMD® Pro a4-3350b APU with Radeon r4 graphics.
Here is the error message:
loki@loki:~/main/vs/actic/rust-nomic/spin-lock$ cargo run RUST_BACKTRACE=1
Compiling spin-lock v0.1.0 (/home/loki/main/vs/actic/rust-nomic/spin-lock)
Finished dev [unoptimized + debuginfo] target(s) in 0.98s
Running `target/debug/spin-lock RUST_BACKTRACE=1`
Hello, world!
thread 'main' panicked at 'assertion failed: `(left == right)`
left: `9999995`,
right: `10000000`', src/main.rs:15:5
note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace
safe.rs
:
use core::ops::{Deref,DerefMut};
use core::sync::atomic::{AtomicBool,Ordering::{Acquire,Release}};
use core::cell::UnsafeCell;
use core::hint::spin_loop;
#[derive(Debug)]
pub struct SpinLock<T>{
// Status true -> its locked || Status false -> its un_locked(ready to lock)
status:AtomicBool,
pub data:UnsafeCell<T>
}
pub struct SpinGuard<'a,T>{
lock:&'a SpinLock<T>
}
unsafe impl<T> Sync for SpinLock<T> where T:Send{}
impl<T> SpinLock<T>{
#[inline]
pub const fn new(data:T)->Self{
Self { status: AtomicBool::new(false), data: UnsafeCell::new(data) }
}
pub fn lock(&self)->SpinGuard<T>{
while self.status.swap(true,Acquire){
spin_loop();
}
SpinGuard { lock: self }
}
}
impl<'a,T> SpinGuard<'a,T>{
pub fn release(self){
self.lock.status.store(false, Release)
}
}
impl<T> Deref for SpinGuard<'_,T>{
type Target = T;
fn deref(&self) -> &Self::Target {
unsafe{&*self.lock.data.get()}
}
}
impl<T> DerefMut for SpinGuard<'_,T>{
fn deref_mut(&mut self) -> &mut Self::Target {
unsafe{&mut *self.lock.data.get()}
}
}
impl<T> Drop for SpinGuard<'_,T>{
fn drop(&mut self) {
self.lock.status.store(false, Release)
}
}
main.rs
:
use std::thread;
mod safe;
fn main() {
println!("Hello, world!");
let mut x=safe::SpinLock::new(0);
thread::scope(|t|{
for _ in 0..10000000{
t.spawn(||{
let mut q=x.lock();
*q+=1;
q.release();
});
}
});
assert_eq!(x.data.get_mut(),&mut 10000000);
println!("{:?}",x);
}
By adding a couple of
sleep
s, you can make your error a lot more reproducible:The reason is that you unlock twice. It's a little hidden, but the
.release()
function actually unlocks once directly, and once in thedrop()
function.Simply delete the first unlock in the
.release()
function, then it works :)As another nitpick, I would replace the
Acquire
inswap
with anAcqRel
. But I'm not 100% sure that it makes a difference.