0

I'm using the discord crate, which uses event loops. I need to check for events concurrently, while I am taking user input:

let clone = context.clone();
thread::spawn(
    move || loop {
        match clone.lock().unwrap().gateway.recv_event() {
            Ok(event) => {
                // println!("Updating state: {:?}", event);
                clone.lock().unwrap().state.update(&event)
            },
            Err(err) => {
                stderr!("Error receiving: {}", err);
            },
        }
    }
);

This doesn't work because it stays locked... So what about

println!("Locking");
let mut gateway = {
    &mut clone.lock().unwrap().gateway
};
println!("Unlocked? {:?}", clone);
match gateway.recv_event() {
    Ok(event) => {

This also doesn't seem to work:

Locking
Unlocked? Mutex { <locked> }

How would this be solved?

It occurred to me it might just be best if there was a way to access to mutex's contents without locking.

Example in Playground.

The original question is here on reddit.

Shepmaster
  • 388,571
  • 95
  • 1,107
  • 1,366
jD91mZM2
  • 25
  • 2
  • 8

1 Answers1

1

I would recommend you to read again on Mutex and what a lock guard is (see also Borrow data out of a mutex "borrowed value does not live long enough", which has a similar problem). The problem is that clone.lock().unwrap().gateway.recv_event() creates a Mutex lock guard that is retained until the full match statement. For instance, let us redefine Something:

#[derive(Debug)]
struct Something(i32);

impl Something {
    pub fn get_some_event(&self) -> i32 {
        thread::sleep(Duration::from_secs(1));
        self.0
    }
}

This code works:

let variable = Mutex::new(Something(4));
match variable.lock().unwrap().get_some_event() 
{
    5 => {
        variable.lock().unwrap();
    }
    _ => {
        println!("{:?}", variable); // Still locked
    }
}
println!("{:?}", variable); // no longer locked!

But this one leads to a deadlock (since the standard mutexes in Rust are not reentrant):

let variable = Mutex::new(Something(5));
match variable.lock().unwrap().get_some_event() 
{
    5 => {
        variable.lock().unwrap(); // deadlock!
    }
    _ => {
        println!("{:?}", variable);
    }
}
println!("{:?}", variable);

The solution lies in the fact that you only need the lock while fetching the event (or while performing other actions). While not doing so, make sure that the respective lock is dropped.

let r =  clone.lock().unwrap().gateway.recv_event(); // get result + drop lock
match event {
    Ok(event) => {
         clone.lock().unwrap().state.update(&event) // also ok
     },
     ...
}
E_net4
  • 27,810
  • 13
  • 101
  • 139
  • Thanks a lot for your answer! However, the problem is that recv_event is the thing that takes time, but I need to use that concurrently. – jD91mZM2 Jun 01 '17 at 10:02
  • @LEGOlord208 I don't understand what you mean with that. The only safe way to call `&mut self` methods concurrently is with synchronization mechanisms such as `Mutex`. Consider improving your question to fit additional requirements. – E_net4 Jun 01 '17 at 10:18
  • Ooooooooohhhhhhhhhhhhhhhhhhhh uhh I think you just solved it right there. Maybe it doesn't have to be mutable! Lemme check. aww crap. If this is impossible I might as well make a pull request to discord-rs to make it not mutable if that's possible. – jD91mZM2 Jun 01 '17 at 11:36
  • You don't think you're understanding the problem. But neither can I help if you do not update your question. In the mean time, check out the book on "interior mutability" and "concurrency". – E_net4 Jun 01 '17 at 11:39
  • Not understanding the problem? The problem is that recv_event blocks, and since it's a mutable reference, I have to lock it for that entire time, which means I can't do anything at the same time. Correct? – jD91mZM2 Jun 01 '17 at 11:46
  • Ah, so you wanted multiple threads to receive events at the same time! In that case no, you can't do that directly. :) – E_net4 Jun 01 '17 at 11:49
  • Receive events at the same time as doing something else on the object, yeah. Thanks a lot for your answer. While it turns out it's practically impossible, I'll still accept it since you helped a lot. Thanks a lot, and cheers! [P.S. Wrote an update on the original reddit post](https://www.reddit.com/r/rust/comments/6e6nwu/accessing_event_loop_via_mutex/diba5c4/) – jD91mZM2 Jun 01 '17 at 11:56