Must async executor provide happens-before relationship?

Yes, condvar provides a happens-before relationship.

I can't see that in Condvar in std::sync - Rust , can you please point me at the right documentation?

I don't think it's documented anywhere. But it's clearly documented for mutex here, and in the case of pollster that is suffiicent.

Is notifier accessing that mutex when notifying condvar?

Yes.

As far as I can see in the std implementation here

it doesn't touch mutex on notify.

I'm not sure does futex api itself privide happens-before relationship, but std implementation of Mutex establishes it itself with orderings while using futex api. My logic leads (that is unreliable) me to conlcusion that no, fuxes api is not providing happens-before relationship, because then std implementation could've just rely on that.

So as far as I understand, convar provide happens-before relationship to modifications to data inside Mutex (because Mutex provides them with atomics in std implementation), but not to the notify_one call

I was talking about pollster. Look here:

    fn notify(&self) {
        let mut state = self.state.lock().unwrap();
        match *state {
            // The signal was already notified, no need to do anything because the thread will be waking up anyway
            SignalState::Notified => {}
            // The signal wasnt notified but a thread isnt waiting on it, so we can avoid doing unnecessary work by
            // skipping the condvar and leaving behind a message telling the thread that a notification has already
            // occurred should it come along in the future.
            SignalState::Empty => *state = SignalState::Notified,
            // The signal wasnt notified and there's a waiting thread. Reset the signal so it can be wait()'ed on again
            // and wake up the thread. Because there should only be a single thread waiting, `notify_all` would also be
            // valid.
            SignalState::Waiting => {
                *state = SignalState::Empty;
                self.cond.notify_one();
            }
        }
    }

The mutex is locked here, and notify_one is called while the mutex is held.

1 Like

As for futex_wait, mutex is using atomics together with it because that's how you're intended to use it. The futex syscalls are much much slower than a single atomic operation, so using atomics directly will make the code much faster when the mutex doesn't need to block.

Found the documentation;

// readiness logic ...

It's a bit sparse.

--

will_wake() could be used to bypass waker sync; so back to seeing task state sync as more necessary rather than precautionary.

Note that tokio is using SeqCst access in the same situation, with Condvar

The condvar is irrelevant for this topic. The orderings that matter are in the task state.

// thread 1:
change to `value`
store(release) channel state

// thread 2:
load(acquire) channel state
read value

Strictly speaking, this code does not guaranteed that change to value happens-before read value even if store happens first except that the atomic load reads the value written by store.