Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mocks panic when accessed from multiple threads #35

Closed
Wesmania opened this issue Mar 29, 2021 · 6 comments · Fixed by #36
Closed

Mocks panic when accessed from multiple threads #35

Wesmania opened this issue Mar 29, 2021 · 6 comments · Fixed by #36

Comments

@Wesmania
Copy link

On 0.0.8, this minimal example panics:

#[faux::create]
struct Foo { }

#[faux::methods]
impl Foo {
    pub fn foo(&self) {}
}

fn main() {
    let mut fake = Foo::faux();
    faux::when!(fake.foo).then(|()| {});
    let fa1 = std::sync::Arc::new(fake);
    let fa2 = fa1.clone();
    let t1 = std::thread::spawn(move || loop { fa1.foo() });
    let t2 = std::thread::spawn(move || loop { fa2.foo() });
    t1.join().unwrap();
    t2.join().unwrap();
}

try_lock() in morphed.rs seems to be the cause.

@nrxus
Copy link
Owner

nrxus commented Mar 30, 2021

Hm, this is kind of tricky.

faux stores the mocks in a Mutex<MockStore>. In order to access them, we need to lock the mutex. I originally did try_lock instead of lock thinking:

At least it will panic instead of deadlocking

But looking at your example, doing a lock should work. I am still concerned there may be some edge case out there where doing lock would case a deadlock, but maybe having it work for the easy case is worth that.

What do you think?

@Wesmania
Copy link
Author

One issue I can think of is that a global lock can cause a deadlock when calling different methods. Something like that:

faux::when!(foo.baz).then(|()| { a.lock(); a.unlock(); });
fn thread1() {
    a.lock();
    foo.bar();
    a.unlock();
}

fn thread2() {
    foo.baz():
}

First thread1 locks a, then thread2 calls baz and waits, then thread1 calls bar and we deadlock.

One way to alleviate that could be to wrap each mocked method in an Arc<Mutex<_>> so we can clone the method out of MockStore and release its lock immediately. That protects us from most surprises.

@nrxus
Copy link
Owner

nrxus commented Mar 30, 2021

I don't understand how an Arc<Mutex<_>> would protect us from a deadlock, can you please elaborate a little more?

For what it's worth, .then can only be passed in a 'static closure, so the only way it could borrow a lock (like it does in your example) is through then_unchecked which is marked as unsafe. This should already hint users to be very careful.

@Wesmania
Copy link
Author

From what I understand, the MockStore lock is taken for the entire duration of a call, so it synchronizes access to all mock's methods, causing an unexpected dealock described above. The idea behind storing an Arc<Mutex<_>> is to grab the MockStore mutex only to clone the Arc out, then hold the method's mutex during the call. It doesn't eliminate deadlocks entirely, but it no longer locks the entire object, so playing around with sleeping/locking inside a mock method is less likely to cause unexpected deadlocks.

@nrxus
Copy link
Owner

nrxus commented Mar 31, 2021

Ohh I see, that's an interesting thought. That gets a little tricky as it needs to be wrapped in a Mutex<> for the entire mock store already as some mocks get consumed. I think for now I will change it to be a lock rather than try_lock and add a warning on the docs somewhere regarding possible deadlocks. This is a great catch, thanks!

And of course I welcome PRs if you beat me to the fix since I have been very focused on the new api changes lately, and I still have work to do there.

Wesmania pushed a commit to Wesmania/faux that referenced this issue Mar 31, 2021
This makes mocks no longer panic, while making sure that calling
individual mocked methods does not lock the entire mock. For
convenience, the mutex is moved inside MockStore.

Fixes nrxus#35.

Signed-off-by: Igor Kotrasinski <[email protected]>
@Wesmania
Copy link
Author

Wesmania commented Mar 31, 2021

I think #36 is a good start, though I think I completely ignored the mock consuming bit.

@nrxus nrxus closed this as completed in 3f27234 Apr 1, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants