2

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();
                }
        });
}
Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366

1 Answers1

3

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();
        }
    });
}
Community
  • 1
  • 1
Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • I have thought of this and tried your solution. But then it says `handler might not live long enough`. But that is not an option anyway because in my real project I don't setup the thread myself. I just hand in a trait object with the call back function and a library starts the thread. – Johannes Mueller Jan 13 '17 at 19:23
  • Interesting error, which has nothing to do with the `Mutex` question: you cannot [borrow from a sibling](http://stackoverflow.com/questions/32300132/why-cant-i-store-a-value-and-a-reference-to-that-value-in-the-same-struct). As for your issue, I'm sorry, but them's are rule. You cannot lock from a thread, unlock from another thread, and expect things to work: you *have* to design your program in another way. – Matthieu M. Jan 13 '17 at 19:29
  • But it's the same thread, isn't it? It's one thread that keeps calling `callback()` over and over again. I want it to keep the lock across several of those cycles. – Johannes Mueller Jan 13 '17 at 19:35
  • 2
    @JohannesMueller: Are you sure it is? What if the library uses a thread-pool? What if the current version of the library doesn't use a thread-pool but the next does (ie, beware of using `unsafe` to bypass the restriction)? In any case, you currently have the problem that you cannot take a lock on a sibling field, maybe solving *that* issue will guide you toward another more accommodating design? – Matthieu M. Jan 13 '17 at 19:42