I have a thread that periodically calls a callback function. Depending on the state, the callback function shall acquire an RwLock
of a resource shared with other threads and keep the resource locked even beyond the scope of the callback function. It shall then again depending on the state release the resource again in a later callback cycle.
My idea was to put an Option<RwLockReadGuard<T>>
into a struct which would be None
when the resource is not locked and Some(RwLockReadGuard<T>)
when the resource is locked.
Unfortunately, I can't make this work. I have to set up the struct which contains the Option<RwLockReadGuard<T>>
outside the thread of the callback function. Even though at the time the struct is moved into the thread the Option
is None
, the compiler won't let me pass the option because the trait bound ``std::sync::RwLockReadGuard<'_, T>: std::marker::Send`` is not satisfied
.
Maybe some code. I hope it's self-explaining enough.
use std::thread;
use std::sync::{Arc, RwLock, RwLockReadGuard};
struct Handler<'a> {
resource: Arc<RwLock<String>>,
locked_resource: Option<RwLockReadGuard<'a, String>>,
counter: usize,
}
impl<'a> Handler<'a> {
fn callback(&'a mut self) {
println!("Callback {}", self.counter);
if self.counter == 0 {
println!("Locking resource");
let res = self.resource.read().unwrap();
self.locked_resource = Some(res);
}
self.counter += 1;
if self.counter == 100 {
println!("Releasing resource");
self.locked_resource = None;
}
if self.counter == 200 {
self.counter = 0;
}
}
}
fn main() {
let resource = Arc::new(RwLock::new("foo".to_string()));
let handler = Handler {
resource: resource.clone(),
locked_resource: None,
counter: 0
};
// This gives E0277
let thread = thread::spawn( move || {
loop {
handler.callback();
}
});
}
The problem is: locking and unlocking need to occur on the same thread. This is, for example, a limitation of pthread.
Fortunately, the Rust type system is expressive enough to model this: by making RwLockReadGuard
be !Send
, it prevents locks to be accidentally shared! All hail Rust!
So you can lock and unlock in different callback functions... but on the same thread.
In your example, this is as simple as moving the creation of handler
inside the thread. In your real application, it might be a bit more complicated, but rest assured: the compiler will hold your hand along the way ;)
fn main() {
let resource = Arc::new(RwLock::new("foo".to_string()));
let thread = thread::spawn( move || {
let handler = Handler {
resource: resource,
locked_resource: None,
counter: 0
};
loop {
handler.callback();
}
});
}