GithubHelp home page GithubHelp logo

Comments (7)

Matthias247 avatar Matthias247 commented on June 25, 2024

I was a little bit scared that exposing it would have a side effect when reviewing #38 .

I then decided it wasn't critical since usages like Channel require the RawMutex to be Sync in order for the created construct to be thread-safe:

impl<MutexType: RawMutex + Sync, T: Send, A> Sync for GenericChannel<MutexType, T, A>
where

So as long as you use NoopLock only with futures-intrusive it would be safe.
Passing a NoopLock to another library requiring a RawMutex might be an issue. But technically those should also require Sync on the RawMutex in order to to be able to use it for multi-threaded locking.

from futures-intrusive.

goffrie avatar goffrie commented on June 25, 2024

But you can also lock a mutex twice on the same thread and gain two simultaneous &mut references.

from futures-intrusive.

goffrie avatar goffrie commented on June 25, 2024

Sample:

type BadLock<T> = lock_api::Mutex<futures_intrusive::NoopLock, T>;
fn main() {
    let lock = BadLock::new(Some(String::from("hello world")));
    let g1 = lock.lock();
    let mut g2 = lock.lock();
    let string = g1.as_ref().unwrap();
    g2.take().unwrap();
    eprintln!("boom: {}", string.to_string());
}

from futures-intrusive.

goffrie avatar goffrie commented on June 25, 2024

(I actually think this is "exploitable" without #38 too, since you can use generics to unify a LocalMutex<T> with a GenericMutex<LockType, T> and then create your own lock_api::Mutex<LockType, T>, but that's a lot more contrived..)

from futures-intrusive.

Matthias247 avatar Matthias247 commented on June 25, 2024

I thought about this a bit more before pushing 0.4.

It seems like there doesn't exist any thread-safety issues, since lock-api still requires RawMutex to be Sync for that. See https://docs.rs/lock_api/0.4.2/src/lock_api/mutex.rs.html#140-141

With that in mind the borrow-checking problem persists. That could be fixed by converting NoopLock into a RefCell like implementation. A potential compromise could be to do that only in debug mode, and avoid the extra storage space in release mode. I would be open for a CR for that.

from futures-intrusive.

goffrie avatar goffrie commented on June 25, 2024

I thought about this issue again. NoopLock is also technically unsound even when used with futures-intrusive data structures like LocalMutex, because reentrancy is possible if a nonstandard Waker implementation is in use. This seems incredibly unlikely to happen in practice, but one could imagine e.g. a Waker with some instrumentation attached to it.

Here's a contrived example:

use std::cell::Cell;
use std::future::Future;
use std::ptr;
use std::rc::Rc;
use std::task::{Context, RawWaker, RawWakerVTable, Waker};

use futures::pin_mut;
use futures_intrusive::sync::LocalMutex as Mutex;

thread_local! {
    static MUTEX: Cell<Option<Rc<Mutex<()>>>> = Cell::new(None);
}

const VTABLE: RawWakerVTable = RawWakerVTable::new(
    |_| {
        MUTEX.with(|m| {
            if let Some(m) = m.take() {
                m.try_lock();
            }
        });
        RawWaker::new(ptr::null(), &VTABLE)
    },
    |_| (),
    |_| (),
    |_| (),
);

fn main() {
    let waker = RawWaker::new(ptr::null(), &VTABLE);
    let waker = unsafe { Waker::from_raw(waker) }; // safety: VTABLE meets the RawWaker invariants
    let mutex = Rc::new(Mutex::new((), true));
    let _lock = mutex.try_lock().unwrap();
    MUTEX.with(|m| {
        m.set(Some(mutex.clone()));
    });
    let f = mutex.lock();
    pin_mut!(f);
    let mut cx = Context::from_waker(&waker);
    assert!(f.as_mut().poll(&mut cx).is_pending());
}

This causes the MutexState to be locked twice simultaneously (you can tell because if you import the normal futures_intrusive::sync::Mutex instead, it deadlocks).

from futures-intrusive.

Matthias247 avatar Matthias247 commented on June 25, 2024

Since I had to push a new major revision anyway due to the parking lot version stuff I now simply made NoopLock back private.

For the last example: This deadlocks happens because it tries to lock the synchronous parking lot Mutex 2 times in the same thread. With the singlethreaded variant (NoopLock) this doesn't happen, and the try_lock will fail. The async Mutex is only allocated a single time and works as it should. Of course we discuss about the mutable reference things, but that seems the same discussion as #56.

from futures-intrusive.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.