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

macros: don't take ownership of futures in macros #5087

Merged
merged 9 commits into from Oct 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
28 changes: 24 additions & 4 deletions tokio/src/future/poll_fn.rs
Expand Up @@ -7,13 +7,23 @@ use std::future::Future;
use std::pin::Pin;
use std::task::{Context, Poll};

// This struct is intentionally `!Unpin` when `F` is `!Unpin`. This is to
// mitigate the issue where rust puts noalias on mutable references to the
// `PollFn` type if it is `Unpin`. If the closure has ownership of a future,
// then this "leaks" and the future is affected by noalias too, which we don't
// want.
//
// See this thread for more information:
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
//
// The fact that `PollFn` is not `Unpin` when it shouldn't be is tested in
// `tests/async_send_sync.rs`.

/// Future for the [`poll_fn`] function.
pub struct PollFn<F> {
f: F,
}

impl<F> Unpin for PollFn<F> {}

/// Creates a new future wrapping around a function returning [`Poll`].
pub fn poll_fn<T, F>(f: F) -> PollFn<F>
where
Expand All @@ -34,7 +44,17 @@ where
{
type Output = T;

fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
(self.f)(cx)
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<T> {
// Safety: We never construct a `Pin<&mut F>` anywhere, so accessing `f`
// mutably in an unpinned way is sound.
//
// This use of unsafe cannot be replaced with the pin-project macro
// because:
// * If we put `#[pin]` on the field, then it gives us a `Pin<&mut F>`,
// which we can't use to call the closure.
// * If we don't put `#[pin]` on the field, then it makes `PollFn` be
// unconditionally `Unpin`, which we also don't want.
let me = unsafe { Pin::into_inner_unchecked(self) };
(me.f)(cx)
}
}
12 changes: 11 additions & 1 deletion tokio/src/macros/join.rs
Expand Up @@ -72,8 +72,18 @@ macro_rules! join {

// Safety: nothing must be moved out of `futures`. This is to satisfy
// the requirement of `Pin::new_unchecked` called below.
//
// We can't use the `pin!` macro for this because `futures` is a tuple
// and the standard library provides no way to pin-project to the fields
// of a tuple.
let mut futures = ( $( maybe_done($e), )* );

// This assignment makes sure that the `poll_fn` closure only has a
// reference to the futures, instead of taking ownership of them. This
// mitigates the issue described in
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
Darksonn marked this conversation as resolved.
Show resolved Hide resolved
let mut futures = &mut futures;

// Each time the future created by poll_fn is polled, a different future will be polled first
// to ensure every future passed to join! gets a chance to make progress even if
// one of the futures consumes the whole budget.
Expand Down Expand Up @@ -106,7 +116,7 @@ macro_rules! join {
to_run -= 1;

// Extract the future for this branch from the tuple.
let ( $($skip,)* fut, .. ) = &mut futures;
let ( $($skip,)* fut, .. ) = &mut *futures;

// Safety: future is stored on the stack above
// and never moved.
Expand Down
12 changes: 11 additions & 1 deletion tokio/src/macros/select.rs
Expand Up @@ -460,8 +460,18 @@ macro_rules! select {
let mut output = {
// Safety: Nothing must be moved out of `futures`. This is to
// satisfy the requirement of `Pin::new_unchecked` called below.
//
// We can't use the `pin!` macro for this because `futures` is a
// tuple and the standard library provides no way to pin-project to
// the fields of a tuple.
let mut futures = ( $( $fut , )+ );

// This assignment makes sure that the `poll_fn` closure only has a
// reference to the futures, instead of taking ownership of them.
// This mitigates the issue described in
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
let mut futures = &mut futures;

$crate::macros::support::poll_fn(|cx| {
// Track if any branch returns pending. If no branch completes
// **or** returns pending, this implies that all branches are
Expand Down Expand Up @@ -497,7 +507,7 @@ macro_rules! select {

// Extract the future for this branch from the
// tuple
let ( $($skip,)* fut, .. ) = &mut futures;
let ( $($skip,)* fut, .. ) = &mut *futures;

// Safety: future is stored on the stack above
// and never moved.
Expand Down
12 changes: 11 additions & 1 deletion tokio/src/macros/try_join.rs
Expand Up @@ -118,8 +118,18 @@ macro_rules! try_join {

// Safety: nothing must be moved out of `futures`. This is to satisfy
// the requirement of `Pin::new_unchecked` called below.
//
// We can't use the `pin!` macro for this because `futures` is a tuple
// and the standard library provides no way to pin-project to the fields
// of a tuple.
let mut futures = ( $( maybe_done($e), )* );

// This assignment makes sure that the `poll_fn` closure only has a
// reference to the futures, instead of taking ownership of them. This
// mitigates the issue described in
// <https://internals.rust-lang.org/t/surprising-soundness-trouble-around-pollfn/17484>
let mut futures = &mut futures;

// Each time the future created by poll_fn is polled, a different future will be polled first
// to ensure every future passed to join! gets a chance to make progress even if
// one of the futures consumes the whole budget.
Expand Down Expand Up @@ -152,7 +162,7 @@ macro_rules! try_join {
to_run -= 1;

// Extract the future for this branch from the tuple.
let ( $($skip,)* fut, .. ) = &mut futures;
let ( $($skip,)* fut, .. ) = &mut *futures;

// Safety: future is stored on the stack above
// and never moved.
Expand Down
15 changes: 15 additions & 0 deletions tokio/tests/async_send_sync.rs
Expand Up @@ -136,6 +136,21 @@ macro_rules! cfg_not_wasi {
}
}

// Manualy re-implementation of `async_assert_fn` for `poll_fn`. The macro
// doesn't work for this particular case because constructing the closure
// is too complicated.
const _: fn() = || {
let pinned = std::marker::PhantomPinned;
let f = tokio::macros::support::poll_fn(move |_| {
// Use `pinned` to take ownership of it.
let _ = &pinned;
std::task::Poll::Pending::<()>
});
require_send(&f);
require_sync(&f);
AmbiguousIfUnpin::some_item(&f);
};

cfg_not_wasi! {
mod fs {
use super::*;
Expand Down
6 changes: 4 additions & 2 deletions tokio/tests/macros_join.rs
Expand Up @@ -3,6 +3,7 @@
use std::sync::Arc;

#[cfg(tokio_wasm_not_wasi)]
#[cfg(target_pointer_width = "64")]
use wasm_bindgen_test::wasm_bindgen_test as test;
#[cfg(tokio_wasm_not_wasi)]
use wasm_bindgen_test::wasm_bindgen_test as maybe_tokio_test;
Expand Down Expand Up @@ -64,6 +65,7 @@ async fn two_await() {
}

#[test]
#[cfg(target_pointer_width = "64")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we be making similar assertions about the size of the futures on 32-bit platforms as well? or, calculate the expected size by adding either 8 or 4 bytes based on the value of target_pointer_width?

fn join_size() {
use futures::future;
use std::mem;
Expand All @@ -72,14 +74,14 @@ fn join_size() {
let ready = future::ready(0i32);
tokio::join!(ready)
};
assert_eq!(mem::size_of_val(&fut), 20);
assert_eq!(mem::size_of_val(&fut), 32);

let fut = async {
let ready1 = future::ready(0i32);
let ready2 = future::ready(0i32);
tokio::join!(ready1, ready2)
};
assert_eq!(mem::size_of_val(&fut), 32);
assert_eq!(mem::size_of_val(&fut), 48);
}

async fn non_cooperative_task(permits: Arc<Semaphore>) -> usize {
Expand Down
7 changes: 4 additions & 3 deletions tokio/tests/macros_select.rs
Expand Up @@ -207,6 +207,7 @@ async fn nested() {
}

#[maybe_tokio_test]
#[cfg(target_pointer_width = "64")]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

as with join, could we make a separate set of assertions about the future size on 32-bit platforms?

async fn struct_size() {
use futures::future;
use std::mem;
Expand All @@ -219,7 +220,7 @@ async fn struct_size() {
}
};

assert!(mem::size_of_val(&fut) <= 32);
assert_eq!(mem::size_of_val(&fut), 40);

let fut = async {
let ready1 = future::ready(0i32);
Expand All @@ -231,7 +232,7 @@ async fn struct_size() {
}
};

assert!(mem::size_of_val(&fut) <= 40);
assert_eq!(mem::size_of_val(&fut), 48);

let fut = async {
let ready1 = future::ready(0i32);
Expand All @@ -245,7 +246,7 @@ async fn struct_size() {
}
};

assert!(mem::size_of_val(&fut) <= 48);
assert_eq!(mem::size_of_val(&fut), 56);
}

#[maybe_tokio_test]
Expand Down
5 changes: 3 additions & 2 deletions tokio/tests/macros_try_join.rs
Expand Up @@ -88,6 +88,7 @@ async fn err_abort_early() {
}

#[test]
#[cfg(target_pointer_width = "64")]
fn join_size() {
use futures::future;
use std::mem;
Expand All @@ -96,14 +97,14 @@ fn join_size() {
let ready = future::ready(ok(0i32));
tokio::try_join!(ready)
};
assert_eq!(mem::size_of_val(&fut), 20);
assert_eq!(mem::size_of_val(&fut), 32);

let fut = async {
let ready1 = future::ready(ok(0i32));
let ready2 = future::ready(ok(0i32));
tokio::try_join!(ready1, ready2)
};
assert_eq!(mem::size_of_val(&fut), 32);
assert_eq!(mem::size_of_val(&fut), 48);
}

fn ok<T>(val: T) -> Result<T, ()> {
Expand Down