Skip to content

Concurrency primitives in sync.rs with static enforcement. #3145

Closed
@bblum

Description

@bblum

These will serve as enhanced versions of arc::exclusive. The plan is for mutex_arc to be the same as exclusive, but scheduler enabled, and come equipped with a working condition variable. It is marked unsafe because you can still create cycles with it (benefit: you can nest them).

rw_arc is somewhat more advanced, and notably more safe. The const serves two purposes - it allows handing out immutable references while in read mode (just like arc::arc does today), and it also prevents nesting them inside each other, since they won't be const themselves.

I am also thinking of exposing semaphores, with acquire and release and potentially also cond_wait and cond_signal. These wouldn't protect anything themselves, but could perhaps be used to synchronise beyond-rust shared resources, such as the filesystem.

Proposed interface:

trait condvar {
    fn signal();
    fn wait();
}

impl <T: send> for &mutex_arc<T> {
    unsafe fn access<U>(blk: fn(&mut T) -> U) -> U;
    unsafe fn access_cond<U>(blk: fn(&mut T, condvar) -> U) -> U;
}

impl <T: const send> for &rw_arc<T> {
    // Read mode
    fn access_read<U>(blk: fn(&T) -> U) -> U;

    // Write mode
    fn access<U>(blk: fn(&mut T) -> U) -> U;
    fn access_cond<U>(blk: fn(&mut T, &condvar) -> U) -> U;
    fn access_downgrade<U>(blk: fn(+write_mode<T>) -> U) -> U;
}

fn downgrade<T: const send>(+write_mode<T>) -> read_mode<T>;

impl <T: const send> for &write_mode<T> {
    fn access<U>(blk: fn(&mut T) -> U) -> U;
    fn access_cond<U>(blk: fn(&mut T, &condvar) -> U) -> U;
}

impl <T: const send> for &read_mode<T> {
    fn access<U>(blk: fn(&T) -> U) -> U;
}

A couple points:

  • The condvar is just like rust_cond_lock very-unsafely provided in the past.

cvar example:

do mutex_arc.access_cond |state, cond| {
    ...
    while not_satisfied(state) {
        cond.wait();
    }
    ...
}
  • I don't think it's meaningful to hand out a condition variable for rws in read-mode. And it'd be a bunch nastier to implement. Feel free to argue, but I think that (a) signalling on one is meaningless, since you can't have changed anything inside, and (b) waiting on one is meaningless, because either you forbid writers from going before you wake (in which case what are you waiting for?) or writers can go (in which case you might as well have dropped the lock wholesale).
  • Downgrade is really neat (hopefully). The read_mode and write_mode are linear tokens that allow you to access the state, and downgrade consumes the write mode. This allows you atomically downgrade without releasing the lock, while statically enforcing no mutation after the downgrade.

Downgrade example:

do rw_arc.access_downgrade |write_mode| {
    do write_mode.access |state| {
        ... mutate state ...
    }
    let read_mode = downgrade(write_mode);
    do read_mode.access |state| {
        ... state is immutable ...
    }
}

In particular, I would like confirmation that my understanding of region pointers will enforce the im/mutability properties.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-lifetimesArea: Lifetimes / regionsE-hardCall for participation: Hard difficulty. Experience needed to fix: A lot.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions