Skip to content

Dealocker is a crate that aims to eliminate deadlocks in a builder-pattern inspired manner

License

Notifications You must be signed in to change notification settings

Areskiko/deadlocker

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

4 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Deadlocker

Dealocker is a crate that aims to eliminate deadlocks in a builder-pattern inspired manner


The main feature of the crate is the derive macro which will do a number of things:

  1. Create one struct for each combination of locks that may be held
  2. Create a struct for holding references to the locks in the original struct
  3. Allow the locker struct to chain methods to specify desired locks in any order
  4. Implement a final lock method on the locker struct which will lock the desired locks in a deterministic order, eliminating deadlocks caused by out-of-order acquisition of locks

Example

use deadlocker::Locker;
use std::sync::{Arc, Mutex};

type Foo = Vec<usize>;
type Bar = usize;
type Baz = u8;

#[derive(Locker)]
pub struct MyStruct {
    #[result]
    pub foo: Arc<Mutex<Foo>>,
    #[result]
    pub bar: Arc<Mutex<Bar>>,
    #[result]
    pub baz: Arc<Mutex<Baz>>,
}

pub fn main() {
    let mut my_struct = MyStruct {
        foo: Arc::new(Mutex::new(Vec::new())),
        bar: Arc::new(Mutex::new(0)),
        baz: Arc::new(Mutex::new(0)),
    };

    {
        let mut lock = my_struct
            .locker()
            .baz()
            .foo()
            .lock()
            .expect("Mutex was poisoned");

        lock.foo.push(1);
        **lock.baz = 1;
    }

    {
        let lock = my_struct
            .locker()
            .bar()
            .foo()
            .baz()
            .lock()
            .expect("Mutex was poisoned");

        println!("Foo: {:?}", **lock.foo);
        println!("Bar: {:?}", **lock.bar);
        println!("Baz: {:?}", **lock.baz);
    }
}

Attributes

Each field may be annotated with a number of attributes to modify the behaviour of the derive macro. The effects are noted below, and examples are provided in the examples directory

outer_type

Indicates what is to be the outer type, i.e. what is the locking part, as opposed to the inner_type. This is the complement to inner_type, it only makes sense to specify one of them, and inner_type takes precedence.

It is specified using a regex with a single capture group where the inner_type is supposed to be. Specifying the outer type is mostly useful when the inner type is long and complex.

#[outer_type = "Arc<Mutex<(.*)>>"]

Note that this is by default set to the above, meaning that if your field conforms to the pattern you needn't specify anything. See the basic example for more.

inner_type

Indicates what is the be the inner type, i.e. what is being guarded by the lock. This is the complement to outer_type, it only makes sense to specify one of them, and this takes precedence over outer_type

#[inner_type = "usize"]

async_lock

Marks the lock as async, this causes the final lock method in a chain containing an async_lock marked field to become asynchronous as well. This does not affect other locks in the struct, so the final lock method needn't be asynchronous just because some of the locks are.

#[async_lock]

lock_method

Indicates how to get at the inner_type from the lock. For std::sync::Mutex this is lock(), and for tokio::sync::Mutex it is lock().await. Note the omission of the leading period, as well as the trailing semicolon.

#[lock_method = "lock()"]

For synchronous locks this defaults to lock(), and to lock().await for asynchronous ones, meaning most people won't have to specify this.

result

Marks the lock as yielding a result over its guard, rather than the guard directly. This is true for std::sync::Mutex but not for tokio::sync::Mutex. This causes the final lock method in a chain containing a result marked field to return a result, with the normally returned struct embedded in its Ok variant.

#[result]

include

Indicates that this field should be included in the locker struct. The presence of a single field marked as such implies that no field without such a mark should be included. Useful if you have a large state struct where only a small portion are locks. This overrides any exclude attribute.

#[include]

exclude

Indicates that this field should not be included in the locker struct. Useful if you have a large state struct where most of the fields are locked.

#[exclude]

About

Dealocker is a crate that aims to eliminate deadlocks in a builder-pattern inspired manner

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages