Any way to wrap CondVar


#1

Newbie here. Just trying to implement an EventBox which wraps CondVar to provide easy event handling object.
The code likes this:

use std::sync::{Condvar, Mutex};
use std::collections::HashMap;
use std::any::Any;

pub type EventType = i32;

pub type Value = Box<Any + 'static + Send>;
pub type Events = HashMap<EventType, Value>;

pub struct EventBox {
    mutex: Mutex<bool>, // value doesn't matter.
    events: Events,
    cond: Condvar,
    ignore: Events,
}

impl EventBox {
    pub fn new() -> Self {
        EventBox {
            events: HashMap::new(),
            mutex: Mutex::new(false),
            cond: Condvar::new(),
            ignore: HashMap::new(),
        }
    }

    /// wait: wait for an event(any) to fire
    /// if any event is triggered, run callback on events vector
    pub fn wait(&mut self, mut callback: Box<FnMut(&mut Events)>) {
        let mtx = self.mutex.lock().unwrap();
        let num_of_events = self.events.len();
        if num_of_events == 0 {
            self.cond.wait(mtx);
        }
        callback(&mut self.events);
    }

    /// set: fires an event
    pub fn set(&mut self, e: EventType, value: Value) {
        self.mutex.lock();
        let val = self.events.entry(e).or_insert(Box::new(0));
        *val = value;
        if !self.ignore.contains_key(&e) {
            self.cond.notify_all();
        }
    }
}

unsafe impl Sync for EventBox {}

The problem here is I want to share the EventBox object in two threads, and in order to call set or wait, the object need to be mutable. However shared mutable state is not available in rust(not entirely true).

There are several ideas:

  1. Use Arc<Mutex<EventBox>> to share the mutable states between threads. This will not work because when one thread called eventbox.wait(), the other thread will not be able to call eventbox.set(), because it is locked.
  2. Use Arc<Mutex<T>, CondVar> in the first place. But then the goal of simplify the code fails.
  3. Use macro to reduce repeated code. While that’s good, I’d like not using it in this case.

So dear friend, is there any way to achieve this goal? Any ideas is welcome, Thanks in advance!


#2

I think the “normal” solution or at least my normal solution is to make wait and set non mutable and put events and ignore inside the mutex so they are protected.

When i’m dealing with multiple threads, I often end up with an “internal” structure that just contains the protected data:

struct Data {
    events : Events,
    ignore : Events
}

pub struct EventBox {
    mutex : Mutex<Data>,
    cond : Condvar
}

#3

Good Suggestion! I’ll try it out. Thanks!

It seems the only way to share mutable states between threads is using Mutex. There is no way to tell rust that a Struct is safe to be shared mutable, which however is actually not true, because even all the methods are safe to be called we can not ensure the fields are not modified.

Thus, it seems we can only embed the mutable state in an “internal” structure.


#4

I believe the problem with sharing a mutable structure between threads is actually the CPU cache. When you set a value in a struct it might not get written out so the other CPUs can get the value immediately or when you read a value it might just use a cached value for that CPU.

The Mutex ensures that on entry/exit that the state of the protected structure is synchronized across all the CPUs.

You could use AtomicPtr to share a mutable structure between two threads without a mutex. But then you will be responsible for ensuring that the CPU’s cache is correctly maintained.