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

WaitCell::wait does not tolerate spurious polls #449

Closed
hawkw opened this issue Jul 22, 2023 · 0 comments · Fixed by #453
Closed

WaitCell::wait does not tolerate spurious polls #449

hawkw opened this issue Jul 22, 2023 · 0 comments · Fixed by #453
Assignees
Labels
crate/maitake Related to the `maitake` crate kind/bug Something isn't working

Comments

@hawkw
Copy link
Owner

hawkw commented Jul 22, 2023

This test fails:

#[test]
fn wait_spurious_poll() {
    use tokio_test::{assert_pending, assert_ready_ok, task};

    let cell = Arc::new(WaitCell::new());
    let mut task = task::spawn({
        let cell = cell.clone();
        async move { cell.wait().await }
    });

    assert_pending!(task.poll(), "first poll should be pending");
    assert_pending!(task.poll(), "second poll should be pending");

    cell.wake();

    assert_ready_ok!(task.poll(), "should have been woken");
}

The second poll should return Poll::Pending, because the WaitCell has not yet been woken. However:

running 1 test
thread 'sync::wait_cell::tests::spurious_poll' panicked at 'ready; value = Ok(()); second poll should be pending', maitake/src/sync/wait_cell.rs:432:9
stack backtrace:
   0: rust_begin_unwind
             at /rustc/7820b62d20bc548096d4632a3487987308cb4b5d/library/std/src/panicking.rs:579:5
   1: core::panicking::panic_fmt
             at /rustc/7820b62d20bc548096d4632a3487987308cb4b5d/library/core/src/panicking.rs:64:14
   2: maitake::sync::wait_cell::tests::spurious_poll
   3: maitake::sync::wait_cell::tests::spurious_poll::{{closure}}
             at ./src/sync/wait_cell.rs:422:24
   4: core::ops::function::FnOnce::call_once
             at /rustc/7820b62d20bc548096d4632a3487987308cb4b5d/library/core/src/ops/function.rs:250:5
   5: core::ops::function::FnOnce::call_once
             at /rustc/7820b62d20bc548096d4632a3487987308cb4b5d/library/core/src/ops/function.rs:250:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.
test sync::wait_cell::tests::spurious_poll ... FAILED

failures:

failures:
    sync::wait_cell::tests::spurious_poll

Shoutout to @jamesmunns for catching this oen!

@hawkw hawkw added kind/bug Something isn't working crate/maitake Related to the `maitake` crate labels Jul 22, 2023
@hawkw hawkw self-assigned this Jul 22, 2023
hawkw added a commit that referenced this issue Jul 22, 2023
hawkw added a commit that referenced this issue Jul 22, 2023
This branch changes the `Wait` future for `maitake::sync::WaitCell` to
handle spurious polls correctly. Currently, a `wait_cell::Wait` future
assumes that if it's ever polled a second time, that means its waker was
woken. However, there might be other reasons that a stack of futures
containing a `Wait` is polled again, and the `Wait` future will
incorrectly complete immediately in that case.

This branch fixes this by replacing the `bool` field in `Wait` that's
set on first poll with an "event count" stored in the remaining
`WaitCell` state bits. Now, when a `Wait` is created, it loads the
current event count, and calls to `wake()` and `close()` increment the
event count. The `Wait` future then checks if the event count has gone
up when it's polled, rather than just checking if it's ever been polled
before. This allows the `Wait` future to determine if it is being polled
because the `WaitCell` woke it up, or if it's being polled because some
other future decided to poll it. This *also* has the side benefit of
fixing racy scenarios where the `WaitCell` is woken between when the
`Wait` future is created and when it's polled for the first time.

Fixes #449
hawkw added a commit that referenced this issue Jul 23, 2023
This branch changes the `Wait` future for `maitake::sync::WaitCell` to
handle spurious polls correctly. Currently, a `wait_cell::Wait` future
assumes that if it's ever polled a second time, that means its waker was
woken. However, there might be other reasons that a stack of futures
containing a `Wait` is polled again, and the `Wait` future will
incorrectly complete immediately in that case.

This branch fixes this by replacing the `bool` field in `Wait` that's
set on first poll with an additional bit stored in the `WaitCell`'s
state field. This is set when the cell is actually woken, and only
unset by the `Wait` future when it's polled. If the `WOKEN` bit was
set, the `Wait` future completes, and if it was unset, the future
re-registers itself. This way, the `Wait` future only completes if it
was *woken by the waitcell*, rather than on *any* poll if the task was
woken by something else.

Fixes #449
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
crate/maitake Related to the `maitake` crate kind/bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.

1 participant