How to use RwLock/Mutex.into_inner() with Arc<RwLock<T>>


#1

Rust 1.6 stabilised into_inner() method on RwLock/Mutex fn into_inner(self) -> LockResult<T>.

I thought this would suit one scenario in my code:
I have several workers running, and they need to share some data. Thus I have an Arc<RwLock<T>> for the shared data. And at a certain stage, the workers will suspend, and a controller thread will start. At this stage, the shared data is no longer accessible for the workers (they are all suspended), and the controller should be able to access the data without locking.

I would be interested to use into_inner() to get the data for the controller so that I can use it freely. However I couldn’t figure out how to do it.

If the RwLock is not within Arc, it is simple.

struct Foo {
    i: i32
}

fn main() {
    use std::sync::RwLock;
    let mut phase1_foo : Option<RwLock<Foo>> = Some(RwLock::new(Foo{i: 0}));    
    let mut phase2_foo : Option<Foo> = None;

    // now phase 2, the data needs to be accessed without lock
    phase2_foo = Some(phase1_foo.take().unwrap().into_inner().unwrap());    

    // back to phase 1, protect the data with lock
    phase1_foo = Some(RwLock::new(phase2_foo.unwrap()));
}

But if the RwLock is used with Arc, I am not sure how to achieve this.

use std::sync::{Arc, Mutex, Condvar, RwLock};
use std::thread;

#[macro_use]
extern crate lazy_static;

lazy_static! {
    static ref COND : Arc<(Mutex<usize>, Condvar)> = {
        Arc::new((Mutex::new(0), Condvar::new()))
    };
}

pub struct Worker {
    global: Arc<RwLock<Global>>
}

impl Worker {
    pub fn sleep(&self) {
        let &(ref lock, ref cvar) = &*COND.clone();
        let mut count = lock.lock().unwrap();
        *count += 1;
        
        while *count != 0 {
            count = cvar.wait(count).unwrap();
        }
    }
}

pub struct Global(i32);

fn main() {
    let global = Arc::new(RwLock::new(Global(0)));
    
    for i in 0..10 {
        let global = global.clone();
        thread::spawn(move || {
                let worker = Worker{global: global};
                worker.sleep();
            });
    }
    
    // workers do not access 'global' at this stage
    // it is possible to extract the 'global' from the lock and access it freely?
}

I would appreciate any help.


#2

The Arc type is inherently about sharing, while into_inner only works if the RwLock is guaranteed to be not shared, since it steals the data right out from inside it. The compiler can’t statically tell that a given Arc isn’t shared, but you can dynamically assert/check non-sharing by first stealing the RwLock out of the Arc, with try_unwrap.

Note, however, that this basically requires all the child threads to have either finished, dropped their handle to the Arc, or have downgraded it to a Weak. This problem gets much harder if you’re relying on some other, external synchronisation that guarantees no other accesses can be happening.


#3

Another option if the workers won’t exit is to use Option<T> and then steal with Option::take. This causes the protected variable to become None, allowing you to dynamically check that the value hasn’t been stolen.


#4

Thanks, huon and sorear.

I have been successful in getting the protected data Arc<RwLock<T>> out of lock after the workers are suspended. However, I am trying to figure out how to get T back in RwLock for each worker thread and resume workers.

One way is to let the main thread recreate Arc<RwLock<T>> for each worker, and set one by one.

But I would prefer if each worker can set that back by themselves. I tried to use Weak reference to do this.

Before worker suspension, I Arc::downgrade() the Arc for each worker. So at the suspension point, there are several Weak<RwLock<T>>, but only 1 strong reference to Arc<RwLock<T>> so that I am able to try_unwrap() the Arc. But it seems if I unwrap the Arc, the Arc will be destroyed and all the weak references are invalidated, and the workers will not be able to upgrade the weak reference back into a Arc<RwLock<T>>.

Any thought on this?


#5

That sounds like a use case for Arc<RwLock<Option<T>>> then; you can take the rwlock, then use val = guard.take() to get ownership of the value, then put it back by assigning *guard = Some(val). As long as your code doesn’t panic between the take and putback, nobody else will see None.

I presume there’s a reason you can’t just leave the value in the RwLock and work with a &mut borrow in the main thread?