diff --git a/.clippy.toml b/.clippy.toml deleted file mode 100644 index 992016c29a..0000000000 --- a/.clippy.toml +++ /dev/null @@ -1 +0,0 @@ -msrv = "1.36" diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000000..79a8d7d53f --- /dev/null +++ b/.editorconfig @@ -0,0 +1,6 @@ +[*.rs] +end_of_line = lf +insert_final_newline = true +charset = utf-8 +indent_style = space +indent_size = 4 diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index 2fdc28ffbd..0000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1 +0,0 @@ -* @taiki-e diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cb62875f83..ae34fc33a8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,5 +1,8 @@ name: CI +permissions: + contents: read + on: pull_request: push: @@ -10,80 +13,110 @@ on: - cron: '0 1 * * *' env: - RUSTFLAGS: -D warnings + CARGO_INCREMENTAL: 0 + CARGO_NET_RETRY: 10 + CARGO_TERM_COLOR: always RUST_BACKTRACE: 1 + RUSTFLAGS: -D warnings + RUSTUP_MAX_RETRIES: 10 defaults: run: shell: bash +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + jobs: test: - name: cargo +${{ matrix.rust }} test (${{ matrix.os }}) + name: cargo test strategy: + fail-fast: false matrix: - rust: - - nightly - os: - - ubuntu-latest - - macos-latest - - windows-latest + include: + - os: ubuntu-latest + - os: macos-latest + - os: windows-latest + - os: ubuntu-latest + target: aarch64-unknown-linux-gnu + - os: ubuntu-latest + target: armv7-unknown-linux-gnueabihf + - os: ubuntu-latest + target: armv5te-unknown-linux-gnueabi + - os: ubuntu-latest + target: i686-unknown-linux-gnu runs-on: ${{ matrix.os }} + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust # --no-self-update is necessary because the windows environment cannot self-update rustup.exe. - run: rustup update ${{ matrix.rust }} --no-self-update && rustup default ${{ matrix.rust }} - - run: cargo test --workspace --all-features - - run: cargo test --workspace --all-features --release + run: rustup update nightly --no-self-update && rustup default nightly + - uses: taiki-e/setup-cross-toolchain-action@v1 + with: + target: ${{ matrix.target }} + if: matrix.target != '' + - run: cargo test --workspace --all-features $DOCTEST_XCOMPILE + - run: cargo test --workspace --all-features --release $DOCTEST_XCOMPILE + # TODO: https://github.com/rust-lang/futures-rs/issues/2451 + if: matrix.target != 'aarch64-unknown-linux-gnu' core-msrv: - name: cargo +${{ matrix.rust }} build (futures-{core, io, sink, task, channel}) + name: cargo +${{ matrix.rust }} build (futures-{core, io, sink}) strategy: matrix: rust: - # This is the minimum Rust version supported by futures-core, futures-io, futures-sink, futures-task, futures-channel. - # When updating this, the reminder to update the minimum required version of `async-await` feature in README.md. - - 1.36.0 + # This is the minimum Rust version supported by futures-core, futures-io, futures-sink. + # When updating this, the reminder to update the minimum required version in README.md and Cargo.toml. + - '1.36' runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} # cargo does not support for --features/--no-default-features with workspace, so use cargo-hack instead. # Refs: cargo#3620, cargo#4106, cargo#4463, cargo#4753, cargo#5015, cargo#5364, cargo#6195 - - run: cargo install cargo-hack + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack # remove dev-dependencies to avoid https://github.com/rust-lang/cargo/issues/4866 - run: cargo hack --remove-dev-deps --workspace # Check no-default-features - run: | cargo hack build --workspace --ignore-private --no-default-features \ - --exclude futures --exclude futures-util --exclude futures-macro --exclude futures-executor --exclude futures-test + --exclude futures --exclude futures-util --exclude futures-task --exclude futures-macro --exclude futures-executor --exclude futures-channel --exclude futures-test # Check alloc feature - run: | cargo hack build --workspace --ignore-private --no-default-features --features alloc --ignore-unknown-features \ - --exclude futures --exclude futures-util --exclude futures-macro --exclude futures-executor --exclude futures-test + --exclude futures --exclude futures-util --exclude futures-task --exclude futures-macro --exclude futures-executor --exclude futures-channel --exclude futures-test # Check std feature - run: | cargo hack build --workspace --ignore-private --no-default-features --features std \ - --exclude futures --exclude futures-util --exclude futures-macro --exclude futures-executor --exclude futures-test + --exclude futures --exclude futures-util --exclude futures-task --exclude futures-macro --exclude futures-executor --exclude futures-channel --exclude futures-test util-msrv: name: cargo +${{ matrix.rust }} build + env: + CARGO_NET_GIT_FETCH_WITH_CLI: true strategy: matrix: rust: - # This is the minimum Rust version supported by futures, futures-util, futures-macro, futures-executor, futures-test. - # When updating this, the reminder to update the minimum required version of `async-await` feature in README.md. - - 1.37.0 + # This is the minimum Rust version supported by futures, futures-util, futures-task, futures-macro, futures-executor, futures-channel, futures-test. + # When updating this, the reminder to update the minimum required version in README.md and Cargo.toml. + - '1.56' runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - - run: cargo install cargo-hack + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack # remove dev-dependencies to avoid https://github.com/rust-lang/cargo/issues/4866 - run: cargo hack --remove-dev-deps --workspace + # Check default features + - run: cargo hack build --workspace --ignore-private # Check no-default-features - run: cargo hack build --workspace --exclude futures-test --ignore-private --no-default-features # Check alloc feature @@ -91,115 +124,92 @@ jobs: # Check std feature - run: cargo hack build --workspace --ignore-private --no-default-features --features std --ignore-unknown-features # Check compat feature (futures, futures-util) - - run: cargo hack build -p futures -p futures-util --no-default-features --features std,io-compat + # Exclude io-compat feature because the MSRV when it is enabled depends on the MSRV of tokio 0.1. + - run: cargo hack build -p futures -p futures-util --no-default-features --features std,compat # Check thread-pool feature (futures, futures-executor) - run: cargo hack build -p futures -p futures-executor --no-default-features --features std,thread-pool - async-await-msrv: - name: cargo +${{ matrix.rust }} build - strategy: - matrix: - rust: - # This is the minimum Rust version supported by `async-await` feature. - # When updating this, the reminder to update the minimum required version of `async-await` feature in README.md. - - 1.39.0 - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install Rust - run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - - run: cargo install cargo-hack - - run: cargo hack build --workspace --no-dev-deps - build: name: cargo +${{ matrix.rust }} build strategy: + fail-fast: false matrix: rust: - stable - beta - nightly runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update ${{ matrix.rust }} && rustup default ${{ matrix.rust }} - - run: cargo install cargo-hack + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack - run: cargo hack build --workspace --no-dev-deps - run: cargo build --tests --features default,thread-pool,io-compat --manifest-path futures/Cargo.toml minimal-versions: - name: cargo build -Z minimal-versions + name: cargo minimal-versions build runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update nightly && rustup default nightly - - run: cargo install cargo-hack - # remove dev-dependencies to avoid https://github.com/rust-lang/cargo/issues/4866 - - run: cargo hack --remove-dev-deps --workspace - - run: cargo update -Z minimal-versions - - run: cargo build --workspace --all-features + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack + - name: Install cargo-minimal-versions + uses: taiki-e/install-action@cargo-minimal-versions + - run: cargo minimal-versions build --workspace --ignore-private --all-features - thumbv6m: - name: cargo build --target thumbv6m-none-eabi + no-std: + name: cargo build --target ${{ matrix.target }} + strategy: + fail-fast: false + matrix: + # thumbv7m-none-eabi supports atomic CAS. + # thumbv6m-none-eabi supports atomic, but not atomic CAS. + target: + - thumbv7m-none-eabi + - thumbv6m-none-eabi runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update nightly && rustup default nightly - - run: rustup target add thumbv6m-none-eabi - - run: cargo install cargo-hack + - run: rustup target add ${{ matrix.target }} + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack # remove dev-dependencies to avoid https://github.com/rust-lang/cargo/issues/4866 - run: cargo hack --remove-dev-deps --workspace - run: | - cargo build --manifest-path futures/Cargo.toml \ - --target thumbv6m-none-eabi \ - --no-default-features \ - --features unstable,cfg-target-has-atomic + cargo hack build --manifest-path futures/tests/no-std/Cargo.toml \ + --each-feature --optional-deps \ + --target ${{ matrix.target }} - run: | - cargo build --manifest-path futures/Cargo.toml \ - --target thumbv6m-none-eabi \ + cargo hack build --workspace --ignore-private \ + --exclude futures-test --exclude futures-macro \ --no-default-features \ - --features alloc,unstable,cfg-target-has-atomic + --target ${{ matrix.target }} - run: | - cargo build --manifest-path futures/Cargo.toml \ - --target thumbv6m-none-eabi \ - --no-default-features \ - --features async-await,unstable,cfg-target-has-atomic - - thumbv7m: - name: cargo build --target thumbv7m-none-eabi - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - name: Install Rust - run: rustup update nightly && rustup default nightly - - run: rustup target add thumbv7m-none-eabi - - run: cargo install cargo-hack - # remove dev-dependencies to avoid https://github.com/rust-lang/cargo/issues/4866 - - run: cargo hack --remove-dev-deps --workspace - - run: | - cargo build --manifest-path futures/Cargo.toml \ - --target thumbv7m-none-eabi \ - --no-default-features \ - --features unstable,cfg-target-has-atomic - - run: | - cargo build --manifest-path futures/Cargo.toml \ - --target thumbv7m-none-eabi \ - --no-default-features \ - --features alloc + cargo hack build --workspace --ignore-private \ + --exclude futures-test --exclude futures-macro \ + --no-default-features --features alloc --ignore-unknown-features \ + --target ${{ matrix.target }} - run: | - cargo build --manifest-path futures/Cargo.toml \ - --target thumbv7m-none-eabi \ - --no-default-features \ - --features async-await + cargo hack build --workspace --ignore-private \ + --exclude futures-test --exclude futures-macro \ + --no-default-features --features async-await,alloc --ignore-unknown-features \ + --target ${{ matrix.target }} bench: name: cargo bench runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update nightly && rustup default nightly - run: cargo bench --workspace @@ -208,11 +218,13 @@ jobs: features: name: cargo hack check --feature-powerset runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update nightly && rustup default nightly - - run: cargo install cargo-hack + - name: Install cargo-hack + uses: taiki-e/install-action@cargo-hack # Check each specified feature works properly # * `--feature-powerset` - run for the feature powerset of the package # * `--depth 2` - limit the max number of simultaneous feature flags of `--feature-powerset` @@ -226,45 +238,78 @@ jobs: --workspace --exclude futures-test \ --features unstable --ignore-unknown-features + miri: + name: cargo miri test + runs-on: ubuntu-latest + timeout-minutes: 60 + steps: + - uses: taiki-e/checkout-action@v1 + - name: Install Rust + run: rustup toolchain install nightly --component miri && rustup default nightly + - run: cargo miri test --workspace --all-features -- --skip panic_on_drop_fut + env: + MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-symbolic-alignment-check -Zmiri-disable-isolation + RUSTFLAGS: ${{ env.RUSTFLAGS }} -Z randomize-layout + # This test is expected to leak. + - run: cargo miri test --workspace --all-features --test stream_futures_unordered -- panic_on_drop_fut + env: + MIRIFLAGS: -Zmiri-strict-provenance -Zmiri-symbolic-alignment-check -Zmiri-disable-isolation -Zmiri-ignore-leaks + RUSTDOCFLAGS: ${{ env.RUSTDOCFLAGS }} -Z randomize-layout + RUSTFLAGS: ${{ env.RUSTFLAGS }} -Z randomize-layout + san: name: cargo test -Z sanitizer=${{ matrix.sanitizer }} strategy: + fail-fast: false matrix: sanitizer: - address - memory - thread runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust - run: rustup update nightly && rustup default nightly - - run: rustup component add rust-src - - run: cargo -Z build-std test --workspace --all-features --target x86_64-unknown-linux-gnu --lib --tests + run: rustup toolchain install nightly --component rust-src && rustup default nightly + # https://github.com/google/sanitizers/issues/1716 / https://github.com/actions/runner-images/issues/9491 + - run: sudo sysctl vm.mmap_rnd_bits=28 + # Exclude futures-macro to work around upstream bug since nightly-2024-10-06. + - run: cargo -Z build-std test --workspace --all-features --target x86_64-unknown-linux-gnu --lib --tests --exclude futures-macro -- --skip panic_on_drop_fut env: # TODO: Once `cfg(sanitize = "..")` is stable, replace # `cfg(futures_sanitizer)` with `cfg(sanitize = "..")` and remove # `--cfg futures_sanitizer`. RUSTFLAGS: -D warnings -Z sanitizer=${{ matrix.sanitizer }} --cfg futures_sanitizer - clippy: - name: cargo clippy + # This branch no longer actively developed. Most commits to this + # branch are backporting and should not be blocked by clippy. + # clippy: + # name: cargo clippy + # runs-on: ubuntu-latest + # timeout-minutes: 60 + # steps: + # - uses: taiki-e/checkout-action@v1 + # - name: Install Rust + # run: rustup toolchain install nightly --component clippy && rustup default nightly + # - run: cargo clippy --workspace --all-features --all-targets + + fmt: + name: cargo fmt runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 - - name: Install Rust and Clippy - run: | - toolchain=nightly-$(curl -sSf https://rust-lang.github.io/rustup-components-history/x86_64-unknown-linux-gnu/clippy) - rustup set profile minimal - rustup default "$toolchain" - rustup component add clippy - - run: cargo clippy --workspace --all-features --all-targets + - uses: taiki-e/checkout-action@v1 + - name: Install Rust + run: rustup update stable + - run: cargo fmt --all -- --check docs: name: cargo doc runs-on: ubuntu-latest + timeout-minutes: 60 steps: - - uses: actions/checkout@v2 + - uses: taiki-e/checkout-action@v1 - name: Install Rust run: rustup update nightly && rustup default nightly - run: RUSTDOCFLAGS="-D warnings --cfg docsrs" cargo doc --workspace --no-deps --all-features diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000000..d56fcdddc3 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,19 @@ +name: Release + +on: + push: + tags: + - '[0-9]+.*' + +jobs: + create-release: + if: github.repository_owner == 'rust-lang' + runs-on: ubuntu-latest + steps: + - uses: taiki-e/checkout-action@v1 + - uses: taiki-e/create-gh-release-action@v1 + with: + changelog: CHANGELOG.md + branch: 'master|[0-9]+\.[0-9]+' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.rustfmt.toml b/.rustfmt.toml index c7ad93bafe..2a79d9274a 100644 --- a/.rustfmt.toml +++ b/.rustfmt.toml @@ -1 +1,2 @@ -disable_all_formatting = true +use_small_heuristics = "Max" +edition = "2018" diff --git a/CHANGELOG.md b/CHANGELOG.md index c9f5679f90..992a457a1c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,20 +1,178 @@ +# 0.3.31 - 2024-10-05 + +* Fix use after free of task in `FuturesUnordered` when dropped future panics (#2886) +* Fix soundness bug in `task::waker_ref` (#2830) + This is a breaking change but allowed because it is soundness bug fix. +* Fix bugs in `AsyncBufRead::read_line` and `AsyncBufReadExt::lines` (#2884) +* Fix parsing issue in `select!`/`select_biased!` (#2832) + This is technically a breaking change as it will now reject a very odd undocumented syntax that was previously accidentally accepted. +* Work around issue due to upstream `Waker::will_wake` change (#2865) +* Add `stream::Iter::{get_ref,get_mut,into_inner}` (#2875) +* Add `future::AlwaysReady` (#2825) +* Relax trait bound on non-constructor methods of `io::{BufReader,BufWriter}` (#2848) + +# 0.3.30 - 2023-12-24 + +* Add `{BiLock,SplitStream,SplitSink,ReadHalf,WriteHalf}::is_pair_of` (#2797) +* Fix panic in `FuturesUnordered::clear` (#2809) +* Fix panic in `AsyncBufReadExt::fill_buf` (#2801, #2812) +* Improve support for targets without atomic CAS (#2811) +* Remove build scripts (#2811) + +# 0.3.29 - 2023-10-26 + +* Add `TryStreamExt::try_ready_chunks` (#2757) +* Add `TryStreamExt::{try_all,try_any}` (#2783) +* Add `UnboundedSender::{len,is_empty}` (#2750) +* Fix `Sync` impl of `FuturesUnordered` (#2788) +* Fix infinite loop caused by invalid UTF-8 bytes (#2785) +* Fix build error with -Z minimal-versions (#2761) + +# 0.3.28 - 2023-03-30 + +* Update to syn 2. This raises MSRV of utility crates to 1.56. (#2730, #2733) +* Fix bug in `FlattenUnordered` (#2726, #2728) + +# 0.3.27 - 2023-03-11 + +* Add `TryFlattenUnordered` (#2577, #2590, #2606, #2607) +* Add `AbortHandle::is_aborted` (#2710) +* Add `AbortRegistration::handle` (#2712) +* Make `BiLock` strict-provenance compatible (#2716) + +# 0.3.26 - 2023-01-30 + +* Add `Either::as_pin_mut` and `Either::as_pin_ref` (#2691) +* Add `Shared::ptr_eq` and `Shared::ptr_hash` (#2691) +* Implement `FusedStream` for `Buffered` (#2676) +* Implement `FusedStream` for all streams in `ReadyChunks` (#2693) +* Fix bug in `FuturesOrdered::push_front` (#2664) +* Remove `Fut::Output: Clone` bounds from some `Shared` methods (#2662) +* Remove `T: Debug` bounds from `Debug` implementations of `mpsc` and `oneshot` types (#2666, #2667) + +# 0.3.25 - 2022-10-20 + +* Fix soundness issue in `join!` and `try_join!` macros (#2649) +* Implement `Clone` for `sink::Drain` (#2650) + +# 0.3.24 - 2022-08-29 + +* Fix incorrect termination of `select_with_strategy` streams (#2635) + +# 0.3.23 - 2022-08-14 + +* Work around MSRV increase due to a cargo bug. + +# 0.3.22 - 2022-08-14 + +* Fix `Sync` impl of `BiLockGuard` (#2570) +* Fix partial iteration in `FuturesUnordered` (#2574) +* Fix false detection of inner panics in `Shared` (#2576) +* Add `Mutex::lock_owned` and `Mutex::try_lock_owned` (#2571) +* Add `io::copy_buf_abortable` (#2507) +* Remove `Unpin` bound from `TryStreamExt::into_async_read` (#2599) +* Make `run_until_stalled` handle self-waking futures (#2593) +* Use `FuturesOrdered` in `try_join_all` (#2556) +* Fix orderings in `LocalPool` waker (#2608) +* Fix `stream::Chunk` adapters size hints (#2611) +* Add `push_front` and `push_back` to `FuturesOrdered` (#2591) +* Deprecate `FuturesOrdered::push` in favor of `FuturesOrdered::push_back` (#2591) +* Performance improvements (#2583, #2626) +* Documentation improvements (#2579, #2604, #2613) + +# 0.3.21 - 2022-02-06 + +* Fix potential data race in `FlattenUnordered` that introduced in 0.3.20 (#2566) + +# 0.3.20 - 2022-02-06 + +NOTE: This release has been yanked due to a bug fixed in 0.3.21. + +* Fix stacked borrows violations when `-Zmiri-tag-raw-pointers` is enabled. This raises MSRV of `futures-task` to 1.45. (#2548, #2550) +* Change `FuturesUnordered` to respect yielding from future (#2551) +* Add `StreamExt::{flatten_unordered, flat_map_unordered}` (#2083) + +# 0.3.19 - 2021-12-18 + +* Remove unstable `read-initializer` feature (#2534) +* Fix panic in `FuturesUnordered` (#2535) +* Fix compatibility issue with `FuturesUnordered` and tokio's cooperative scheduling (#2527) +* Add `StreamExt::count` (#2495) + +# 0.3.18 - 2021-11-23 + +NOTE: This release has been yanked. See #2529 for details. + +* Fix unusable `Sink` implementation on `stream::Scan` (#2499) +* Make `task::noop_waker_ref` available without `std` feature (#2505) +* Add async `LineWriter` (#2477) +* Remove dependency on `proc-macro-hack`. This raises MSRV of utility crates to 1.45. (#2520) + +# 0.3.17 - 2021-08-30 + +* Use `FuturesOrdered` in `join_all` (#2412) +* Add `{future, stream}::poll_immediate` (#2452) +* Add `stream_select!` macro (#2262) +* Implement `Default` for `OptionFuture` (#2471) +* Add `Peekable::{peek_mut, poll_peek_mut}` (#2488) +* Add `BufReader::seek_relative` (#2489) + +# 0.3.16 - 2021-07-23 + +* Add `TryStreamExt::try_chunks` (#2438) +* Add `StreamExt::{all, any}` (#2460) +* Add `stream::select_with_strategy` (#2450) +* Update to new `io_slice_advance` interface (#2454) + +# 0.3.15 - 2021-05-11 + +* Use `#[proc_macro]` at Rust 1.45+ to fix an issue where proc macros don't work with rust-analyzer (#2407) +* Support targets that do not have atomic CAS on stable Rust (#2400) +* futures-test: Add async `#[test]` function attribute (#2409) +* Add `stream::abortable` (#2410) +* Add `FuturesUnordered::clear` (#2415) +* Implement `IntoIterator` for `FuturesUnordered` (#2423) +* Implement `Send` and `Sync` for `FuturesUnordered` iterators (#2416) +* Make `FuturesUnordered::iter_pin_ref` public (#2423) +* Add `SelectAll::clear` (#2430) +* Add `SelectAll::{iter, iter_mut}` (#2428) +* Implement `IntoIterator` for `SelectAll` (#2428) +* Implement `Clone` for `WeakShared` (#2396) + +# 0.3.14 - 2021-04-10 + +* Add `future::SelectAll::into_inner` (#2363) +* Allow calling `UnboundedReceiver::try_next` after `None` (#2369) +* Reexport non-Ext traits from the root of `futures_util` (#2377) +* Add `AsyncSeekExt::stream_position` (#2380) +* Add `stream::Peekable::{next_if, next_if_eq}` (#2379) + # 0.3.13 - 2021-02-23 -- Mitigated starvation issues in `FuturesUnordered` (#2333) -- Fixed race with dropping `mpsc::Receiver` (#2304) -- Added `Shared::{strong_count, weak_count}` (#2346) -- Added `no_std` support for `task::noop_waker_ref` (#2332) -- Implemented `Stream::size_hint` for `Either` (#2325) + +* Mitigated starvation issues in `FuturesUnordered` (#2333) +* Fixed race with dropping `mpsc::Receiver` (#2304) +* Added `Shared::{strong_count, weak_count}` (#2346) +* Added `no_std` support for `task::noop_waker_ref` (#2332) +* Implemented `Stream::size_hint` for `Either` (#2325) # 0.3.12 - 2021-01-15 + * Fixed `Unpin` impl of `future::{MaybeDone, TryMaybeDone}` where trait bounds were accidentally added in 0.3.9. (#2317) # 0.3.11 - 2021-01-14 + * Fixed heap buffer overflow in `AsyncReadExt::{read_to_end, read_to_string}` (#2314) # 0.3.10 - 2021-01-13 + +NOTE: This release has been yanked. See #2310 for details. + * Fixed type-inference in `sink::unfold` by specifying more of its types (breaking change -- see #2311) # 0.3.9 - 2021-01-08 + +NOTE: This release has been yanked. See #2310 for details. + * Significantly improved compile time when `async-await` crate feature is disabled (#2273) * Added `stream::repeat_with` (#2279) * Added `StreamExt::unzip` (#2263) @@ -25,6 +183,9 @@ * Re-exported `MapOkOrElse`, `MapInto`, `OkInto`, `TryFlatten`, `WriteAllVectored` (#2275) # 0.3.8 - 2020-11-04 + +NOTE: This release has been yanked. See #2310 for details. + * Switched proc-macros to use native `#[proc_macro]` at Rust 1.45+ (#2243) * Added `WeakShared` (#2169) * Added `TryStreamExt::try_buffered` (#2245) @@ -33,11 +194,17 @@ * Fixed panic in some `TryStreamExt` combinators (#2250) # 0.3.7 - 2020-10-23 + +NOTE: This release has been yanked. See #2310 for details. + * Fixed unsoundness in `MappedMutexGuard` (#2240) * Re-exported `TakeUntil` (#2235) * futures-test: Prevent double panic in `panic_waker` (#2236) # 0.3.6 - 2020-10-06 + +NOTE: This release has been yanked. See #2310 for details. + * Fixed UB due to missing 'static on `task::waker` (#2206) * Added `AsyncBufReadExt::fill_buf` (#2225) * Added `TryStreamExt::try_take_while` (#2212) @@ -51,6 +218,9 @@ * futures-test: Implemented more traits for `AssertUnmoved` (#2208) # 0.3.5 - 2020-05-08 + +NOTE: This release has been yanked. See #2310 for details. + * Added `StreamExt::flat_map`. * Added `StreamExt::ready_chunks`. * Added `*_unpin` methods to `SinkExt`. @@ -68,12 +238,21 @@ * Removed and replaced a large amount of internal `unsafe`. # 0.3.4 - 2020-02-06 + +NOTE: This release has been yanked. See #2310 for details. + * Fixed missing `Drop` for `UnboundedReceiver` (#2064) # 0.3.3 - 2020-02-04 + +NOTE: This release has been yanked. See #2310 for details. + * Fixed compatibility issue with pinned facade (#2062) # 0.3.2 - 2020-02-03 + +NOTE: This release has been yanked. See #2310 for details. + * Improved buffering performance of `SplitSink` (#1969) * Added `select_biased!` macro (#1976) * Added `hash_receiver` method to mpsc channel (#1962) @@ -81,7 +260,7 @@ * Fixed bug with zero-size buffers in vectored IO (#1998) * `AtomicWaker::new()` is now `const fn` (#2007) * Fixed bug between threadpool and user park/unparking (#2010) -* Added `stream::Peakable::peek` (#2021) +* Added `stream::Peekable::peek` (#2021) * Added `StreamExt::scan` (#2044) * Added impl of `AsyncRead`/`Write` for `BufReader`/`Writer` (#2033) * Added impl of `Spawn` and `LocalSpawn` for `Arc` (#2039) @@ -91,10 +270,16 @@ * Mitigated starvation issues in `FuturesUnordered` (#2049) * Added `TryFutureExt::map_ok_or_else` (#2058) -# 0.3.1 - 2019-11-7 -* Fix signature of `LocalSpawn` trait (breaking change -- see #1959) +# 0.3.1 - 2019-11-07 + +NOTE: This release has been yanked. See #2310 for details. + +* Fix signature of `SpawnExt` and `LocalSpawnExt` trait (breaking change -- see #1959) + +# 0.3.0 - 2019-11-05 + +NOTE: This release has been yanked. See #2310 for details. -# 0.3.0 - 2019-11-5 * Stable release along with stable async/await! * Added async/await to default features (#1953) * Changed `Spawn` trait and `FuturesUnordered::push` to take `&self` (#1950) @@ -115,7 +300,8 @@ * Added some missing `Clone` implementations * Documentation fixes -# 0.3.0-alpha.19 - 2019-9-25 +# 0.3.0-alpha.19 - 2019-09-25 + * Stabilized the `async-await` feature (#1816) * Made `async-await` feature no longer require `std` feature (#1815) * Updated `proc-macro2`, `syn`, and `quote` to 1.0 (#1798) @@ -138,7 +324,8 @@ * Removed dependency on `rand` by using our own PRNG (#1837) * Removed `futures-core` dependency from `futures-sink` (#1832) -# 0.3.0-alpha.18 - 2019-8-9 +# 0.3.0-alpha.18 - 2019-08-09 + * Rewrote `join!` and `try_join!` as procedural macros to allow passing expressions (#1783) * Banned manual implementation of `TryFuture` and `TryStream` for forward compatibility. See #1776 for more details. (#1777) * Changed `AsyncReadExt::read_to_end` to return the total number of bytes read (#1721) @@ -157,7 +344,8 @@ * Added `TryStreamExt::try_flatten` (#1731) * Added `FutureExt::now_or_never` (#1747) -# 0.3.0-alpha.17 - 2019-7-3 +# 0.3.0-alpha.17 - 2019-07-03 + * Removed `try_ready!` macro in favor of `ready!(..)?`. (#1602) * Removed `io::Window::{set_start, set_end}` in favor of `io::Window::set`. (#1667) * Re-exported `pin_utils::pin_mut!` macro. (#1686) @@ -190,7 +378,8 @@ * Renamed `Sink::SinkError` to `Sink::Error`. * Made a number of dependencies of `futures-util` optional. -# 0.3.0-alpha.16 - 2019-5-10 +# 0.3.0-alpha.16 - 2019-05-10 + * Updated to new nightly `async_await`. * Changed `AsyncRead::poll_vectored_read` and `AsyncWrite::poll_vectored_write` to use stabilized `std::io::{IoSlice, IoSliceMut}` instead of `iovec::IoVec`, and renamed to @@ -201,7 +390,8 @@ * Added `AsyncBufReadExt::{read_line, lines}`. * Added `io::BufReader`. -# 0.3.0-alpha.15 - 2019-4-26 +# 0.3.0-alpha.15 - 2019-04-26 + * Updated to stabilized `futures_api`. * Removed `StreamObj`, cautioned against usage of `FutureObj`. * Changed `StreamExt::select` to a function. @@ -214,10 +404,11 @@ * Added functions to partially progress a local pool. * Changed to use our own `Either` type rather than the one from the `either` crate. -# 0.3.0-alpha.14 - 2019-4-15 +# 0.3.0-alpha.14 - 2019-04-15 + * Updated to new nightly `futures_api`. * Changed `Forward` combinator to drop sink after completion, and allow `!Unpin` `Sink`s. -* Added 0.1 <-> 0.3 compatability shim for `Sink`s. +* Added 0.1 <-> 0.3 compatibility shim for `Sink`s. * Changed `Sink::Item` to a generic parameter `Sink`, allowing `Sink`s to accept multiple different types, including types containing references. * Changed `AsyncRead` and `AsyncWrite` to take `Pin<&mut Self>` rather than `&mut self`. @@ -225,7 +416,8 @@ * Changed `join` and `try_join` combinators to functions. * Fixed propagation of `cfg-target-has-atomic` feature. -# 0.3.0-alpha.13 - 2019-2-20 +# 0.3.0-alpha.13 - 2019-02-20 + * Updated to new nightly with stabilization candidate API. * Removed `LocalWaker`. * Added `#[must_use]` to `Stream` and `Sink` traits. @@ -235,7 +427,8 @@ * Removed `TokioDefaultSpawner` and `tokio-compat`. * Moved intra-crate dependencies to exact versions. -# 0.3.0-alpha.12 - 2019-1-14 +# 0.3.0-alpha.12 - 2019-01-14 + * Updated to new nightly with a modification to `Pin::set`. * Expose `AssertUnmoved` and `PendingOnce`. * Prevent double-panic in `AssertUnmoved`. @@ -243,6 +436,7 @@ * Implement `Default` for `Mutex` and `SelectAll`. # 0.3.0-alpha.11 - 2018-12-27 + * Updated to newly stabilized versions of the `pin` and `arbitrary_self_types` features. * Re-added `select_all` for streams. * Added `TryStream::into_async_read` for converting from a stream of bytes into @@ -252,6 +446,7 @@ * Exposed `join_all` from the facade # 0.3.0-alpha.10 - 2018-11-27 + * Revamped `select!` macro * Added `select_next_some` method for getting only the `Some` elements of a stream from `select!` * Added `futures::lock::Mutex` for async-aware synchronization. @@ -264,27 +459,33 @@ * Added `try_concat` # 0.3.0-alpha.9 - 2018-10-18 + * Fixed in response to new nightly handling of 2018 edition + `#![no_std]` # 0.3.0-alpha.8 - 2018-10-16 + * Fixed stack overflow in 0.1 compatibility layer * Added AsyncRead / AsyncWrite compatibility layer * Added Spawn -> 0.1 Executor compatibility * Made 0.1 futures usable on 0.3 executors without an additional global `Task`, accomplished by wrapping 0.1 futures in an 0.1 `Spawn` when using them as 0.3 futures. -* Cleanups and improvments to the `AtomicWaker` implementation. +* Cleanups and improvements to the `AtomicWaker` implementation. # 0.3.0-alpha.7 - 2018-10-01 + * Update to new nightly which removes `Spawn` from `task::Context` and replaces `Context` with `LocalWaker`. * Add `Spawn` and `LocalSpawn` traits and `FutureObj` and `LocalFutureObj` types to `futures-core`. # 0.3.0-alpha.6 - 2018-09-10 + * Replace usage of `crate` visibility with `pub(crate)` now that `crate` visibility is no longer included in the 2018 edition * Remove newly-stabilized "edition" feature in Cargo.toml files # 0.3.0-alpha.5 - 2018-09-03 + * Revert usage of cargo crate renaming feature # 0.3.0-alpha.4 - 2018-09-02 + **Note: This release does not work, use `0.3.0-alpha.5` instead** * `future::ok` and `future:err` to create result wrapping futures (similar to `future::ready`) @@ -292,13 +493,14 @@ * `StreamExt::boxed` combinator * Unsoundness fix for `FuturesUnordered` * `StreamObj` (similar to `FutureObj`) -* Code examples for compatiblity layer functions -* Use cargo create renaming feature to import `futures@0.1` for compatiblily layer +* Code examples for compatibility layer functions +* Use cargo create renaming feature to import `futures@0.1` for compatibility layer * Import pinning APIs from `core::pin` * Run Clippy in CI only when it is available # 0.3.0-alpha.3 - 2018-08-15 -* Compatibilty with newest nightly + +* Compatibility with newest nightly * Futures 0.1 compatibility layer including Tokio compatibility * Added `spawn!` and `spawn_with_handle!` macros * Added `SpawnExt` methods `spawn` and `spawn_with_handle` @@ -316,16 +518,17 @@ * Doc improvements to `StreamExt::select` # 0.3.0-alpha.2 - 2018-07-30 + * The changelog is back! -* Compatiblity with futures API in latest nightly +* Compatibility with futures API in latest nightly * Code examples and doc improvements - - IO: Methods of traits `AsyncReadExt`, `AsyncWriteExt` - - Future: - - Methods of trait `TryFutureExt` - - Free functions `empty`, `lazy`, `maybe_done`, `poll_fn` and `ready` - - Type `FutureOption` - - Macros `join!`, `select!` and `pending!` - - Stream: Methods of trait `TryStreamExt` + * IO: Methods of traits `AsyncReadExt`, `AsyncWriteExt` + * Future: + * Methods of trait `TryFutureExt` + * Free functions `empty`, `lazy`, `maybe_done`, `poll_fn` and `ready` + * Type `FutureOption` + * Macros `join!`, `select!` and `pending!` + * Stream: Methods of trait `TryStreamExt` * Added `TryStreamExt` combinators `map_ok`, `map_err`, `err_into`, `try_next` and `try_for_each` * Added `Drain`, a sink that will discard all items given to it. Can be created using the `drain` function * Bugfix for the `write_all` combinator @@ -339,10 +542,11 @@ * We now use the unstable `use_extern_macros` feature for macro reexports * CI improvements: Named CI jobs, tests are now run on macOS and Linux, the docs are generated and Clippy needs to pass * `#[deny(warnings)]` was removed from all crates and is now only enforced in the CI -* We now have a naming convention for type paramters: `Fut` future, `F` function, `St` stream, `Si` sink, `S` sink & stream, `R` reader, `W` writer, `T` value, `E` error +* We now have a naming convention for type parameters: `Fut` future, `F` function, `St` stream, `Si` sink, `S` sink & stream, `R` reader, `W` writer, `T` value, `E` error * "Task" is now defined as our term for "lightweight thread". The code of the executors and `FuturesUnordered` was refactored to align with this definition. # 0.3.0-alpha.1 - 2018-07-19 + * Major changes: See [the announcement](https://rust-lang-nursery.github.io/futures-rs/blog/2018/07/19/futures-0.3.0-alpha.1.html) on our new blog for details. The changes are too numerous to be covered in this changelog because nearly every line of code was modified. # 0.1.17 - 2017-10-31 diff --git a/Cargo.toml b/Cargo.toml index f972a73d2c..1a90362f31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,15 @@ members = [ "futures/tests/macro-tests", "futures/tests/macro-reexport", + "futures/tests/no-std", "examples/functional", "examples/imperative", ] + +[workspace.lints.rust] +missing_debug_implementations = "warn" +rust_2018_idioms = "warn" +single_use_lifetimes = "warn" +unreachable_pub = "warn" +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(futures_sanitizer)'] } diff --git a/README.md b/README.md index 8c8df06b67..355d6078e3 100644 --- a/README.md +++ b/README.md @@ -8,20 +8,16 @@

- Build Status + Build Status - Crates.io - - - - Rustc Version + crates.io

- + Documentation | Website @@ -42,13 +38,7 @@ Add this to your `Cargo.toml`: futures = "0.3" ``` -Now, you can use futures-rs: - -```rust -use futures::future::Future; -``` - -The current futures-rs requires Rust 1.39 or later. +The current `futures` requires Rust 1.56 or later. ### Feature `std` @@ -61,19 +51,11 @@ a `#[no_std]` environment, use: futures = { version = "0.3", default-features = false } ``` -# License - -This project is licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - https://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or - https://opensource.org/licenses/MIT) - -at your option. +## License -### Contribution +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. Unless you explicitly state otherwise, any contribution intentionally submitted -for inclusion in futures-rs by you, as defined in the Apache-2.0 license, shall be -dual licensed as above, without any additional terms or conditions. +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/ci/publish.sh b/ci/publish.sh new file mode 100755 index 0000000000..cf898ba491 --- /dev/null +++ b/ci/publish.sh @@ -0,0 +1,28 @@ +#!/bin/bash + +set -euo pipefail +IFS=$'\n\t' +cd "$(dirname "$0")"/.. + +# A list of paths to the crate to be published. +# It will be published in the order listed. +members=( + "futures-core" + "futures-io" + "futures-sink" + "futures-task" + "futures-channel" + "futures-macro" + "futures-util" + "futures-executor" + "futures" + "futures-test" +) + +for member in "${members[@]}"; do + ( + set -x + cd "${member}" + cargo +stable publish + ) +done diff --git a/examples/functional/Cargo.toml b/examples/functional/Cargo.toml index 4376909be2..bb95a6f6a3 100644 --- a/examples/functional/Cargo.toml +++ b/examples/functional/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "futures-example-functional" +version = "0.0.0" edition = "2018" -version = "0.1.0" -authors = ["Alex Crichton "] publish = false [dependencies] futures = { path = "../../futures", features = ["thread-pool"] } + +[lints] +workspace = true diff --git a/examples/functional/src/main.rs b/examples/functional/src/main.rs index 3ce65de66a..d535a61b45 100644 --- a/examples/functional/src/main.rs +++ b/examples/functional/src/main.rs @@ -30,9 +30,7 @@ fn main() { // responsible for transmission pool.spawn_ok(fut_tx_result); - let fut_values = rx - .map(|v| v * 2) - .collect(); + let fut_values = rx.map(|v| v * 2).collect(); // Use the executor provided to this async block to wait for the // future to complete. @@ -40,9 +38,9 @@ fn main() { }; // Actually execute the above future, which will invoke Future::poll and - // subsequenty chain appropriate Future::poll and methods needing executors + // subsequently chain appropriate Future::poll and methods needing executors // to drive all futures. Eventually fut_values will be driven to completion. let values: Vec = executor::block_on(fut_values); - println!("Values={:?}", values); -} \ No newline at end of file + println!("Values={values:?}"); +} diff --git a/examples/imperative/Cargo.toml b/examples/imperative/Cargo.toml index e048316cfd..afd7a20428 100644 --- a/examples/imperative/Cargo.toml +++ b/examples/imperative/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "futures-example-imperative" +version = "0.0.0" edition = "2018" -version = "0.1.0" -authors = ["Alex Crichton "] publish = false [dependencies] futures = { path = "../../futures", features = ["thread-pool"] } + +[lints] +workspace = true diff --git a/examples/imperative/src/main.rs b/examples/imperative/src/main.rs index ff1afffe27..1c952537bf 100644 --- a/examples/imperative/src/main.rs +++ b/examples/imperative/src/main.rs @@ -34,15 +34,15 @@ fn main() { // of the stream to be available. while let Some(v) = rx.next().await { pending.push(v * 2); - }; + } pending }; // Actually execute the above future, which will invoke Future::poll and - // subsequenty chain appropriate Future::poll and methods needing executors + // subsequently chain appropriate Future::poll and methods needing executors // to drive all futures. Eventually fut_values will be driven to completion. let values: Vec = executor::block_on(fut_values); - println!("Values={:?}", values); -} \ No newline at end of file + println!("Values={values:?}"); +} diff --git a/futures-channel/Cargo.toml b/futures-channel/Cargo.toml index 9a33320a16..4d430b78d7 100644 --- a/futures-channel/Cargo.toml +++ b/futures-channel/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-channel" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.56" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-channel/0.3" description = """ Channels for asynchronous communication using futures-rs. """ @@ -17,15 +16,14 @@ std = ["alloc", "futures-core/std"] alloc = ["futures-core/alloc"] sink = ["futures-sink"] -# Unstable features -# These features are outside of the normal semver guarantees and require the -# `unstable` feature as an explicit opt-in to unstable API. -unstable = ["futures-core/unstable"] -cfg-target-has-atomic = ["futures-core/cfg-target-has-atomic"] +# These features are no longer used. +# TODO: remove in the next major version. +unstable = [] +cfg-target-has-atomic = [] [dependencies] -futures-core = { path = "../futures-core", version = "0.3.13", default-features = false } -futures-sink = { path = "../futures-sink", version = "0.3.13", default-features = false, optional = true } +futures-core = { path = "../futures-core", version = "0.3.31", default-features = false } +futures-sink = { path = "../futures-sink", version = "0.3.31", default-features = false, optional = true } [dev-dependencies] futures = { path = "../futures", default-features = true } @@ -34,3 +32,6 @@ futures-test = { path = "../futures-test", default-features = true } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/futures-channel/README.md b/futures-channel/README.md new file mode 100644 index 0000000000..e886bd1cad --- /dev/null +++ b/futures-channel/README.md @@ -0,0 +1,23 @@ +# futures-channel + +Channels for asynchronous communication using futures-rs. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-channel = "0.3" +``` + +The current `futures-channel` requires Rust 1.56 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-channel/benches/sync_mpsc.rs b/futures-channel/benches/sync_mpsc.rs index e22fe60666..7c3c3d3a80 100644 --- a/futures-channel/benches/sync_mpsc.rs +++ b/futures-channel/benches/sync_mpsc.rs @@ -7,8 +7,8 @@ use { futures::{ channel::mpsc::{self, Sender, UnboundedSender}, ready, - stream::{Stream, StreamExt}, sink::Sink, + stream::{Stream, StreamExt}, task::{Context, Poll}, }, futures_test::task::noop_context, @@ -25,7 +25,6 @@ fn unbounded_1_tx(b: &mut Bencher) { // 1000 iterations to avoid measuring overhead of initialization // Result should be divided by 1000 for i in 0..1000 { - // Poll, not ready, park assert_eq!(Poll::Pending, rx.poll_next_unpin(&mut cx)); @@ -73,7 +72,6 @@ fn unbounded_uncontended(b: &mut Bencher) { }) } - /// A Stream that continuously sends incrementing number of the queue struct TestSender { tx: Sender, @@ -84,9 +82,7 @@ struct TestSender { impl Stream for TestSender { type Item = u32; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll> - { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = &mut *self; let mut tx = Pin::new(&mut this.tx); @@ -123,12 +119,7 @@ fn bounded_100_tx(b: &mut Bencher) { // Each sender can send one item after specified capacity let (tx, mut rx) = mpsc::channel(0); - let mut tx: Vec<_> = (0..100).map(|_| { - TestSender { - tx: tx.clone(), - last: 0 - } - }).collect(); + let mut tx: Vec<_> = (0..100).map(|_| TestSender { tx: tx.clone(), last: 0 }).collect(); for i in 0..10 { for x in &mut tx { diff --git a/futures-channel/src/lib.rs b/futures-channel/src/lib.rs index 22d90d8a63..09d6a1e2de 100644 --- a/futures-channel/src/lib.rs +++ b/futures-channel/src/lib.rs @@ -11,34 +11,28 @@ //! All items are only available when the `std` or `alloc` feature of this //! library is activated, and it is activated by default. -#![cfg_attr(feature = "cfg-target-has-atomic", feature(cfg_target_has_atomic))] +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, unsafe_op_in_unsafe_fn)] -#![cfg_attr(not(feature = "std"), no_std)] +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +extern crate alloc; +#[cfg(feature = "std")] +extern crate std; -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] - -#[cfg(all(feature = "cfg-target-has-atomic", not(feature = "unstable")))] -compile_error!("The `cfg-target-has-atomic` feature requires the `unstable` feature as an explicit opt-in to unstable features"); - -macro_rules! cfg_target_has_atomic { - ($($item:item)*) => {$( - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] - $item - )*}; -} - -cfg_target_has_atomic! { - #[cfg(feature = "alloc")] - extern crate alloc; - - #[cfg(feature = "alloc")] - mod lock; - #[cfg(feature = "std")] - pub mod mpsc; - #[cfg(feature = "alloc")] - pub mod oneshot; -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod lock; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "std")] +pub mod mpsc; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub mod oneshot; diff --git a/futures-channel/src/lock.rs b/futures-channel/src/lock.rs index 5eecdd9aa2..b328d0f7dd 100644 --- a/futures-channel/src/lock.rs +++ b/futures-channel/src/lock.rs @@ -6,8 +6,8 @@ use core::cell::UnsafeCell; use core::ops::{Deref, DerefMut}; -use core::sync::atomic::Ordering::SeqCst; use core::sync::atomic::AtomicBool; +use core::sync::atomic::Ordering::SeqCst; /// A "mutex" around a value, similar to `std::sync::Mutex`. /// @@ -37,10 +37,7 @@ unsafe impl Sync for Lock {} impl Lock { /// Creates a new lock around the given value. pub(crate) fn new(t: T) -> Self { - Self { - locked: AtomicBool::new(false), - data: UnsafeCell::new(t), - } + Self { locked: AtomicBool::new(false), data: UnsafeCell::new(t) } } /// Attempts to acquire this lock, returning whether the lock was acquired or diff --git a/futures-channel/src/mpsc/mod.rs b/futures-channel/src/mpsc/mod.rs index dd503436bf..03a8a53898 100644 --- a/futures-channel/src/mpsc/mod.rs +++ b/futures-channel/src/mpsc/mod.rs @@ -79,13 +79,13 @@ // by the queue structure. use futures_core::stream::{FusedStream, Stream}; -use futures_core::task::{Context, Poll, Waker}; use futures_core::task::__internal::AtomicWaker; +use futures_core::task::{Context, Poll, Waker}; use std::fmt; use std::pin::Pin; -use std::sync::{Arc, Mutex}; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::SeqCst; +use std::sync::{Arc, Mutex}; use std::thread; use crate::mpsc::queue::Queue; @@ -94,13 +94,11 @@ mod queue; #[cfg(feature = "sink")] mod sink_impl; -#[derive(Debug)] struct UnboundedSenderInner { // Channel state shared between the sender and receiver. inner: Arc>, } -#[derive(Debug)] struct BoundedSenderInner { // Channel state shared between the sender and receiver. inner: Arc>, @@ -121,31 +119,28 @@ impl Unpin for BoundedSenderInner {} /// The transmission end of a bounded mpsc channel. /// -/// This value is created by the [`channel`](channel) function. -#[derive(Debug)] +/// This value is created by the [`channel`] function. pub struct Sender(Option>); /// The transmission end of an unbounded mpsc channel. /// -/// This value is created by the [`unbounded`](unbounded) function. -#[derive(Debug)] +/// This value is created by the [`unbounded`] function. pub struct UnboundedSender(Option>); +#[allow(dead_code)] trait AssertKinds: Send + Sync + Clone {} impl AssertKinds for UnboundedSender {} /// The receiving end of a bounded mpsc channel. /// -/// This value is created by the [`channel`](channel) function. -#[derive(Debug)] +/// This value is created by the [`channel`] function. pub struct Receiver { inner: Option>>, } /// The receiving end of an unbounded mpsc channel. /// -/// This value is created by the [`unbounded`](unbounded) function. -#[derive(Debug)] +/// This value is created by the [`unbounded`] function. pub struct UnboundedReceiver { inner: Option>>, } @@ -209,9 +204,7 @@ impl SendError { impl fmt::Debug for TrySendError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TrySendError") - .field("kind", &self.err.kind) - .finish() + f.debug_struct("TrySendError").field("kind", &self.err.kind).finish() } } @@ -251,8 +244,7 @@ impl TrySendError { impl fmt::Debug for TryRecvError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("TryRecvError") - .finish() + f.debug_tuple("TryRecvError").finish() } } @@ -264,7 +256,6 @@ impl fmt::Display for TryRecvError { impl std::error::Error for TryRecvError {} -#[derive(Debug)] struct UnboundedInner { // Internal channel state. Consists of the number of messages stored in the // channel as well as a flag signalling that the channel is closed. @@ -280,7 +271,6 @@ struct UnboundedInner { recv_task: AtomicWaker, } -#[derive(Debug)] struct BoundedInner { // Max buffer size of the channel. If `None` then the channel is unbounded. buffer: usize, @@ -303,7 +293,7 @@ struct BoundedInner { } // Struct representation of `Inner::state`. -#[derive(Debug, Clone, Copy)] +#[derive(Clone, Copy)] struct State { // `true` when the channel is open is_open: bool, @@ -313,13 +303,13 @@ struct State { } // The `is_open` flag is stored in the left-most bit of `Inner::state` -const OPEN_MASK: usize = usize::max_value() - (usize::max_value() >> 1); +const OPEN_MASK: usize = usize::MAX - (usize::MAX >> 1); // When a new channel is created, it is created in the open state with no // pending messages. const INIT_STATE: usize = OPEN_MASK; -// The maximum number of messages that a channel can track is `usize::max_value() >> 1` +// The maximum number of messages that a channel can track is `usize::MAX >> 1` const MAX_CAPACITY: usize = !(OPEN_MASK); // The maximum requested buffer size must be less than the maximum capacity of @@ -327,7 +317,6 @@ const MAX_CAPACITY: usize = !(OPEN_MASK); const MAX_BUFFER: usize = MAX_CAPACITY >> 1; // Sent to the consumer to wake up blocked producers -#[derive(Debug)] struct SenderTask { task: Option, is_parked: bool, @@ -335,10 +324,7 @@ struct SenderTask { impl SenderTask { fn new() -> Self { - Self { - task: None, - is_parked: false, - } + Self { task: None, is_parked: false } } fn notify(&mut self) { @@ -358,9 +344,8 @@ impl SenderTask { /// guaranteed slot in the channel capacity, and on top of that there are /// `buffer` "first come, first serve" slots available to all senders. /// -/// The [`Receiver`](Receiver) returned implements the -/// [`Stream`](futures_core::stream::Stream) trait, while [`Sender`](Sender) implements -/// `Sink`. +/// The [`Receiver`] returned implements the [`Stream`] trait, while [`Sender`] +/// implements `Sink`. pub fn channel(buffer: usize) -> (Sender, Receiver) { // Check that the requested buffer size does not exceed the maximum buffer // size permitted by the system. @@ -381,9 +366,7 @@ pub fn channel(buffer: usize) -> (Sender, Receiver) { maybe_parked: false, }; - let rx = Receiver { - inner: Some(inner), - }; + let rx = Receiver { inner: Some(inner) }; (Sender(Some(tx)), rx) } @@ -399,7 +382,6 @@ pub fn channel(buffer: usize) -> (Sender, Receiver) { /// the channel. Using an `unbounded` channel has the ability of causing the /// process to run out of memory. In this case, the process will be aborted. pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { - let inner = Arc::new(UnboundedInner { state: AtomicUsize::new(INIT_STATE), message_queue: Queue::new(), @@ -407,13 +389,9 @@ pub fn unbounded() -> (UnboundedSender, UnboundedReceiver) { recv_task: AtomicWaker::new(), }); - let tx = UnboundedSenderInner { - inner: inner.clone(), - }; + let tx = UnboundedSenderInner { inner: inner.clone() }; - let rx = UnboundedReceiver { - inner: Some(inner), - }; + let rx = UnboundedReceiver { inner: Some(inner) }; (UnboundedSender(Some(tx)), rx) } @@ -430,13 +408,10 @@ impl UnboundedSenderInner { if state.is_open { Poll::Ready(Ok(())) } else { - Poll::Ready(Err(SendError { - kind: SendErrorKind::Disconnected, - })) + Poll::Ready(Err(SendError { kind: SendErrorKind::Disconnected })) } } - // Push message to the queue and signal to the receiver fn queue_push_and_signal(&self, msg: T) { // Push the message onto the message queue @@ -462,16 +437,17 @@ impl UnboundedSenderInner { // This probably is never hit? Odds are the process will run out of // memory first. It may be worth to return something else in this // case? - assert!(state.num_messages < MAX_CAPACITY, "buffer space \ - exhausted; sending this messages would overflow the state"); + assert!( + state.num_messages < MAX_CAPACITY, + "buffer space \ + exhausted; sending this messages would overflow the state" + ); state.num_messages += 1; let next = encode_state(&state); match self.inner.state.compare_exchange(curr, next, SeqCst, SeqCst) { - Ok(_) => { - return Some(state.num_messages) - } + Ok(_) => return Some(state.num_messages), Err(actual) => curr = actual, } } @@ -516,12 +492,7 @@ impl BoundedSenderInner { fn try_send(&mut self, msg: T) -> Result<(), TrySendError> { // If the sender is currently blocked, reject the message if !self.poll_unparked(None).is_ready() { - return Err(TrySendError { - err: SendError { - kind: SendErrorKind::Full, - }, - val: msg, - }); + return Err(TrySendError { err: SendError { kind: SendErrorKind::Full }, val: msg }); } // The channel has capacity to accept the message, so send it @@ -530,11 +501,8 @@ impl BoundedSenderInner { // Do the send without failing. // Can be called only by bounded sender. - #[allow(clippy::debug_assert_with_mut_call)] - fn do_send_b(&mut self, msg: T) - -> Result<(), TrySendError> - { - // Anyone callig do_send *should* make sure there is room first, + fn do_send_b(&mut self, msg: T) -> Result<(), TrySendError> { + // Anyone calling do_send *should* make sure there is room first, // but assert here for tests as a sanity check. debug_assert!(self.poll_unparked(None).is_ready()); @@ -551,12 +519,12 @@ impl BoundedSenderInner { // the configured buffer size num_messages > self.inner.buffer } - None => return Err(TrySendError { - err: SendError { - kind: SendErrorKind::Disconnected, - }, - val: msg, - }), + None => { + return Err(TrySendError { + err: SendError { kind: SendErrorKind::Disconnected }, + val: msg, + }) + } }; // If the channel has reached capacity, then the sender task needs to @@ -600,16 +568,17 @@ impl BoundedSenderInner { // This probably is never hit? Odds are the process will run out of // memory first. It may be worth to return something else in this // case? - assert!(state.num_messages < MAX_CAPACITY, "buffer space \ - exhausted; sending this messages would overflow the state"); + assert!( + state.num_messages < MAX_CAPACITY, + "buffer space \ + exhausted; sending this messages would overflow the state" + ); state.num_messages += 1; let next = encode_state(&state); match self.inner.state.compare_exchange(curr, next, SeqCst, SeqCst) { - Ok(_) => { - return Some(state.num_messages) - } + Ok(_) => return Some(state.num_messages), Err(actual) => curr = actual, } } @@ -644,15 +613,10 @@ impl BoundedSenderInner { /// capacity, in which case the current task is queued to be notified once /// capacity is available; /// - `Poll::Ready(Err(SendError))` if the receiver has been dropped. - fn poll_ready( - &mut self, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { let state = decode_state(self.inner.state.load(SeqCst)); if !state.is_open { - return Poll::Ready(Err(SendError { - kind: SendErrorKind::Disconnected, - })); + return Poll::Ready(Err(SendError { kind: SendErrorKind::Disconnected })); } self.poll_unparked(Some(cx)).map(Ok) @@ -699,7 +663,7 @@ impl BoundedSenderInner { if !task.is_parked { self.maybe_parked = false; - return Poll::Ready(()) + return Poll::Ready(()); } // At this point, an unpark request is pending, so there will be an @@ -724,12 +688,7 @@ impl Sender { if let Some(inner) = &mut self.0 { inner.try_send(msg) } else { - Err(TrySendError { - err: SendError { - kind: SendErrorKind::Disconnected, - }, - val: msg, - }) + Err(TrySendError { err: SendError { kind: SendErrorKind::Disconnected }, val: msg }) } } @@ -739,8 +698,7 @@ impl Sender { /// [`poll_ready`](Sender::poll_ready) has reported that the channel is /// ready to receive a message. pub fn start_send(&mut self, msg: T) -> Result<(), SendError> { - self.try_send(msg) - .map_err(|e| e.err) + self.try_send(msg).map_err(|e| e.err) } /// Polls the channel to determine if there is guaranteed capacity to send @@ -755,13 +713,8 @@ impl Sender { /// capacity, in which case the current task is queued to be notified once /// capacity is available; /// - `Poll::Ready(Err(SendError))` if the receiver has been dropped. - pub fn poll_ready( - &mut self, - cx: &mut Context<'_>, - ) -> Poll> { - let inner = self.0.as_mut().ok_or(SendError { - kind: SendErrorKind::Disconnected, - })?; + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + let inner = self.0.as_mut().ok_or(SendError { kind: SendErrorKind::Disconnected })?; inner.poll_ready(cx) } @@ -799,7 +752,10 @@ impl Sender { } /// Hashes the receiver into the provided hasher - pub fn hash_receiver(&self, hasher: &mut H) where H: std::hash::Hasher { + pub fn hash_receiver(&self, hasher: &mut H) + where + H: std::hash::Hasher, + { use std::hash::Hash; let ptr = self.0.as_ref().map(|inner| inner.ptr()); @@ -809,13 +765,8 @@ impl Sender { impl UnboundedSender { /// Check if the channel is ready to receive a message. - pub fn poll_ready( - &self, - _: &mut Context<'_>, - ) -> Poll> { - let inner = self.0.as_ref().ok_or(SendError { - kind: SendErrorKind::Disconnected, - })?; + pub fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { + let inner = self.0.as_ref().ok_or(SendError { kind: SendErrorKind::Disconnected })?; inner.poll_ready_nb() } @@ -845,12 +796,7 @@ impl UnboundedSender { } } - Err(TrySendError { - err: SendError { - kind: SendErrorKind::Disconnected, - }, - val: msg, - }) + Err(TrySendError { err: SendError { kind: SendErrorKind::Disconnected }, val: msg }) } /// Send a message on the channel. @@ -858,8 +804,7 @@ impl UnboundedSender { /// This method should only be called after `poll_ready` has been used to /// verify that the channel is ready to receive a message. pub fn start_send(&mut self, msg: T) -> Result<(), SendError> { - self.do_send_nb(msg) - .map_err(|e| e.err) + self.do_send_nb(msg).map_err(|e| e.err) } /// Sends a message along this channel. @@ -888,12 +833,29 @@ impl UnboundedSender { } /// Hashes the receiver into the provided hasher - pub fn hash_receiver(&self, hasher: &mut H) where H: std::hash::Hasher { + pub fn hash_receiver(&self, hasher: &mut H) + where + H: std::hash::Hasher, + { use std::hash::Hash; let ptr = self.0.as_ref().map(|inner| inner.ptr()); ptr.hash(hasher); } + + /// Return the number of messages in the queue or 0 if channel is disconnected. + pub fn len(&self) -> usize { + if let Some(sender) = &self.0 { + decode_state(sender.inner.state.load(SeqCst)).num_messages + } else { + 0 + } + } + + /// Return false is channel has no queued messages, true otherwise. + pub fn is_empty(&self) -> bool { + self.len() == 0 + } } impl Clone for Sender { @@ -928,9 +890,7 @@ impl Clone for UnboundedSenderInner { Ok(_) => { // The ABA problem doesn't matter here. We only care that the // number of senders never exceeds the maximum. - return Self { - inner: self.inner.clone(), - }; + return Self { inner: self.inner.clone() }; } Err(actual) => curr = actual, } @@ -992,6 +952,18 @@ impl Drop for BoundedSenderInner { } } +impl fmt::Debug for Sender { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Sender").field("closed", &self.is_closed()).finish() + } +} + +impl fmt::Debug for UnboundedSender { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("UnboundedSender").field("closed", &self.is_closed()).finish() + } +} + /* * * ===== impl Receiver ===== @@ -1021,19 +993,22 @@ impl Receiver { /// only when you've otherwise arranged to be notified when the channel is /// no longer empty. /// - /// This function will panic if called after `try_next` or `poll_next` has - /// returned `None`. + /// This function returns: + /// * `Ok(Some(t))` when message is fetched + /// * `Ok(None)` when channel is closed and no messages left in the queue + /// * `Err(e)` when there are no messages available, but channel is not yet closed pub fn try_next(&mut self) -> Result, TryRecvError> { match self.next_message() { - Poll::Ready(msg) => { - Ok(msg) - }, + Poll::Ready(msg) => Ok(msg), Poll::Pending => Err(TryRecvError { _priv: () }), } } fn next_message(&mut self) -> Poll> { - let inner = self.inner.as_mut().expect("Receiver::next_message called after `None`"); + let inner = match self.inner.as_mut() { + None => return Poll::Ready(None), + Some(inner) => inner, + }; // Pop off a message match unsafe { inner.message_queue.pop_spin() } { Some(msg) => { @@ -1098,18 +1073,15 @@ impl FusedStream for Receiver { impl Stream for Receiver { type Item = T; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - // Try to read a message off of the message queue. + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + // Try to read a message off of the message queue. match self.next_message() { Poll::Ready(msg) => { if msg.is_none() { self.inner = None; } Poll::Ready(msg) - }, + } Poll::Pending => { // There are no messages to read, in this case, park. self.inner.as_ref().unwrap().recv_task.register(cx.waker()); @@ -1120,6 +1092,14 @@ impl Stream for Receiver { } } } + + fn size_hint(&self) -> (usize, Option) { + if let Some(inner) = &self.inner { + decode_state(inner.state.load(SeqCst)).size_hint() + } else { + (0, Some(0)) + } + } } impl Drop for Receiver { @@ -1152,6 +1132,18 @@ impl Drop for Receiver { } } +impl fmt::Debug for Receiver { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let closed = if let Some(ref inner) = self.inner { + decode_state(inner.state.load(SeqCst)).is_closed() + } else { + false + }; + + f.debug_struct("Receiver").field("closed", &closed).finish() + } +} + impl UnboundedReceiver { /// Closes the receiving half of a channel, without dropping it. /// @@ -1169,19 +1161,22 @@ impl UnboundedReceiver { /// only when you've otherwise arranged to be notified when the channel is /// no longer empty. /// - /// This function will panic if called after `try_next` or `poll_next` has - /// returned `None`. + /// This function returns: + /// * `Ok(Some(t))` when message is fetched + /// * `Ok(None)` when channel is closed and no messages left in the queue + /// * `Err(e)` when there are no messages available, but channel is not yet closed pub fn try_next(&mut self) -> Result, TryRecvError> { match self.next_message() { - Poll::Ready(msg) => { - Ok(msg) - }, + Poll::Ready(msg) => Ok(msg), Poll::Pending => Err(TryRecvError { _priv: () }), } } fn next_message(&mut self) -> Poll> { - let inner = self.inner.as_mut().expect("Receiver::next_message called after `None`"); + let inner = match self.inner.as_mut() { + None => return Poll::Ready(None), + Some(inner) => inner, + }; // Pop off a message match unsafe { inner.message_queue.pop_spin() } { Some(msg) => { @@ -1230,10 +1225,7 @@ impl FusedStream for UnboundedReceiver { impl Stream for UnboundedReceiver { type Item = T; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { // Try to read a message off of the message queue. match self.next_message() { Poll::Ready(msg) => { @@ -1241,7 +1233,7 @@ impl Stream for UnboundedReceiver { self.inner = None; } Poll::Ready(msg) - }, + } Poll::Pending => { // There are no messages to read, in this case, park. self.inner.as_ref().unwrap().recv_task.register(cx.waker()); @@ -1252,6 +1244,14 @@ impl Stream for UnboundedReceiver { } } } + + fn size_hint(&self) -> (usize, Option) { + if let Some(inner) = &self.inner { + decode_state(inner.state.load(SeqCst)).size_hint() + } else { + (0, Some(0)) + } + } } impl Drop for UnboundedReceiver { @@ -1284,6 +1284,18 @@ impl Drop for UnboundedReceiver { } } +impl fmt::Debug for UnboundedReceiver { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let closed = if let Some(ref inner) = self.inner { + decode_state(inner.state.load(SeqCst)).is_closed() + } else { + false + }; + + f.debug_struct("Receiver").field("closed", &closed).finish() + } +} + /* * * ===== impl Inner ===== @@ -1330,6 +1342,14 @@ impl State { fn is_closed(&self) -> bool { !self.is_open && self.num_messages == 0 } + + fn size_hint(&self) -> (usize, Option) { + if self.is_open { + (self.num_messages, None) + } else { + (self.num_messages, Some(self.num_messages)) + } + } } /* @@ -1339,10 +1359,7 @@ impl State { */ fn decode_state(num: usize) -> State { - State { - is_open: num & OPEN_MASK == OPEN_MASK, - num_messages: num & MAX_CAPACITY, - } + State { is_open: num & OPEN_MASK == OPEN_MASK, num_messages: num & MAX_CAPACITY } } fn encode_state(state: &State) -> usize { diff --git a/futures-channel/src/mpsc/queue.rs b/futures-channel/src/mpsc/queue.rs index b00e1b1755..78cbdce6ec 100644 --- a/futures-channel/src/mpsc/queue.rs +++ b/futures-channel/src/mpsc/queue.rs @@ -43,10 +43,11 @@ pub(super) use self::PopResult::*; -use std::thread; +use std::boxed::Box; use std::cell::UnsafeCell; use std::ptr; use std::sync::atomic::{AtomicPtr, Ordering}; +use std::thread; /// A result of the `pop` function. pub(super) enum PopResult { @@ -61,7 +62,6 @@ pub(super) enum PopResult { Inconsistent, } -#[derive(Debug)] struct Node { next: AtomicPtr, value: Option, @@ -70,21 +70,17 @@ struct Node { /// The multi-producer single-consumer structure. This is not cloneable, but it /// may be safely shared so long as it is guaranteed that there is only one /// popper at a time (many pushers are allowed). -#[derive(Debug)] pub(super) struct Queue { head: AtomicPtr>, tail: UnsafeCell<*mut Node>, } -unsafe impl Send for Queue { } -unsafe impl Sync for Queue { } +unsafe impl Send for Queue {} +unsafe impl Sync for Queue {} impl Node { unsafe fn new(v: Option) -> *mut Self { - Box::into_raw(Box::new(Self { - next: AtomicPtr::new(ptr::null_mut()), - value: v, - })) + Box::into_raw(Box::new(Self { next: AtomicPtr::new(ptr::null_mut()), value: v })) } } @@ -93,10 +89,7 @@ impl Queue { /// one consumer. pub(super) fn new() -> Self { let stub = unsafe { Node::new(None) }; - Self { - head: AtomicPtr::new(stub), - tail: UnsafeCell::new(stub), - } + Self { head: AtomicPtr::new(stub), tail: UnsafeCell::new(stub) } } /// Pushes a new value onto this queue. @@ -121,19 +114,25 @@ impl Queue { /// /// This function is unsafe because only one thread can call it at a time. pub(super) unsafe fn pop(&self) -> PopResult { - let tail = *self.tail.get(); - let next = (*tail).next.load(Ordering::Acquire); - - if !next.is_null() { - *self.tail.get() = next; - assert!((*tail).value.is_none()); - assert!((*next).value.is_some()); - let ret = (*next).value.take().unwrap(); - drop(Box::from_raw(tail)); - return Data(ret); - } + unsafe { + let tail = *self.tail.get(); + let next = (*tail).next.load(Ordering::Acquire); + + if !next.is_null() { + *self.tail.get() = next; + assert!((*tail).value.is_none()); + assert!((*next).value.is_some()); + let ret = (*next).value.take().unwrap(); + drop(Box::from_raw(tail)); + return Data(ret); + } - if self.head.load(Ordering::Acquire) == tail {Empty} else {Inconsistent} + if self.head.load(Ordering::Acquire) == tail { + Empty + } else { + Inconsistent + } + } } /// Pop an element similarly to `pop` function, but spin-wait on inconsistent @@ -142,7 +141,7 @@ impl Queue { /// This function is unsafe because only one thread can call it at a time. pub(super) unsafe fn pop_spin(&self) -> Option { loop { - match self.pop() { + match unsafe { self.pop() } { Empty => return None, Data(t) => return Some(t), // Inconsistent means that there will be a message to pop diff --git a/futures-channel/src/mpsc/sink_impl.rs b/futures-channel/src/mpsc/sink_impl.rs index 4ce66b4e59..1be20162c2 100644 --- a/futures-channel/src/mpsc/sink_impl.rs +++ b/futures-channel/src/mpsc/sink_impl.rs @@ -6,24 +6,15 @@ use std::pin::Pin; impl Sink for Sender { type Error = SendError; - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { (*self).poll_ready(cx) } - fn start_send( - mut self: Pin<&mut Self>, - msg: T, - ) -> Result<(), Self::Error> { + fn start_send(mut self: Pin<&mut Self>, msg: T) -> Result<(), Self::Error> { (*self).start_send(msg) } - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { match (*self).poll_ready(cx) { Poll::Ready(Err(ref e)) if e.is_disconnected() => { // If the receiver disconnected, we consider the sink to be flushed. @@ -33,10 +24,7 @@ impl Sink for Sender { } } - fn poll_close( - mut self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { self.disconnect(); Poll::Ready(Ok(())) } @@ -45,31 +33,19 @@ impl Sink for Sender { impl Sink for UnboundedSender { type Error = SendError; - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Self::poll_ready(&*self, cx) } - fn start_send( - mut self: Pin<&mut Self>, - msg: T, - ) -> Result<(), Self::Error> { + fn start_send(mut self: Pin<&mut Self>, msg: T) -> Result<(), Self::Error> { Self::start_send(&mut *self, msg) } - fn poll_flush( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn poll_close( - mut self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { self.disconnect(); Poll::Ready(Ok(())) } @@ -78,29 +54,19 @@ impl Sink for UnboundedSender { impl Sink for &UnboundedSender { type Error = SendError; - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { UnboundedSender::poll_ready(*self, cx) } fn start_send(self: Pin<&mut Self>, msg: T) -> Result<(), Self::Error> { - self.unbounded_send(msg) - .map_err(TrySendError::into_send_error) + self.unbounded_send(msg).map_err(TrySendError::into_send_error) } - fn poll_flush( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn poll_close( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll> { + fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { self.close_channel(); Poll::Ready(Ok(())) } diff --git a/futures-channel/src/oneshot.rs b/futures-channel/src/oneshot.rs index dbbce8112b..fe5b115a33 100644 --- a/futures-channel/src/oneshot.rs +++ b/futures-channel/src/oneshot.rs @@ -7,24 +7,22 @@ use core::fmt; use core::pin::Pin; use core::sync::atomic::AtomicBool; use core::sync::atomic::Ordering::SeqCst; -use futures_core::future::{Future, FusedFuture}; +use futures_core::future::{FusedFuture, Future}; use futures_core::task::{Context, Poll, Waker}; use crate::lock::Lock; /// A future for a value that will be provided by another asynchronous task. /// -/// This is created by the [`channel`](channel) function. +/// This is created by the [`channel`] function. #[must_use = "futures do nothing unless you `.await` or poll them"] -#[derive(Debug)] pub struct Receiver { inner: Arc>, } /// A means of transmitting a single value to another task. /// -/// This is created by the [`channel`](channel) function. -#[derive(Debug)] +/// This is created by the [`channel`] function. pub struct Sender { inner: Arc>, } @@ -35,7 +33,6 @@ impl Unpin for Sender {} /// Internal state of the `Receiver`/`Sender` pair above. This is all used as /// the internal synchronization between the two for send/recv operations. -#[derive(Debug)] struct Inner { /// Indicates whether this oneshot is complete yet. This is filled in both /// by `Sender::drop` and by `Receiver::drop`, and both sides interpret it @@ -106,12 +103,8 @@ struct Inner { /// ``` pub fn channel() -> (Sender, Receiver) { let inner = Arc::new(Inner::new()); - let receiver = Receiver { - inner: inner.clone(), - }; - let sender = Sender { - inner, - }; + let receiver = Receiver { inner: inner.clone() }; + let sender = Sender { inner }; (sender, receiver) } @@ -127,7 +120,7 @@ impl Inner { fn send(&self, t: T) -> Result<(), T> { if self.complete.load(SeqCst) { - return Err(t) + return Err(t); } // Note that this lock acquisition may fail if the receiver @@ -164,7 +157,7 @@ impl Inner { // destructor, but our destructor hasn't run yet so if it's set then the // oneshot is gone. if self.complete.load(SeqCst) { - return Poll::Ready(()) + return Poll::Ready(()); } // If our other half is not gone then we need to park our current task @@ -273,7 +266,10 @@ impl Inner { } else { let task = cx.waker().clone(); match self.rx_task.try_lock() { - Some(mut slot) => { *slot = Some(task); false }, + Some(mut slot) => { + *slot = Some(task); + false + } None => true, } }; @@ -336,8 +332,8 @@ impl Sender { /// Completes this oneshot with a successful result. /// /// This function will consume `self` and indicate to the other end, the - /// [`Receiver`](Receiver), that the value provided is the result of the - /// computation this represents. + /// [`Receiver`], that the value provided is the result of the computation + /// this represents. /// /// If the value is successfully enqueued for the remote end to receive, /// then `Ok(())` is returned. If the receiving end was dropped before @@ -347,7 +343,7 @@ impl Sender { } /// Polls this `Sender` half to detect whether its associated - /// [`Receiver`](Receiver) has been dropped. + /// [`Receiver`] has been dropped. /// /// # Return values /// @@ -363,10 +359,10 @@ impl Sender { } /// Creates a future that resolves when this `Sender`'s corresponding - /// [`Receiver`](Receiver) half has hung up. + /// [`Receiver`] half has hung up. /// /// This is a utility wrapping [`poll_canceled`](Sender::poll_canceled) - /// to expose a [`Future`](core::future::Future). + /// to expose a [`Future`]. pub fn cancellation(&mut self) -> Cancellation<'_, T> { Cancellation { inner: self } } @@ -394,6 +390,12 @@ impl Drop for Sender { } } +impl fmt::Debug for Sender { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Sender").field("complete", &self.inner.complete).finish() + } +} + /// A future that resolves when the receiving end of a channel has hung up. /// /// This is an `.await`-friendly interface around [`poll_canceled`](Sender::poll_canceled). @@ -411,8 +413,8 @@ impl Future for Cancellation<'_, T> { } } -/// Error returned from a [`Receiver`](Receiver) when the corresponding -/// [`Sender`](Sender) is dropped. +/// Error returned from a [`Receiver`] when the corresponding [`Sender`] is +/// dropped. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct Canceled; @@ -453,10 +455,7 @@ impl Receiver { impl Future for Receiver { type Output = Result; - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.inner.recv(cx) } } @@ -481,3 +480,9 @@ impl Drop for Receiver { self.inner.drop_rx() } } + +impl fmt::Debug for Receiver { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Receiver").field("complete", &self.inner.complete).finish() + } +} diff --git a/futures-channel/tests/channel.rs b/futures-channel/tests/channel.rs index 73dac645b1..5f01a8ef4c 100644 --- a/futures-channel/tests/channel.rs +++ b/futures-channel/tests/channel.rs @@ -1,8 +1,8 @@ use futures::channel::mpsc; use futures::executor::block_on; use futures::future::poll_fn; -use futures::stream::StreamExt; use futures::sink::SinkExt; +use futures::stream::StreamExt; use std::sync::atomic::{AtomicUsize, Ordering}; use std::thread; @@ -11,9 +11,7 @@ fn sequence() { let (tx, rx) = mpsc::channel(1); let amt = 20; - let t = thread::spawn(move || { - block_on(send_sequence(amt, tx)) - }); + let t = thread::spawn(move || block_on(send_sequence(amt, tx))); let list: Vec<_> = block_on(rx.collect()); let mut list = list.into_iter(); for i in (1..=amt).rev() { @@ -34,9 +32,7 @@ async fn send_sequence(n: u32, mut sender: mpsc::Sender) { fn drop_sender() { let (tx, mut rx) = mpsc::channel::(1); drop(tx); - let f = poll_fn(|cx| { - rx.poll_next_unpin(cx) - }); + let f = poll_fn(|cx| rx.poll_next_unpin(cx)); assert_eq!(block_on(f), None) } diff --git a/futures-channel/tests/mpsc-close.rs b/futures-channel/tests/mpsc-close.rs index 9eb5296d88..2d49936fc1 100644 --- a/futures-channel/tests/mpsc-close.rs +++ b/futures-channel/tests/mpsc-close.rs @@ -13,9 +13,7 @@ use std::time::{Duration, Instant}; fn smoke() { let (mut sender, receiver) = mpsc::channel(1); - let t = thread::spawn(move || { - while let Ok(()) = block_on(sender.send(42)) {} - }); + let t = thread::spawn(move || while let Ok(()) = block_on(sender.send(42)) {}); // `receiver` needs to be dropped for `sender` to stop sending and therefore before the join. block_on(receiver.take(3).for_each(|_| futures::future::ready(()))); @@ -149,6 +147,7 @@ fn single_receiver_drop_closes_channel_and_drains() { // Stress test that `try_send()`s occurring concurrently with receiver // close/drops don't appear as successful sends. +#[cfg_attr(miri, ignore)] // Miri is too slow #[test] fn stress_try_send_as_receiver_closes() { const AMT: usize = 10000; @@ -166,7 +165,7 @@ fn stress_try_send_as_receiver_closes() { struct TestRx { rx: mpsc::Receiver>, // The number of times to query `rx` before dropping it. - poll_count: usize + poll_count: usize, } struct TestTask { command_rx: mpsc::Receiver, @@ -175,10 +174,10 @@ fn stress_try_send_as_receiver_closes() { } impl TestTask { /// Create a new TestTask - fn new() -> (TestTask, mpsc::Sender) { + fn new() -> (Self, mpsc::Sender) { let (command_tx, command_rx) = mpsc::channel::(0); ( - TestTask { + Self { command_rx, test_rx: None, countdown: 0, // 0 means no countdown is in progress. @@ -190,14 +189,11 @@ fn stress_try_send_as_receiver_closes() { impl Future for TestTask { type Output = (); - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // Poll the test channel, if one is present. if let Some(rx) = &mut self.test_rx { if let Poll::Ready(v) = rx.poll_next_unpin(cx) { - let _ = v.expect("test finished unexpectedly!"); + let _ = v.expect("test finished unexpectedly!"); } self.countdown -= 1; // Busy-poll until the countdown is finished. @@ -209,9 +205,9 @@ fn stress_try_send_as_receiver_closes() { self.test_rx = Some(rx); self.countdown = poll_count; cx.waker().wake_by_ref(); - }, + } Poll::Ready(None) => return Poll::Ready(()), - Poll::Pending => {}, + Poll::Pending => {} } if self.countdown == 0 { // Countdown complete -- drop the Receiver. @@ -255,10 +251,14 @@ fn stress_try_send_as_receiver_closes() { if prev_weak.upgrade().is_none() { break; } - assert!(t0.elapsed() < Duration::from_secs(SPIN_TIMEOUT_S), + assert!( + t0.elapsed() < Duration::from_secs(SPIN_TIMEOUT_S), "item not dropped on iteration {} after \ {} sends ({} successful). spin=({})", - i, attempted_sends, successful_sends, spins + i, + attempted_sends, + successful_sends, + spins ); spins += 1; thread::sleep(Duration::from_millis(SPIN_SLEEP_MS)); @@ -273,6 +273,27 @@ fn stress_try_send_as_receiver_closes() { } } drop(cmd_tx); - bg.join() - .expect("background thread join"); + bg.join().expect("background thread join"); +} + +#[test] +fn unbounded_try_next_after_none() { + let (tx, mut rx) = mpsc::unbounded::(); + // Drop the sender, close the channel. + drop(tx); + // Receive the end of channel. + assert_eq!(Ok(None), rx.try_next().map_err(|_| ())); + // None received, check we can call `try_next` again. + assert_eq!(Ok(None), rx.try_next().map_err(|_| ())); +} + +#[test] +fn bounded_try_next_after_none() { + let (tx, mut rx) = mpsc::channel::(17); + // Drop the sender, close the channel. + drop(tx); + // Receive the end of channel. + assert_eq!(Ok(None), rx.try_next().map_err(|_| ())); + // None received, check we can call `try_next` again. + assert_eq!(Ok(None), rx.try_next().map_err(|_| ())); } diff --git a/futures-channel/tests/mpsc-size_hint.rs b/futures-channel/tests/mpsc-size_hint.rs new file mode 100644 index 0000000000..d9cdaa31fa --- /dev/null +++ b/futures-channel/tests/mpsc-size_hint.rs @@ -0,0 +1,40 @@ +use futures::channel::mpsc; +use futures::stream::Stream; + +#[test] +fn unbounded_size_hint() { + let (tx, mut rx) = mpsc::unbounded::(); + assert_eq!((0, None), rx.size_hint()); + tx.unbounded_send(1).unwrap(); + assert_eq!((1, None), rx.size_hint()); + rx.try_next().unwrap().unwrap(); + assert_eq!((0, None), rx.size_hint()); + tx.unbounded_send(2).unwrap(); + tx.unbounded_send(3).unwrap(); + assert_eq!((2, None), rx.size_hint()); + drop(tx); + assert_eq!((2, Some(2)), rx.size_hint()); + rx.try_next().unwrap().unwrap(); + assert_eq!((1, Some(1)), rx.size_hint()); + rx.try_next().unwrap().unwrap(); + assert_eq!((0, Some(0)), rx.size_hint()); +} + +#[test] +fn channel_size_hint() { + let (mut tx, mut rx) = mpsc::channel::(10); + assert_eq!((0, None), rx.size_hint()); + tx.try_send(1).unwrap(); + assert_eq!((1, None), rx.size_hint()); + rx.try_next().unwrap().unwrap(); + assert_eq!((0, None), rx.size_hint()); + tx.try_send(2).unwrap(); + tx.try_send(3).unwrap(); + assert_eq!((2, None), rx.size_hint()); + drop(tx); + assert_eq!((2, Some(2)), rx.size_hint()); + rx.try_next().unwrap().unwrap(); + assert_eq!((1, Some(1)), rx.size_hint()); + rx.try_next().unwrap().unwrap(); + assert_eq!((0, Some(0)), rx.size_hint()); +} diff --git a/futures-channel/tests/mpsc.rs b/futures-channel/tests/mpsc.rs index 61c5a50246..28d476b05a 100644 --- a/futures-channel/tests/mpsc.rs +++ b/futures-channel/tests/mpsc.rs @@ -1,15 +1,16 @@ use futures::channel::{mpsc, oneshot}; use futures::executor::{block_on, block_on_stream}; -use futures::future::{FutureExt, poll_fn}; -use futures::stream::{Stream, StreamExt}; +use futures::future::{poll_fn, FutureExt}; +use futures::pin_mut; use futures::sink::{Sink, SinkExt}; +use futures::stream::{Stream, StreamExt}; use futures::task::{Context, Poll}; -use futures::pin_mut; use futures_test::task::{new_count_waker, noop_context}; -use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; use std::thread; +#[allow(dead_code)] trait AssertSend: Send {} impl AssertSend for mpsc::Sender {} impl AssertSend for mpsc::Receiver {} @@ -77,7 +78,7 @@ fn send_shared_recv() { fn send_recv_threads() { let (mut tx, rx) = mpsc::channel::(16); - let t = thread::spawn(move|| { + let t = thread::spawn(move || { block_on(tx.send(1)).unwrap(); }); @@ -200,11 +201,11 @@ fn tx_close_gets_none() { #[test] fn stress_shared_unbounded() { - const AMT: u32 = 10000; + const AMT: u32 = if cfg!(miri) { 100 } else { 10000 }; const NTHREADS: u32 = 8; let (tx, rx) = mpsc::unbounded::(); - let t = thread::spawn(move|| { + let t = thread::spawn(move || { let result: Vec<_> = block_on(rx.collect()); assert_eq!(result.len(), (AMT * NTHREADS) as usize); for item in result { @@ -215,7 +216,7 @@ fn stress_shared_unbounded() { for _ in 0..NTHREADS { let tx = tx.clone(); - thread::spawn(move|| { + thread::spawn(move || { for _ in 0..AMT { tx.unbounded_send(1).unwrap(); } @@ -229,11 +230,11 @@ fn stress_shared_unbounded() { #[test] fn stress_shared_bounded_hard() { - const AMT: u32 = 10000; + const AMT: u32 = if cfg!(miri) { 100 } else { 10000 }; const NTHREADS: u32 = 8; let (tx, rx) = mpsc::channel::(0); - let t = thread::spawn(move|| { + let t = thread::spawn(move || { let result: Vec<_> = block_on(rx.collect()); assert_eq!(result.len(), (AMT * NTHREADS) as usize); for item in result { @@ -259,7 +260,7 @@ fn stress_shared_bounded_hard() { #[allow(clippy::same_item_push)] #[test] fn stress_receiver_multi_task_bounded_hard() { - const AMT: usize = 10_000; + const AMT: usize = if cfg!(miri) { 100 } else { 10_000 }; const NTHREADS: u32 = 2; let (mut tx, rx) = mpsc::channel::(0); @@ -297,9 +298,9 @@ fn stress_receiver_multi_task_bounded_hard() { } Poll::Ready(None) => { *rx_opt = None; - break - }, - Poll::Pending => {}, + break; + } + Poll::Pending => {} } } } else { @@ -311,7 +312,6 @@ fn stress_receiver_multi_task_bounded_hard() { th.push(t); } - for i in 0..AMT { block_on(tx.send(i)).unwrap(); } @@ -328,7 +328,9 @@ fn stress_receiver_multi_task_bounded_hard() { /// after sender dropped. #[test] fn stress_drop_sender() { - fn list() -> impl Stream { + const ITER: usize = if cfg!(miri) { 100 } else { 10000 }; + + fn list() -> impl Stream { let (tx, rx) = mpsc::channel(1); thread::spawn(move || { block_on(send_one_two_three(tx)); @@ -336,7 +338,7 @@ fn stress_drop_sender() { rx } - for _ in 0..10000 { + for _ in 0..ITER { let v: Vec<_> = block_on(list().collect()); assert_eq!(v, vec![1, 2, 3]); } @@ -383,7 +385,9 @@ fn stress_close_receiver_iter() { #[test] fn stress_close_receiver() { - for _ in 0..10000 { + const ITER: usize = if cfg!(miri) { 50 } else { 10000 }; + + for _ in 0..ITER { stress_close_receiver_iter(); } } @@ -398,7 +402,7 @@ async fn stress_poll_ready_sender(mut sender: mpsc::Sender, count: u32) { #[allow(clippy::same_item_push)] #[test] fn stress_poll_ready() { - const AMT: u32 = 1000; + const AMT: u32 = if cfg!(miri) { 100 } else { 1000 }; const NTHREADS: u32 = 8; /// Run a stress test using the specified channel capacity. @@ -407,9 +411,7 @@ fn stress_poll_ready() { let mut threads = Vec::new(); for _ in 0..NTHREADS { let sender = tx.clone(); - threads.push(thread::spawn(move || { - block_on(stress_poll_ready_sender(sender, AMT)) - })); + threads.push(thread::spawn(move || block_on(stress_poll_ready_sender(sender, AMT)))); } drop(tx); @@ -429,14 +431,14 @@ fn stress_poll_ready() { #[test] fn try_send_1() { - const N: usize = 3000; + const N: usize = if cfg!(miri) { 100 } else { 3000 }; let (mut tx, rx) = mpsc::channel(0); let t = thread::spawn(move || { for i in 0..N { loop { if tx.try_send(i).is_ok() { - break + break; } } } @@ -542,8 +544,8 @@ fn is_connected_to() { #[test] fn hash_receiver() { - use std::hash::Hasher; use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; let mut hasher_a1 = DefaultHasher::new(); let mut hasher_a2 = DefaultHasher::new(); @@ -631,3 +633,26 @@ fn send_backpressure_multi_senders() { let item = block_on(rx.next()).unwrap(); assert_eq!(item, 2); } + +/// Test that empty channel has zero length and that non-empty channel has length equal to number +/// of enqueued items +#[test] +fn unbounded_len() { + let (tx, mut rx) = mpsc::unbounded(); + assert_eq!(tx.len(), 0); + assert!(tx.is_empty()); + tx.unbounded_send(1).unwrap(); + assert_eq!(tx.len(), 1); + assert!(!tx.is_empty()); + tx.unbounded_send(2).unwrap(); + assert_eq!(tx.len(), 2); + assert!(!tx.is_empty()); + let item = block_on(rx.next()).unwrap(); + assert_eq!(item, 1); + assert_eq!(tx.len(), 1); + assert!(!tx.is_empty()); + let item = block_on(rx.next()).unwrap(); + assert_eq!(item, 2); + assert_eq!(tx.len(), 0); + assert!(tx.is_empty()); +} diff --git a/futures-channel/tests/oneshot.rs b/futures-channel/tests/oneshot.rs index a22d039e40..6b48376dc0 100644 --- a/futures-channel/tests/oneshot.rs +++ b/futures-channel/tests/oneshot.rs @@ -1,6 +1,6 @@ use futures::channel::oneshot::{self, Sender}; use futures::executor::block_on; -use futures::future::{FutureExt, poll_fn}; +use futures::future::{poll_fn, FutureExt}; use futures::task::{Context, Poll}; use futures_test::task::panic_waker_ref; use std::sync::mpsc; @@ -35,6 +35,8 @@ fn cancel_notifies() { #[test] fn cancel_lots() { + const N: usize = if cfg!(miri) { 100 } else { 20000 }; + let (tx, rx) = mpsc::channel::<(Sender<_>, mpsc::Sender<_>)>(); let t = thread::spawn(move || { for (mut tx, tx2) in rx { @@ -43,7 +45,7 @@ fn cancel_lots() { } }); - for _ in 0..20000 { + for _ in 0..N { let (otx, orx) = oneshot::channel::(); let (tx2, rx2) = mpsc::channel(); tx.send((otx, tx2)).unwrap(); @@ -70,7 +72,7 @@ fn close() { rx.close(); block_on(poll_fn(|cx| { match rx.poll_unpin(cx) { - Poll::Ready(Err(_)) => {}, + Poll::Ready(Err(_)) => {} _ => panic!(), }; assert!(tx.poll_canceled(cx).is_ready()); @@ -101,6 +103,8 @@ fn is_canceled() { #[test] fn cancel_sends() { + const N: usize = if cfg!(miri) { 100 } else { 20000 }; + let (tx, rx) = mpsc::channel::>(); let t = thread::spawn(move || { for otx in rx { @@ -108,7 +112,7 @@ fn cancel_sends() { } }); - for _ in 0..20000 { + for _ in 0..N { let (otx, mut orx) = oneshot::channel::(); tx.send(otx).unwrap(); diff --git a/futures-core/Cargo.toml b/futures-core/Cargo.toml index 55601dbb6e..a7d710e6a5 100644 --- a/futures-core/Cargo.toml +++ b/futures-core/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-core" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.36" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-core/0.3" description = """ The core traits and types in for the `futures` library. """ @@ -16,13 +15,13 @@ default = ["std"] std = ["alloc"] alloc = [] -# Unstable features -# These features are outside of the normal semver guarantees and require the -# `unstable` feature as an explicit opt-in to unstable API. +# These features are no longer used. +# TODO: remove in the next major version. unstable = [] cfg-target-has-atomic = [] [dependencies] +portable-atomic = { version = "1.3", optional = true, default-features = false, features = ["require-cas"] } [dev-dependencies] futures = { path = "../futures" } @@ -30,3 +29,6 @@ futures = { path = "../futures" } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/futures-core/README.md b/futures-core/README.md new file mode 100644 index 0000000000..96e0e064bc --- /dev/null +++ b/futures-core/README.md @@ -0,0 +1,23 @@ +# futures-core + +The core traits and types in for the `futures` library. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-core = "0.3" +``` + +The current `futures-core` requires Rust 1.36 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-core/src/future.rs b/futures-core/src/future.rs index d9327c01e5..30c0323a6b 100644 --- a/futures-core/src/future.rs +++ b/futures-core/src/future.rs @@ -9,10 +9,20 @@ pub use core::future::Future; /// An owned dynamically typed [`Future`] for use in cases where you can't /// statically type your result or need to add some indirection. +/// +/// This type is often created by the [`boxed`] method on [`FutureExt`]. See its documentation for more. +/// +/// [`boxed`]: https://docs.rs/futures/latest/futures/future/trait.FutureExt.html#method.boxed +/// [`FutureExt`]: https://docs.rs/futures/latest/futures/future/trait.FutureExt.html #[cfg(feature = "alloc")] pub type BoxFuture<'a, T> = Pin + Send + 'a>>; /// `BoxFuture`, but without the `Send` requirement. +/// +/// This type is often created by the [`boxed_local`] method on [`FutureExt`]. See its documentation for more. +/// +/// [`boxed_local`]: https://docs.rs/futures/latest/futures/future/trait.FutureExt.html#method.boxed_local +/// [`FutureExt`]: https://docs.rs/futures/latest/futures/future/trait.FutureExt.html #[cfg(feature = "alloc")] pub type LocalBoxFuture<'a, T> = Pin + 'a>>; @@ -67,14 +77,12 @@ pub trait TryFuture: Future + private_try_future::Sealed { /// This method is a stopgap for a compiler limitation that prevents us from /// directly inheriting from the `Future` trait; in the future it won't be /// needed. - fn try_poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>; + fn try_poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; } impl TryFuture for F - where F: ?Sized + Future> +where + F: ?Sized + Future>, { type Ok = T; type Error = E; @@ -87,8 +95,8 @@ impl TryFuture for F #[cfg(feature = "alloc")] mod if_alloc { - use alloc::boxed::Box; use super::*; + use alloc::boxed::Box; impl FusedFuture for Box { fn is_terminated(&self) -> bool { diff --git a/futures-core/src/lib.rs b/futures-core/src/lib.rs index ec14adba22..6ff6b974b1 100644 --- a/futures-core/src/lib.rs +++ b/futures-core/src/lib.rs @@ -1,32 +1,27 @@ //! Core traits and types for asynchronous operations in Rust. -#![cfg_attr(feature = "cfg-target-has-atomic", feature(cfg_target_has_atomic))] - -#![cfg_attr(not(feature = "std"), no_std)] - -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] - -#[cfg(all(feature = "cfg-target-has-atomic", not(feature = "unstable")))] -compile_error!("The `cfg-target-has-atomic` feature requires the `unstable` feature as an explicit opt-in to unstable features"); +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, /* unsafe_op_in_unsafe_fn */)] // unsafe_op_in_unsafe_fn requires Rust 1.52 #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; pub mod future; -#[doc(hidden)] pub use self::future::{Future, FusedFuture, TryFuture}; +#[doc(no_inline)] +pub use self::future::{FusedFuture, Future, TryFuture}; pub mod stream; -#[doc(hidden)] pub use self::stream::{Stream, FusedStream, TryStream}; +#[doc(no_inline)] +pub use self::stream::{FusedStream, Stream, TryStream}; #[macro_use] pub mod task; - -// Not public API. -#[doc(hidden)] -pub mod __private { - pub use core::task::Poll; -} diff --git a/futures-core/src/stream.rs b/futures-core/src/stream.rs index 4a13e3bd7d..dd07d5aa01 100644 --- a/futures-core/src/stream.rs +++ b/futures-core/src/stream.rs @@ -6,10 +6,20 @@ use core::task::{Context, Poll}; /// An owned dynamically typed [`Stream`] for use in cases where you can't /// statically type your result or need to add some indirection. +/// +/// This type is often created by the [`boxed`] method on [`StreamExt`]. See its documentation for more. +/// +/// [`boxed`]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html#method.boxed +/// [`StreamExt`]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html #[cfg(feature = "alloc")] pub type BoxStream<'a, T> = Pin + Send + 'a>>; /// `BoxStream`, but without the `Send` requirement. +/// +/// This type is often created by the [`boxed_local`] method on [`StreamExt`]. See its documentation for more. +/// +/// [`boxed_local`]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html#method.boxed_local +/// [`StreamExt`]: https://docs.rs/futures/latest/futures/stream/trait.StreamExt.html #[cfg(feature = "alloc")] pub type LocalBoxStream<'a, T> = Pin + 'a>>; @@ -38,15 +48,15 @@ pub trait Stream { /// stream state: /// /// - `Poll::Pending` means that this stream's next value is not ready - /// yet. Implementations will ensure that the current task will be notified - /// when the next value may be ready. + /// yet. Implementations will ensure that the current task will be notified + /// when the next value may be ready. /// /// - `Poll::Ready(Some(val))` means that the stream has successfully - /// produced a value, `val`, and may produce further values on subsequent - /// `poll_next` calls. + /// produced a value, `val`, and may produce further values on subsequent + /// `poll_next` calls. /// /// - `Poll::Ready(None)` means that the stream has terminated, and - /// `poll_next` should not be invoked again. + /// `poll_next` should not be invoked again. /// /// # Panics /// @@ -63,10 +73,7 @@ pub trait Stream { /// calls. /// /// [`fuse`]: https://docs.rs/futures/0.3/futures/stream/trait.StreamExt.html#method.fuse - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>; + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; /// Returns the bounds on the remaining length of the stream. /// @@ -103,10 +110,7 @@ pub trait Stream { impl Stream for &mut S { type Item = S::Item; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { S::poll_next(Pin::new(&mut **self), cx) } @@ -122,10 +126,7 @@ where { type Item = ::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.get_mut().as_mut().poll_next(cx) } @@ -185,35 +186,36 @@ pub trait TryStream: Stream + private_try_stream::Sealed { /// This method is a stopgap for a compiler limitation that prevents us from /// directly inheriting from the `Stream` trait; in the future it won't be /// needed. - fn try_poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll>>; + fn try_poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>>; } impl TryStream for S - where S: ?Sized + Stream> +where + S: ?Sized + Stream>, { type Ok = T; type Error = E; - fn try_poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll>> - { + fn try_poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { self.poll_next(cx) } } #[cfg(feature = "alloc")] mod if_alloc { - use alloc::boxed::Box; use super::*; + use alloc::boxed::Box; impl Stream for Box { type Item = S::Item; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut **self).poll_next(cx) } @@ -226,10 +228,7 @@ mod if_alloc { impl Stream for std::panic::AssertUnwindSafe { type Item = S::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { unsafe { self.map_unchecked_mut(|x| &mut x.0) }.poll_next(cx) } diff --git a/futures-core/src/task/__internal/atomic_waker.rs b/futures-core/src/task/__internal/atomic_waker.rs index 213355bc6b..3b82fb7cc8 100644 --- a/futures-core/src/task/__internal/atomic_waker.rs +++ b/futures-core/src/task/__internal/atomic_waker.rs @@ -1,9 +1,16 @@ use core::cell::UnsafeCell; use core::fmt; -use core::sync::atomic::AtomicUsize; -use core::sync::atomic::Ordering::{Acquire, Release, AcqRel}; use core::task::Waker; +use atomic::AtomicUsize; +use atomic::Ordering::{AcqRel, Acquire, Release}; + +#[cfg(feature = "portable-atomic")] +use portable_atomic as atomic; + +#[cfg(not(feature = "portable-atomic"))] +use core::sync::atomic; + /// A synchronization primitive for task wakeup. /// /// Sometimes the task interested in a given event will change over time. @@ -199,13 +206,11 @@ impl AtomicWaker { /// Create an `AtomicWaker`. pub const fn new() -> Self { // Make sure that task is Sync + #[allow(dead_code)] trait AssertSync: Sync {} impl AssertSync for Waker {} - Self { - state: AtomicUsize::new(WAITING), - waker: UnsafeCell::new(None), - } + Self { state: AtomicUsize::new(WAITING), waker: UnsafeCell::new(None) } } /// Registers the waker to be notified on calls to `wake`. @@ -267,7 +272,12 @@ impl AtomicWaker { WAITING => { unsafe { // Locked acquired, update the waker cell - *self.waker.get() = Some(waker.clone()); + + // Avoid cloning the waker if the old waker will awaken the same task. + match &*self.waker.get() { + Some(old_waker) if old_waker.will_wake(waker) => (), + _ => *self.waker.get() = Some(waker.clone()), + } // Release the lock. If the state transitioned to include // the `WAKING` bit, this means that at least one wake has @@ -279,8 +289,7 @@ impl AtomicWaker { // nothing to acquire, only release. In case of concurrent // wakers, we need to acquire their releases, so success needs // to do both. - let res = self.state.compare_exchange( - REGISTERING, WAITING, AcqRel, Acquire); + let res = self.state.compare_exchange(REGISTERING, WAITING, AcqRel, Acquire); match res { Ok(_) => { @@ -344,9 +353,7 @@ impl AtomicWaker { // // We just want to maintain memory safety. It is ok to drop the // call to `register`. - debug_assert!( - state == REGISTERING || - state == REGISTERING | WAKING); + debug_assert!(state == REGISTERING || state == REGISTERING | WAKING); } } } @@ -391,9 +398,8 @@ impl AtomicWaker { // not. // debug_assert!( - state == REGISTERING || - state == REGISTERING | WAKING || - state == WAKING); + state == REGISTERING || state == REGISTERING | WAKING || state == WAKING + ); None } } diff --git a/futures-core/src/task/__internal/mod.rs b/futures-core/src/task/__internal/mod.rs index 77e3678075..c248742280 100644 --- a/futures-core/src/task/__internal/mod.rs +++ b/futures-core/src/task/__internal/mod.rs @@ -1,4 +1,7 @@ -#[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] +#[cfg_attr(target_os = "none", cfg(any(target_has_atomic = "ptr", feature = "portable-atomic")))] mod atomic_waker; -#[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] +#[cfg_attr( + target_os = "none", + cfg(any(target_has_atomic = "ptr", feature = "portable-atomic")) +)] pub use self::atomic_waker::AtomicWaker; diff --git a/futures-core/src/task/mod.rs b/futures-core/src/task/mod.rs index f945d5da75..19e4eaecdd 100644 --- a/futures-core/src/task/mod.rs +++ b/futures-core/src/task/mod.rs @@ -7,4 +7,4 @@ mod poll; pub mod __internal; #[doc(no_inline)] -pub use core::task::{Context, Poll, Waker, RawWaker, RawWakerVTable}; +pub use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; diff --git a/futures-core/src/task/poll.rs b/futures-core/src/task/poll.rs index 8fe294c580..607e78e060 100644 --- a/futures-core/src/task/poll.rs +++ b/futures-core/src/task/poll.rs @@ -3,9 +3,10 @@ /// This macro bakes in propagation of `Pending` signals by returning early. #[macro_export] macro_rules! ready { - ($e:expr $(,)?) => (match $e { - $crate::__private::Poll::Ready(t) => t, - $crate::__private::Poll::Pending => - return $crate::__private::Poll::Pending, - }) + ($e:expr $(,)?) => { + match $e { + $crate::task::Poll::Ready(t) => t, + $crate::task::Poll::Pending => return $crate::task::Poll::Pending, + } + }; } diff --git a/futures-executor/Cargo.toml b/futures-executor/Cargo.toml index bc1853b01e..978f8ae018 100644 --- a/futures-executor/Cargo.toml +++ b/futures-executor/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-executor" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.56" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-executor/0.3" description = """ Executors for asynchronous tasks based on the futures-rs library. """ @@ -17,14 +16,17 @@ std = ["futures-core/std", "futures-task/std", "futures-util/std"] thread-pool = ["std", "num_cpus"] [dependencies] -futures-core = { path = "../futures-core", version = "0.3.13", default-features = false } -futures-task = { path = "../futures-task", version = "0.3.13", default-features = false } -futures-util = { path = "../futures-util", version = "0.3.13", default-features = false } +futures-core = { path = "../futures-core", version = "0.3.31", default-features = false } +futures-task = { path = "../futures-task", version = "0.3.31", default-features = false } +futures-util = { path = "../futures-util", version = "0.3.31", default-features = false } num_cpus = { version = "1.8.0", optional = true } [dev-dependencies] -futures = { path = "../futures" } +futures = { path = "../futures", features = ["thread-pool"] } [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/futures-executor/README.md b/futures-executor/README.md new file mode 100644 index 0000000000..724ff5bb33 --- /dev/null +++ b/futures-executor/README.md @@ -0,0 +1,23 @@ +# futures-executor + +Executors for asynchronous tasks based on the futures-rs library. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-executor = "0.3" +``` + +The current `futures-executor` requires Rust 1.56 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-executor/benches/thread_notify.rs b/futures-executor/benches/thread_notify.rs index d8fbec4555..88d0447cf6 100644 --- a/futures-executor/benches/thread_notify.rs +++ b/futures-executor/benches/thread_notify.rs @@ -102,10 +102,7 @@ fn thread_yield_multi_thread(b: &mut Bencher) { }); b.iter(move || { - let y = Yield { - rem: NUM, - tx: tx.clone(), - }; + let y = Yield { rem: NUM, tx: tx.clone() }; block_on(y); }); diff --git a/futures-executor/src/enter.rs b/futures-executor/src/enter.rs index 5895a9efb6..b0e43e0593 100644 --- a/futures-executor/src/enter.rs +++ b/futures-executor/src/enter.rs @@ -1,7 +1,7 @@ use std::cell::Cell; use std::fmt; -thread_local!(static ENTERED: Cell = Cell::new(false)); +std::thread_local!(static ENTERED: Cell = Cell::new(false)); /// Represents an executor context. /// @@ -34,7 +34,7 @@ impl std::error::Error for EnterError {} /// executor. /// /// Executor implementations should call this function before beginning to -/// execute a tasks, and drop the returned [`Enter`](Enter) value after +/// execute a task, and drop the returned [`Enter`](Enter) value after /// completing task execution: /// /// ``` diff --git a/futures-executor/src/lib.rs b/futures-executor/src/lib.rs index b6796490af..a0dc49d7ba 100644 --- a/futures-executor/src/lib.rs +++ b/futures-executor/src/lib.rs @@ -36,29 +36,33 @@ //! [`spawn_obj`]: https://docs.rs/futures/0.3/futures/task/trait.Spawn.html#tymethod.spawn_obj //! [`spawn_local_obj`]: https://docs.rs/futures/0.3/futures/task/trait.LocalSpawn.html#tymethod.spawn_local_obj -#![cfg_attr(not(feature = "std"), no_std)] - -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] - +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, unsafe_op_in_unsafe_fn)] #![cfg_attr(docsrs, feature(doc_cfg))] +#[cfg(feature = "std")] +extern crate std; + #[cfg(feature = "std")] mod local_pool; #[cfg(feature = "std")] pub use crate::local_pool::{block_on, block_on_stream, BlockingStream, LocalPool, LocalSpawner}; -#[cfg(feature = "thread-pool")] -#[cfg(feature = "std")] -mod unpark_mutex; #[cfg(feature = "thread-pool")] #[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] #[cfg(feature = "std")] mod thread_pool; #[cfg(feature = "thread-pool")] +#[cfg(feature = "std")] +mod unpark_mutex; +#[cfg(feature = "thread-pool")] #[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] #[cfg(feature = "std")] pub use crate::thread_pool::{ThreadPool, ThreadPoolBuilder}; diff --git a/futures-executor/src/local_pool.rs b/futures-executor/src/local_pool.rs index 156d5cc642..90c2a41520 100644 --- a/futures-executor/src/local_pool.rs +++ b/futures-executor/src/local_pool.rs @@ -10,8 +10,12 @@ use futures_util::stream::StreamExt; use std::cell::RefCell; use std::ops::{Deref, DerefMut}; use std::rc::{Rc, Weak}; -use std::sync::{Arc, atomic::{AtomicBool, Ordering}}; +use std::sync::{ + atomic::{AtomicBool, Ordering}, + Arc, +}; use std::thread::{self, Thread}; +use std::vec::Vec; /// A single-threaded task pool for polling futures to completion. /// @@ -30,8 +34,7 @@ pub struct LocalPool { incoming: Rc, } -/// A handle to a [`LocalPool`](LocalPool) that implements -/// [`Spawn`](futures_task::Spawn). +/// A handle to a [`LocalPool`] that implements [`Spawn`](futures_task::Spawn). #[derive(Clone, Debug)] pub struct LocalSpawner { incoming: Weak, @@ -50,7 +53,7 @@ pub(crate) struct ThreadNotify { unparked: AtomicBool, } -thread_local! { +std::thread_local! { static CURRENT_THREAD_NOTIFY: Arc = Arc::new(ThreadNotify { thread: thread::current(), unparked: AtomicBool::new(false), @@ -60,7 +63,7 @@ thread_local! { impl ArcWake for ThreadNotify { fn wake_by_ref(arc_self: &Arc) { // Make sure the wakeup is remembered until the next `park()`. - let unparked = arc_self.unparked.swap(true, Ordering::Relaxed); + let unparked = arc_self.unparked.swap(true, Ordering::Release); if !unparked { // If the thread has not been unparked yet, it must be done // now. If it was actually parked, it will run again, @@ -87,49 +90,32 @@ fn run_executor) -> Poll>(mut f: F) -> T { if let Poll::Ready(t) = f(&mut cx) { return t; } - // Consume the wakeup that occurred while executing `f`, if any. - let unparked = thread_notify.unparked.swap(false, Ordering::Acquire); - if !unparked { + + // Wait for a wakeup. + while !thread_notify.unparked.swap(false, Ordering::Acquire) { // No wakeup occurred. It may occur now, right before parking, // but in that case the token made available by `unpark()` // is guaranteed to still be available and `park()` is a no-op. thread::park(); - // When the thread is unparked, `unparked` will have been set - // and needs to be unset before the next call to `f` to avoid - // a redundant loop iteration. - thread_notify.unparked.store(false, Ordering::Release); } } }) } -fn poll_executor) -> T>(mut f: F) -> T { - let _enter = enter().expect( - "cannot execute `LocalPool` executor from within \ - another executor", - ); - - CURRENT_THREAD_NOTIFY.with(|thread_notify| { - let waker = waker_ref(thread_notify); - let mut cx = Context::from_waker(&waker); - f(&mut cx) - }) +/// Check for a wakeup, but don't consume it. +fn woken() -> bool { + CURRENT_THREAD_NOTIFY.with(|thread_notify| thread_notify.unparked.load(Ordering::Acquire)) } impl LocalPool { /// Create a new, empty pool of tasks. pub fn new() -> Self { - Self { - pool: FuturesUnordered::new(), - incoming: Default::default(), - } + Self { pool: FuturesUnordered::new(), incoming: Default::default() } } /// Get a clonable handle to the pool as a [`Spawn`]. pub fn spawner(&self) -> LocalSpawner { - LocalSpawner { - incoming: Rc::downgrade(&self.incoming), - } + LocalSpawner { incoming: Rc::downgrade(&self.incoming) } } /// Run all tasks in the pool to completion. @@ -214,20 +200,26 @@ impl LocalPool { /// further use of one of the pool's run or poll methods. /// Though only one task will be completed, progress may be made on multiple tasks. pub fn try_run_one(&mut self) -> bool { - poll_executor(|ctx| { + run_executor(|cx| { loop { - let ret = self.poll_pool_once(ctx); - - // return if we have executed a future - if let Poll::Ready(Some(_)) = ret { - return true; + self.drain_incoming(); + + match self.pool.poll_next_unpin(cx) { + // Success! + Poll::Ready(Some(())) => return Poll::Ready(true), + // The pool was empty. + Poll::Ready(None) => return Poll::Ready(false), + Poll::Pending => (), } - // if there are no new incoming futures - // then there is no feature that can make progress - // and we can return without having completed a single future - if self.incoming.borrow().is_empty() { - return false; + if !self.incoming.borrow().is_empty() { + // New tasks were spawned; try again. + continue; + } else if woken() { + // The pool yielded to us, but there's more progress to be made. + return Poll::Pending; + } else { + return Poll::Ready(false); } } }) @@ -259,44 +251,52 @@ impl LocalPool { /// of the pool's run or poll methods. While the function is running, all tasks /// in the pool will try to make progress. pub fn run_until_stalled(&mut self) { - poll_executor(|ctx| { - let _ = self.poll_pool(ctx); + run_executor(|cx| match self.poll_pool(cx) { + // The pool is empty. + Poll::Ready(()) => Poll::Ready(()), + Poll::Pending => { + if woken() { + Poll::Pending + } else { + // We're stalled for now. + Poll::Ready(()) + } + } }); } - // Make maximal progress on the entire pool of spawned task, returning `Ready` - // if the pool is empty and `Pending` if no further progress can be made. + /// Poll `self.pool`, re-filling it with any newly-spawned tasks. + /// Repeat until either the pool is empty, or it returns `Pending`. + /// + /// Returns `Ready` if the pool was empty, and `Pending` otherwise. + /// + /// NOTE: the pool may call `wake`, so `Pending` doesn't necessarily + /// mean that the pool can't make progress. fn poll_pool(&mut self, cx: &mut Context<'_>) -> Poll<()> { - // state for the FuturesUnordered, which will never be used loop { - let ret = self.poll_pool_once(cx); + self.drain_incoming(); - // we queued up some new tasks; add them and poll again + let pool_ret = self.pool.poll_next_unpin(cx); + + // We queued up some new tasks; add them and poll again. if !self.incoming.borrow().is_empty() { continue; } - // no queued tasks; we may be done - match ret { - Poll::Pending => return Poll::Pending, + match pool_ret { + Poll::Ready(Some(())) => continue, Poll::Ready(None) => return Poll::Ready(()), - _ => {} + Poll::Pending => return Poll::Pending, } } } - // Try make minimal progress on the pool of spawned tasks - fn poll_pool_once(&mut self, cx: &mut Context<'_>) -> Poll> { - // empty the incoming queue of newly-spawned tasks - { - let mut incoming = self.incoming.borrow_mut(); - for task in incoming.drain(..) { - self.pool.push(task) - } + /// Empty the incoming queue of newly-spawned tasks. + fn drain_incoming(&mut self) { + let mut incoming = self.incoming.borrow_mut(); + for task in incoming.drain(..) { + self.pool.push(task) } - - // try to execute the next ready future - self.pool.poll_next_unpin(cx) } } @@ -310,8 +310,7 @@ impl Default for LocalPool { /// /// This function will block the caller until the given future has completed. /// -/// Use a [`LocalPool`](LocalPool) if you need finer-grained control over -/// spawned tasks. +/// Use a [`LocalPool`] if you need finer-grained control over spawned tasks. pub fn block_on(f: F) -> F::Output { pin_mut!(f); run_executor(|cx| f.as_mut().poll(cx)) diff --git a/futures-executor/src/thread_pool.rs b/futures-executor/src/thread_pool.rs index 741e6d9c6c..c4442e4eeb 100644 --- a/futures-executor/src/thread_pool.rs +++ b/futures-executor/src/thread_pool.rs @@ -2,12 +2,15 @@ use crate::enter; use crate::unpark_mutex::UnparkMutex; use futures_core::future::Future; use futures_core::task::{Context, Poll}; +use futures_task::{waker_ref, ArcWake}; use futures_task::{FutureObj, Spawn, SpawnError}; -use futures_task::{ArcWake, waker_ref}; use futures_util::future::FutureExt; +use std::boxed::Box; use std::cmp; use std::fmt; +use std::format; use std::io; +use std::string::String; use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::mpsc; use std::sync::{Arc, Mutex}; @@ -42,6 +45,7 @@ pub struct ThreadPoolBuilder { before_stop: Option>, } +#[allow(dead_code)] trait AssertSendSync: Send + Sync {} impl AssertSendSync for ThreadPool {} @@ -54,9 +58,7 @@ struct PoolState { impl fmt::Debug for ThreadPool { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("ThreadPool") - .field("size", &self.state.size) - .finish() + f.debug_struct("ThreadPool").field("size", &self.state.size).finish() } } @@ -100,10 +102,7 @@ impl ThreadPool { pub fn spawn_obj_ok(&self, future: FutureObj<'static, ()>) { let task = Task { future, - wake_handle: Arc::new(WakeHandle { - exec: self.clone(), - mutex: UnparkMutex::new(), - }), + wake_handle: Arc::new(WakeHandle { exec: self.clone(), mutex: UnparkMutex::new() }), exec: self.clone(), }; self.state.send(Message::Run(task)); @@ -113,12 +112,15 @@ impl ThreadPool { /// completion. /// /// ``` + /// # { /// use futures::executor::ThreadPool; /// /// let pool = ThreadPool::new().unwrap(); /// /// let future = async { /* ... */ }; /// pool.spawn_ok(future); + /// # } + /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 /// ``` /// /// > **Note**: This method is similar to `SpawnExt::spawn`, except that @@ -132,10 +134,7 @@ impl ThreadPool { } impl Spawn for ThreadPool { - fn spawn_obj( - &self, - future: FutureObj<'static, ()>, - ) -> Result<(), SpawnError> { + fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { self.spawn_obj_ok(future); Ok(()) } @@ -146,10 +145,12 @@ impl PoolState { self.tx.lock().unwrap().send(msg).unwrap(); } - fn work(&self, - idx: usize, - after_start: Option>, - before_stop: Option>) { + fn work( + &self, + idx: usize, + after_start: Option>, + before_stop: Option>, + ) { let _scope = enter().unwrap(); if let Some(after_start) = after_start { after_start(idx); @@ -241,7 +242,8 @@ impl ThreadPoolBuilder { /// The closure provided will receive an index corresponding to the worker /// thread it's running on. pub fn after_start(&mut self, f: F) -> &mut Self - where F: Fn(usize) + Send + Sync + 'static + where + F: Fn(usize) + Send + Sync + 'static, { self.after_start = Some(Arc::new(f)); self @@ -250,13 +252,14 @@ impl ThreadPoolBuilder { /// Execute closure `f` just prior to shutting down each worker thread. /// /// This hook is intended for bookkeeping and monitoring. - /// The closure `f` will be dropped after the `builder` is droppped + /// The closure `f` will be dropped after the `builder` is dropped /// and all threads in the pool have executed it. /// /// The closure provided will receive an index corresponding to the worker /// thread it's running on. pub fn before_stop(&mut self, f: F) -> &mut Self - where F: Fn(usize) + Send + Sync + 'static + where + F: Fn(usize) + Send + Sync + 'static, { self.before_stop = Some(Arc::new(f)); self @@ -328,14 +331,11 @@ impl Task { Poll::Pending => {} Poll::Ready(()) => return wake_handle.mutex.complete(), } - let task = Self { - future, - wake_handle: wake_handle.clone(), - exec, - }; + let task = Self { future, wake_handle: wake_handle.clone(), exec }; match wake_handle.mutex.wait(task) { Ok(()) => return, // we've waited - Err(task) => { // someone's notified us + Err(task) => { + // someone's notified us future = task.future; exec = task.exec; } @@ -347,17 +347,14 @@ impl Task { impl fmt::Debug for Task { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Task") - .field("contents", &"...") - .finish() + f.debug_struct("Task").field("contents", &"...").finish() } } impl ArcWake for WakeHandle { fn wake_by_ref(arc_self: &Arc) { - match arc_self.mutex.notify() { - Ok(task) => arc_self.exec.state.send(Message::Run(task)), - Err(()) => {} + if let Ok(task) = arc_self.mutex.notify() { + arc_self.exec.state.send(Message::Run(task)) } } } @@ -365,18 +362,22 @@ impl ArcWake for WakeHandle { #[cfg(test)] mod tests { use super::*; - use std::sync::mpsc; #[test] fn test_drop_after_start() { - let (tx, rx) = mpsc::sync_channel(2); - let _cpu_pool = ThreadPoolBuilder::new() - .pool_size(2) - .after_start(move |_| tx.send(1).unwrap()).create().unwrap(); - - // After ThreadPoolBuilder is deconstructed, the tx should be droped - // so that we can use rx as an iterator. - let count = rx.into_iter().count(); - assert_eq!(count, 2); + { + let (tx, rx) = mpsc::sync_channel(2); + let _cpu_pool = ThreadPoolBuilder::new() + .pool_size(2) + .after_start(move |_| tx.send(1).unwrap()) + .create() + .unwrap(); + + // After ThreadPoolBuilder is deconstructed, the tx should be dropped + // so that we can use rx as an iterator. + let count = rx.into_iter().count(); + assert_eq!(count, 2); + } + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } } diff --git a/futures-executor/src/unpark_mutex.rs b/futures-executor/src/unpark_mutex.rs index c49c64cc39..f45164be9d 100644 --- a/futures-executor/src/unpark_mutex.rs +++ b/futures-executor/src/unpark_mutex.rs @@ -29,25 +29,22 @@ unsafe impl Sync for UnparkMutex {} // transitions: // The task is blocked, waiting on an event -const WAITING: usize = 0; // --> POLLING +const WAITING: usize = 0; // --> POLLING // The task is actively being polled by a thread; arrival of additional events // of interest should move it to the REPOLL state -const POLLING: usize = 1; // --> WAITING, REPOLL, or COMPLETE +const POLLING: usize = 1; // --> WAITING, REPOLL, or COMPLETE // The task is actively being polled, but will need to be re-polled upon // completion to ensure that all events were observed. -const REPOLL: usize = 2; // --> POLLING +const REPOLL: usize = 2; // --> POLLING // The task has finished executing (either successfully or with an error/panic) -const COMPLETE: usize = 3; // No transitions out +const COMPLETE: usize = 3; // No transitions out impl UnparkMutex { pub(crate) fn new() -> Self { - Self { - status: AtomicUsize::new(WAITING), - inner: UnsafeCell::new(None), - } + Self { status: AtomicUsize::new(WAITING), inner: UnsafeCell::new(None) } } /// Attempt to "notify" the mutex that a poll should occur. @@ -62,8 +59,7 @@ impl UnparkMutex { match status { // The task is idle, so try to run it immediately. WAITING => { - match self.status.compare_exchange(WAITING, POLLING, - SeqCst, SeqCst) { + match self.status.compare_exchange(WAITING, POLLING, SeqCst, SeqCst) { Ok(_) => { let data = unsafe { // SAFETY: we've ensured mutual exclusion via @@ -82,13 +78,10 @@ impl UnparkMutex { // The task is being polled, so we need to record that it should // be *repolled* when complete. - POLLING => { - match self.status.compare_exchange(POLLING, REPOLL, - SeqCst, SeqCst) { - Ok(_) => return Err(()), - Err(cur) => status = cur, - } - } + POLLING => match self.status.compare_exchange(POLLING, REPOLL, SeqCst, SeqCst) { + Ok(_) => return Err(()), + Err(cur) => status = cur, + }, // The task is already scheduled for polling, or is complete, so // we've got nothing to do. @@ -115,7 +108,7 @@ impl UnparkMutex { /// Callable only from the `POLLING`/`REPOLL` states, i.e. between /// successful calls to `notify` and `wait`/`complete`. pub(crate) unsafe fn wait(&self, data: D) -> Result<(), D> { - *self.inner.get() = Some(data); + unsafe { *self.inner.get() = Some(data) } match self.status.compare_exchange(POLLING, WAITING, SeqCst, SeqCst) { // no unparks came in while we were running @@ -126,7 +119,7 @@ impl UnparkMutex { Err(status) => { assert_eq!(status, REPOLL); self.status.store(POLLING, SeqCst); - Err((*self.inner.get()).take().unwrap()) + Err(unsafe { (*self.inner.get()).take().unwrap() }) } } } diff --git a/futures-executor/tests/local_pool.rs b/futures-executor/tests/local_pool.rs index b31f1034cb..d9b2e9d797 100644 --- a/futures-executor/tests/local_pool.rs +++ b/futures-executor/tests/local_pool.rs @@ -1,16 +1,17 @@ use futures::channel::oneshot; use futures::executor::LocalPool; -use futures::future::{self, Future, lazy, poll_fn}; -use futures::task::{Context, Poll, Spawn, LocalSpawn, Waker}; +use futures::future::{self, lazy, poll_fn, Future}; +use futures::task::{Context, LocalSpawn, LocalSpawnExt, Poll, Spawn, SpawnExt, Waker}; use std::cell::{Cell, RefCell}; +use std::marker::PhantomData; use std::pin::Pin; use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; use std::thread; use std::time::Duration; -use std::sync::Arc; -use std::sync::atomic::{Ordering, AtomicBool}; -struct Pending(Rc<()>); +struct Pending(PhantomData>); impl Future for Pending { type Output = (); @@ -21,7 +22,7 @@ impl Future for Pending { } fn pending() -> Pending { - Pending(Rc::new(())) + Pending(PhantomData) } #[test] @@ -52,9 +53,14 @@ fn run_until_executes_spawned() { let (tx, rx) = oneshot::channel(); let mut pool = LocalPool::new(); let spawn = pool.spawner(); - spawn.spawn_local_obj(Box::pin(lazy(move |_| { - tx.send(()).unwrap(); - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + tx.send(()).unwrap(); + })) + .into(), + ) + .unwrap(); pool.run_until(rx).unwrap(); } @@ -74,18 +80,27 @@ fn run_executes_spawned() { let spawn = pool.spawner(); let spawn2 = pool.spawner(); - spawn.spawn_local_obj(Box::pin(lazy(move |_| { - spawn2.spawn_local_obj(Box::pin(lazy(move |_| { - cnt2.set(cnt2.get() + 1); - })).into()).unwrap(); - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + spawn2 + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt2.set(cnt2.get() + 1); + })) + .into(), + ) + .unwrap(); + })) + .into(), + ) + .unwrap(); pool.run(); assert_eq!(cnt.get(), 1); } - #[test] fn run_spawn_many() { const ITER: usize = 200; @@ -97,9 +112,14 @@ fn run_spawn_many() { for _ in 0..ITER { let cnt = cnt.clone(); - spawn.spawn_local_obj(Box::pin(lazy(move |_| { - cnt.set(cnt.get() + 1); - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt.set(cnt.get() + 1); + })) + .into(), + ) + .unwrap(); } pool.run(); @@ -126,9 +146,14 @@ fn try_run_one_executes_one_ready() { spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); let cnt = cnt.clone(); - spawn.spawn_local_obj(Box::pin(lazy(move |_| { - cnt.set(cnt.get() + 1); - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt.set(cnt.get() + 1); + })) + .into(), + ) + .unwrap(); spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); } @@ -154,15 +179,20 @@ fn try_run_one_returns_on_no_progress() { { let cnt = cnt.clone(); let waker = waker.clone(); - spawn.spawn_local_obj(Box::pin(poll_fn(move |ctx| { - cnt.set(cnt.get() + 1); - waker.set(Some(ctx.waker().clone())); - if cnt.get() == ITER { - Poll::Ready(()) - } else { - Poll::Pending - } - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(poll_fn(move |ctx| { + cnt.set(cnt.get() + 1); + waker.set(Some(ctx.waker().clone())); + if cnt.get() == ITER { + Poll::Ready(()) + } else { + Poll::Pending + } + })) + .into(), + ) + .unwrap(); } for i in 0..ITER - 1 { @@ -185,16 +215,21 @@ fn try_run_one_runs_sub_futures() { let inner_spawner = spawn.clone(); let cnt1 = cnt.clone(); - spawn.spawn_local_obj(Box::pin(poll_fn(move |_| { - cnt1.set(cnt1.get() + 1); - - let cnt2 = cnt1.clone(); - inner_spawner.spawn_local_obj(Box::pin(lazy(move |_|{ - cnt2.set(cnt2.get() + 1) - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(poll_fn(move |_| { + cnt1.set(cnt1.get() + 1); - Poll::Pending - })).into()).unwrap(); + let cnt2 = cnt1.clone(); + inner_spawner + .spawn_local_obj(Box::pin(lazy(move |_| cnt2.set(cnt2.get() + 1))).into()) + .unwrap(); + + Poll::Pending + })) + .into(), + ) + .unwrap(); pool.try_run_one(); assert_eq!(cnt.get(), 2); @@ -214,12 +249,12 @@ fn run_until_stalled_returns_multiple_times() { let cnt = Rc::new(Cell::new(0)); let cnt1 = cnt.clone(); - spawn.spawn_local_obj(Box::pin(lazy(move |_|{ cnt1.set(cnt1.get() + 1) })).into()).unwrap(); + spawn.spawn_local_obj(Box::pin(lazy(move |_| cnt1.set(cnt1.get() + 1))).into()).unwrap(); pool.run_until_stalled(); assert_eq!(cnt.get(), 1); let cnt2 = cnt.clone(); - spawn.spawn_local_obj(Box::pin(lazy(move |_|{ cnt2.set(cnt2.get() + 1) })).into()).unwrap(); + spawn.spawn_local_obj(Box::pin(lazy(move |_| cnt2.set(cnt2.get() + 1))).into()).unwrap(); pool.run_until_stalled(); assert_eq!(cnt.get(), 2); } @@ -232,16 +267,21 @@ fn run_until_stalled_runs_spawned_sub_futures() { let inner_spawner = spawn.clone(); let cnt1 = cnt.clone(); - spawn.spawn_local_obj(Box::pin(poll_fn(move |_| { - cnt1.set(cnt1.get() + 1); - - let cnt2 = cnt1.clone(); - inner_spawner.spawn_local_obj(Box::pin(lazy(move |_|{ - cnt2.set(cnt2.get() + 1) - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(poll_fn(move |_| { + cnt1.set(cnt1.get() + 1); - Poll::Pending - })).into()).unwrap(); + let cnt2 = cnt1.clone(); + inner_spawner + .spawn_local_obj(Box::pin(lazy(move |_| cnt2.set(cnt2.get() + 1))).into()) + .unwrap(); + + Poll::Pending + })) + .into(), + ) + .unwrap(); pool.run_until_stalled(); assert_eq!(cnt.get(), 2); @@ -249,7 +289,7 @@ fn run_until_stalled_runs_spawned_sub_futures() { #[test] fn run_until_stalled_executes_all_ready() { - const ITER: usize = 200; + const ITER: usize = if cfg!(miri) { 50 } else { 200 }; const PER_ITER: usize = 3; let cnt = Rc::new(Cell::new(0)); @@ -262,9 +302,14 @@ fn run_until_stalled_executes_all_ready() { spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); let cnt = cnt.clone(); - spawn.spawn_local_obj(Box::pin(lazy(move |_| { - cnt.set(cnt.get() + 1); - })).into()).unwrap(); + spawn + .spawn_local_obj( + Box::pin(lazy(move |_| { + cnt.set(cnt.get() + 1); + })) + .into(), + ) + .unwrap(); // also add some pending tasks to test if they are ignored spawn.spawn_local_obj(Box::pin(pending()).into()).unwrap(); @@ -281,10 +326,15 @@ fn nesting_run() { let mut pool = LocalPool::new(); let spawn = pool.spawner(); - spawn.spawn_obj(Box::pin(lazy(|_| { - let mut pool = LocalPool::new(); - pool.run(); - })).into()).unwrap(); + spawn + .spawn_obj( + Box::pin(lazy(|_| { + let mut pool = LocalPool::new(); + pool.run(); + })) + .into(), + ) + .unwrap(); pool.run(); } @@ -295,10 +345,15 @@ fn nesting_run_run_until_stalled() { let mut pool = LocalPool::new(); let spawn = pool.spawner(); - spawn.spawn_obj(Box::pin(lazy(|_| { - let mut pool = LocalPool::new(); - pool.run_until_stalled(); - })).into()).unwrap(); + spawn + .spawn_obj( + Box::pin(lazy(|_| { + let mut pool = LocalPool::new(); + pool.run_until_stalled(); + })) + .into(), + ) + .unwrap(); pool.run(); } @@ -342,32 +397,26 @@ fn tasks_are_scheduled_fairly() { let mut pool = LocalPool::new(); let spawn = pool.spawner(); - spawn.spawn_local_obj(Box::pin(Spin { - state: state.clone(), - idx: 0, - }).into()).unwrap(); + spawn.spawn_local_obj(Box::pin(Spin { state: state.clone(), idx: 0 }).into()).unwrap(); - spawn.spawn_local_obj(Box::pin(Spin { - state, - idx: 1, - }).into()).unwrap(); + spawn.spawn_local_obj(Box::pin(Spin { state, idx: 1 }).into()).unwrap(); pool.run(); } // Tests that the use of park/unpark in user-code has no -// effect on the expected behaviour of the executor. +// effect on the expected behavior of the executor. #[test] fn park_unpark_independence() { let mut done = false; let future = future::poll_fn(move |cx| { if done { - return Poll::Ready(()) + return Poll::Ready(()); } done = true; cx.waker().clone().wake(); // (*) - // some user-code that temporarily parks the thread + // some user-code that temporarily parks the thread let test = thread::current(); let latch = Arc::new(AtomicBool::new(false)); let signal = latch.clone(); @@ -385,3 +434,64 @@ fn park_unpark_independence() { futures::executor::block_on(future) } +struct SelfWaking { + wakeups_remaining: Rc>, +} + +impl Future for SelfWaking { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + if *self.wakeups_remaining.borrow() != 0 { + *self.wakeups_remaining.borrow_mut() -= 1; + cx.waker().wake_by_ref(); + } + + Poll::Pending + } +} + +/// Regression test for https://github.com/rust-lang/futures-rs/pull/2593 +/// +/// The issue was that self-waking futures could cause `run_until_stalled` +/// to exit early, even when progress could still be made. +#[test] +fn self_waking_run_until_stalled() { + let wakeups_remaining = Rc::new(RefCell::new(10)); + + let mut pool = LocalPool::new(); + let spawner = pool.spawner(); + for _ in 0..3 { + let wakeups_remaining = Rc::clone(&wakeups_remaining); + spawner.spawn_local(SelfWaking { wakeups_remaining }).unwrap(); + } + + // This should keep polling until there are no more wakeups. + pool.run_until_stalled(); + + assert_eq!(*wakeups_remaining.borrow(), 0); +} + +/// Regression test for https://github.com/rust-lang/futures-rs/pull/2593 +/// +/// The issue was that self-waking futures could cause `try_run_one` +/// to exit early, even when progress could still be made. +#[test] +fn self_waking_try_run_one() { + let wakeups_remaining = Rc::new(RefCell::new(10)); + + let mut pool = LocalPool::new(); + let spawner = pool.spawner(); + for _ in 0..3 { + let wakeups_remaining = Rc::clone(&wakeups_remaining); + spawner.spawn_local(SelfWaking { wakeups_remaining }).unwrap(); + } + + spawner.spawn(future::ready(())).unwrap(); + + // The `ready` future should complete. + assert!(pool.try_run_one()); + + // The self-waking futures are each polled once. + assert_eq!(*wakeups_remaining.borrow(), 7); +} diff --git a/futures-io/Cargo.toml b/futures-io/Cargo.toml index 2edbdc37ba..e5320d4281 100644 --- a/futures-io/Cargo.toml +++ b/futures-io/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-io" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.36" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-io/0.3" description = """ The `AsyncRead`, `AsyncWrite`, `AsyncSeek`, and `AsyncBufRead` traits for the futures-rs library. """ @@ -19,10 +18,12 @@ std = [] # These features are outside of the normal semver guarantees and require the # `unstable` feature as an explicit opt-in to unstable API. unstable = [] -read-initializer = [] [dependencies] [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/futures-io/README.md b/futures-io/README.md new file mode 100644 index 0000000000..da6eec28ba --- /dev/null +++ b/futures-io/README.md @@ -0,0 +1,23 @@ +# futures-io + +The `AsyncRead`, `AsyncWrite`, `AsyncSeek`, and `AsyncBufRead` traits for the futures-rs library. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-io = "0.3" +``` + +The current `futures-io` requires Rust 1.36 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-io/src/lib.rs b/futures-io/src/lib.rs index 48de896fa8..9c2fa056ad 100644 --- a/futures-io/src/lib.rs +++ b/futures-io/src/lib.rs @@ -8,38 +8,34 @@ //! All items of this library are only available when the `std` feature of this //! library is activated, and it is activated by default. -#![cfg_attr(all(feature = "read-initializer", feature = "std"), feature(read_initializer))] - -#![cfg_attr(not(feature = "std"), no_std)] - -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] - +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, /* unsafe_op_in_unsafe_fn */)] // unsafe_op_in_unsafe_fn requires Rust 1.52 #![cfg_attr(docsrs, feature(doc_cfg))] -#[cfg(all(feature = "read-initializer", not(feature = "unstable")))] -compile_error!("The `read-initializer` feature requires the `unstable` feature as an explicit opt-in to unstable features"); +#[cfg(feature = "std")] +extern crate std; #[cfg(feature = "std")] mod if_std { + use std::boxed::Box; use std::io; use std::ops::DerefMut; use std::pin::Pin; use std::task::{Context, Poll}; + use std::vec::Vec; // Re-export some types from `std::io` so that users don't have to deal // with conflicts when `use`ing `futures::io` and `std::io`. #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 #[doc(no_inline)] - pub use io::{Error, ErrorKind, Result, IoSlice, IoSliceMut, SeekFrom}; - #[cfg(feature = "read-initializer")] - #[cfg_attr(docsrs, doc(cfg(feature = "read-initializer")))] - #[doc(no_inline)] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use io::Initializer; + pub use io::{Error, ErrorKind, IoSlice, IoSliceMut, Result, SeekFrom}; /// Read bytes asynchronously. /// @@ -49,27 +45,6 @@ mod if_std { /// for wakeup and return if data is not yet available, rather than blocking /// the calling thread. pub trait AsyncRead { - /// Determines if this `AsyncRead`er can work with buffers of - /// uninitialized memory. - /// - /// The default implementation returns an initializer which will zero - /// buffers. - /// - /// This method is only available when the `read-initializer` feature of this - /// library is activated. - /// - /// # Safety - /// - /// This method is `unsafe` because an `AsyncRead`er could otherwise - /// return a non-zeroing `Initializer` from another `AsyncRead` type - /// without an `unsafe` block. - #[cfg(feature = "read-initializer")] - #[cfg_attr(docsrs, doc(cfg(feature = "read-initializer")))] - #[inline] - unsafe fn initializer(&self) -> Initializer { - Initializer::zeroing() - } - /// Attempt to read from the `AsyncRead` into `buf`. /// /// On success, returns `Poll::Ready(Ok(num_bytes_read))`. @@ -85,8 +60,11 @@ mod if_std { /// `Interrupted`. Implementations must convert `WouldBlock` into /// `Poll::Pending` and either internally retry or convert /// `Interrupted` into another error kind. - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> Poll>; + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll>; /// Attempt to read from the `AsyncRead` into `bufs` using vectored /// IO operations. @@ -110,9 +88,11 @@ mod if_std { /// `Interrupted`. Implementations must convert `WouldBlock` into /// `Poll::Pending` and either internally retry or convert /// `Interrupted` into another error kind. - fn poll_read_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) - -> Poll> - { + fn poll_read_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &mut [IoSliceMut<'_>], + ) -> Poll> { for b in bufs { if !b.is_empty() { return self.poll_read(cx, b); @@ -149,8 +129,11 @@ mod if_std { /// /// `poll_write` must try to make progress by flushing the underlying object if /// that is the only way the underlying object can become writable again. - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) - -> Poll>; + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll>; /// Attempt to write bytes from `bufs` into the object using vectored /// IO operations. @@ -175,9 +158,11 @@ mod if_std { /// `Interrupted`. Implementations must convert `WouldBlock` into /// `Poll::Pending` and either internally retry or convert /// `Interrupted` into another error kind. - fn poll_write_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>]) - -> Poll> - { + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { for b in bufs { if !b.is_empty() { return self.poll_write(cx, b); @@ -252,8 +237,11 @@ mod if_std { /// `Interrupted`. Implementations must convert `WouldBlock` into /// `Poll::Pending` and either internally retry or convert /// `Interrupted` into another error kind. - fn poll_seek(self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom) - -> Poll>; + fn poll_seek( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + pos: SeekFrom, + ) -> Poll>; } /// Read bytes asynchronously. @@ -292,8 +280,7 @@ mod if_std { /// `Interrupted`. Implementations must convert `WouldBlock` into /// `Poll::Pending` and either internally retry or convert /// `Interrupted` into another error kind. - fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll>; + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll>; /// Tells this buffer that `amt` bytes have been consumed from the buffer, /// so they should no longer be returned in calls to [`poll_read`]. @@ -315,23 +302,22 @@ mod if_std { macro_rules! deref_async_read { () => { - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - (**self).initializer() - } - - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { Pin::new(&mut **self).poll_read(cx, buf) } - fn poll_read_vectored(mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) - -> Poll> - { + fn poll_read_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &mut [IoSliceMut<'_>], + ) -> Poll> { Pin::new(&mut **self).poll_read_vectored(cx, bufs) } - } + }; } impl AsyncRead for Box { @@ -347,43 +333,41 @@ mod if_std { P: DerefMut + Unpin, P::Target: AsyncRead, { - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - (**self).initializer() - } - - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { self.get_mut().as_mut().poll_read(cx, buf) } - fn poll_read_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) - -> Poll> - { + fn poll_read_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &mut [IoSliceMut<'_>], + ) -> Poll> { self.get_mut().as_mut().poll_read_vectored(cx, bufs) } } macro_rules! delegate_async_read_to_stdio { () => { - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - io::Read::initializer(self) - } - - fn poll_read(mut self: Pin<&mut Self>, _: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { + fn poll_read( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { Poll::Ready(io::Read::read(&mut *self, buf)) } - fn poll_read_vectored(mut self: Pin<&mut Self>, _: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) - -> Poll> - { + fn poll_read_vectored( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + bufs: &mut [IoSliceMut<'_>], + ) -> Poll> { Poll::Ready(io::Read::read_vectored(&mut *self, bufs)) } - } + }; } impl AsyncRead for &[u8] { @@ -392,15 +376,19 @@ mod if_std { macro_rules! deref_async_write { () => { - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) - -> Poll> - { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { Pin::new(&mut **self).poll_write(cx, buf) } - fn poll_write_vectored(mut self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>]) - -> Poll> - { + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { Pin::new(&mut **self).poll_write_vectored(cx, bufs) } @@ -411,7 +399,7 @@ mod if_std { fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut **self).poll_close(cx) } - } + }; } impl AsyncWrite for Box { @@ -427,15 +415,19 @@ mod if_std { P: DerefMut + Unpin, P::Target: AsyncWrite, { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) - -> Poll> - { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { self.get_mut().as_mut().poll_write(cx, buf) } - fn poll_write_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>]) - -> Poll> - { + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { self.get_mut().as_mut().poll_write_vectored(cx, bufs) } @@ -450,15 +442,19 @@ mod if_std { macro_rules! delegate_async_write_to_stdio { () => { - fn poll_write(mut self: Pin<&mut Self>, _: &mut Context<'_>, buf: &[u8]) - -> Poll> - { + fn poll_write( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { Poll::Ready(io::Write::write(&mut *self, buf)) } - fn poll_write_vectored(mut self: Pin<&mut Self>, _: &mut Context<'_>, bufs: &[IoSlice<'_>]) - -> Poll> - { + fn poll_write_vectored( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { Poll::Ready(io::Write::write_vectored(&mut *self, bufs)) } @@ -469,7 +465,7 @@ mod if_std { fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.poll_flush(cx) } - } + }; } impl AsyncWrite for Vec { @@ -478,12 +474,14 @@ mod if_std { macro_rules! deref_async_seek { () => { - fn poll_seek(mut self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom) - -> Poll> - { + fn poll_seek( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + pos: SeekFrom, + ) -> Poll> { Pin::new(&mut **self).poll_seek(cx, pos) } - } + }; } impl AsyncSeek for Box { @@ -499,25 +497,25 @@ mod if_std { P: DerefMut + Unpin, P::Target: AsyncSeek, { - fn poll_seek(self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom) - -> Poll> - { + fn poll_seek( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + pos: SeekFrom, + ) -> Poll> { self.get_mut().as_mut().poll_seek(cx, pos) } } macro_rules! deref_async_buf_read { () => { - fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll> - { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut **self.get_mut()).poll_fill_buf(cx) } fn consume(mut self: Pin<&mut Self>, amt: usize) { Pin::new(&mut **self).consume(amt) } - } + }; } impl AsyncBufRead for Box { @@ -533,9 +531,7 @@ mod if_std { P: DerefMut + Unpin, P::Target: AsyncBufRead, { - fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll> - { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.get_mut().as_mut().poll_fill_buf(cx) } @@ -546,16 +542,14 @@ mod if_std { macro_rules! delegate_async_buf_read_to_stdio { () => { - fn poll_fill_buf(self: Pin<&mut Self>, _: &mut Context<'_>) - -> Poll> - { + fn poll_fill_buf(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { Poll::Ready(io::BufRead::fill_buf(self.get_mut())) } fn consume(self: Pin<&mut Self>, amt: usize) { io::BufRead::consume(self.get_mut(), amt) } - } + }; } impl AsyncBufRead for &[u8] { diff --git a/futures-macro/Cargo.toml b/futures-macro/Cargo.toml index 090f73608d..fdd7be3dd8 100644 --- a/futures-macro/Cargo.toml +++ b/futures-macro/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-macro" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Taylor Cramer ", "Taiki Endo "] +rust-version = "1.56" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-macro/0.3" description = """ The futures-rs procedural macro implementations. """ @@ -17,7 +16,9 @@ proc-macro = true [features] [dependencies] -proc-macro2 = "1.0" -proc-macro-hack = "0.5.19" +proc-macro2 = "1.0.60" quote = "1.0" -syn = { version = "1.0", features = ["full"] } +syn = { version = "2.0.52", features = ["full"] } + +[lints] +workspace = true diff --git a/futures-macro/src/executor.rs b/futures-macro/src/executor.rs new file mode 100644 index 0000000000..7f1d0a93f4 --- /dev/null +++ b/futures-macro/src/executor.rs @@ -0,0 +1,56 @@ +use proc_macro::TokenStream; +use proc_macro2::Span; +use quote::{quote, quote_spanned, ToTokens}; + +pub(crate) fn test(args: TokenStream, item: TokenStream) -> TokenStream { + if !args.is_empty() { + return syn::Error::new_spanned(proc_macro2::TokenStream::from(args), "invalid argument") + .to_compile_error() + .into(); + } + + let mut input = syn::parse_macro_input!(item as syn::ItemFn); + + if input.sig.asyncness.take().is_none() { + return syn::Error::new_spanned(input.sig.fn_token, "Only async functions are supported") + .to_compile_error() + .into(); + } + + // If type mismatch occurs, the current rustc points to the last statement. + let (last_stmt_start_span, last_stmt_end_span) = { + let mut last_stmt = input + .block + .stmts + .last() + .map(ToTokens::into_token_stream) + .unwrap_or_default() + .into_iter(); + // `Span` on stable Rust has a limitation that only points to the first + // token, not the whole tokens. We can work around this limitation by + // using the first/last span of the tokens like + // `syn::Error::new_spanned` does. + let start = last_stmt.next().map_or_else(Span::call_site, |t| t.span()); + let end = last_stmt.last().map_or(start, |t| t.span()); + (start, end) + }; + + let path = quote_spanned! {last_stmt_start_span=> + ::futures_test::__private + }; + let body = &input.block; + input.block.stmts = vec![syn::Stmt::Expr( + syn::parse2(quote_spanned! {last_stmt_end_span=> + #path::block_on(async #body) + }) + .unwrap(), + None, + )]; + + let gen = quote! { + #[::core::prelude::v1::test] + #input + }; + + gen.into() +} diff --git a/futures-macro/src/join.rs b/futures-macro/src/join.rs index 048b62a6cd..0e891b3aad 100644 --- a/futures-macro/src/join.rs +++ b/futures-macro/src/join.rs @@ -1,4 +1,4 @@ -//! The futures-rs `join! macro implementation. +//! The futures-rs `join!` macro implementation. use proc_macro::TokenStream; use proc_macro2::{Span, TokenStream as TokenStream2}; @@ -27,10 +27,7 @@ impl Parse for Join { } } -fn bind_futures( - fut_exprs: Vec, - span: Span, -) -> (Vec, Vec) { +fn bind_futures(fut_exprs: Vec, span: Span) -> (Vec, Vec) { let mut future_let_bindings = Vec::with_capacity(fut_exprs.len()); let future_names: Vec<_> = fut_exprs .into_iter() @@ -41,6 +38,7 @@ fn bind_futures( // Move future into a local so that it is pinned in one place and // is no longer accessible by the end user. let mut #name = __futures_crate::future::maybe_done(#expr); + let mut #name = unsafe { __futures_crate::Pin::new_unchecked(&mut #name) }; }); name }) @@ -61,12 +59,12 @@ pub(crate) fn join(input: TokenStream) -> TokenStream { let poll_futures = future_names.iter().map(|fut| { quote! { __all_done &= __futures_crate::future::Future::poll( - unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }, __cx).is_ready(); + #fut.as_mut(), __cx).is_ready(); } }); let take_outputs = future_names.iter().map(|fut| { quote! { - unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap(), + #fut.as_mut().take_output().unwrap(), } }); @@ -99,17 +97,17 @@ pub(crate) fn try_join(input: TokenStream) -> TokenStream { let poll_futures = future_names.iter().map(|fut| { quote! { if __futures_crate::future::Future::poll( - unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }, __cx).is_pending() + #fut.as_mut(), __cx).is_pending() { __all_done = false; - } else if unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.output_mut().unwrap().is_err() { + } else if #fut.as_mut().output_mut().unwrap().is_err() { // `.err().unwrap()` rather than `.unwrap_err()` so that we don't introduce // a `T: Debug` bound. // Also, for an error type of ! any code after `err().unwrap()` is unreachable. #[allow(unreachable_code)] return __futures_crate::task::Poll::Ready( __futures_crate::Err( - unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().err().unwrap() + #fut.as_mut().take_output().unwrap().err().unwrap() ) ); } @@ -121,7 +119,7 @@ pub(crate) fn try_join(input: TokenStream) -> TokenStream { // an `E: Debug` bound. // Also, for an ok type of ! any code after `ok().unwrap()` is unreachable. #[allow(unreachable_code)] - unsafe { __futures_crate::Pin::new_unchecked(&mut #fut) }.take_output().unwrap().ok().unwrap(), + #fut.as_mut().take_output().unwrap().ok().unwrap(), } }); diff --git a/futures-macro/src/lib.rs b/futures-macro/src/lib.rs index 5f0c47ca89..c1b94785e8 100644 --- a/futures-macro/src/lib.rs +++ b/futures-macro/src/lib.rs @@ -1,43 +1,55 @@ //! The futures-rs procedural macro implementations. -#![recursion_limit = "128"] -#![warn(rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] - -// Since https://github.com/rust-lang/cargo/pull/7700 `proc_macro` is part of the prelude for -// proc-macro crates, but to support older compilers we still need this explicit `extern crate`. -#[allow(unused_extern_crates)] -extern crate proc_macro; +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] use proc_macro::TokenStream; -use proc_macro_hack::proc_macro_hack; +mod executor; mod join; mod select; +mod stream_select; /// The `join!` macro. -#[proc_macro_hack] +#[proc_macro] pub fn join_internal(input: TokenStream) -> TokenStream { crate::join::join(input) } /// The `try_join!` macro. -#[proc_macro_hack] +#[proc_macro] pub fn try_join_internal(input: TokenStream) -> TokenStream { crate::join::try_join(input) } /// The `select!` macro. -#[proc_macro_hack] +#[proc_macro] pub fn select_internal(input: TokenStream) -> TokenStream { crate::select::select(input) } /// The `select_biased!` macro. -#[proc_macro_hack] +#[proc_macro] pub fn select_biased_internal(input: TokenStream) -> TokenStream { crate::select::select_biased(input) } + +// TODO: Change this to doc comment once rustdoc bug fixed: https://github.com/rust-lang/futures-rs/pull/2435 +// The `test` attribute. +#[proc_macro_attribute] +pub fn test_internal(input: TokenStream, item: TokenStream) -> TokenStream { + crate::executor::test(input, item) +} + +/// The `stream_select!` macro. +#[proc_macro] +pub fn stream_select_internal(input: TokenStream) -> TokenStream { + crate::stream_select::stream_select(input.into()) + .unwrap_or_else(syn::Error::into_compile_error) + .into() +} diff --git a/futures-macro/src/select.rs b/futures-macro/src/select.rs index 65b31877ac..ca83a87e4a 100644 --- a/futures-macro/src/select.rs +++ b/futures-macro/src/select.rs @@ -1,10 +1,10 @@ -//! The futures-rs `select! macro implementation. +//! The futures-rs `select!` macro implementation. use proc_macro::TokenStream; use proc_macro2::Span; use quote::{format_ident, quote}; -use syn::{parse_quote, Expr, Ident, Pat, Token}; use syn::parse::{Parse, ParseStream}; +use syn::{parse_quote, Expr, Ident, Pat, Token}; mod kw { syn::custom_keyword!(complete); @@ -51,7 +51,7 @@ impl Parse for Select { CaseKind::Default } else { // ` = ` - let pat = input.parse()?; + let pat = Pat::parse_multi_with_leading_vert(input)?; input.parse::()?; let expr = input.parse()?; CaseKind::Normal(pat, expr) @@ -59,11 +59,14 @@ impl Parse for Select { // `=> ` input.parse::]>()?; - let expr = input.parse::()?; + let expr = Expr::parse_with_earlier_boundary_rule(input)?; // Commas after the expression are only optional if it's a `Block` // or it is the last branch in the `match`. - let is_block = match expr { Expr::Block(_) => true, _ => false }; + let is_block = match expr { + Expr::Block(_) => true, + _ => false, + }; if is_block || input.is_empty() { input.parse::>()?; } else { @@ -76,7 +79,7 @@ impl Parse for Select { CaseKind::Normal(pat, fut_expr) => { select.normal_fut_exprs.push(fut_expr); select.normal_fut_handlers.push((pat, expr)); - }, + } } } @@ -92,22 +95,16 @@ fn declare_result_enum( result_ident: Ident, variants: usize, complete: bool, - span: Span + span: Span, ) -> (Vec, syn::ItemEnum) { // "_0", "_1", "_2" let variant_names: Vec = - (0..variants) - .map(|num| format_ident!("_{}", num, span = span)) - .collect(); + (0..variants).map(|num| format_ident!("_{}", num, span = span)).collect(); let type_parameters = &variant_names; let variants = &variant_names; - let complete_variant = if complete { - Some(quote!(Complete)) - } else { - None - }; + let complete_variant = if complete { Some(quote!(Complete)) } else { None }; let enum_item = parse_quote! { enum #result_ident<#(#type_parameters,)*> { @@ -148,7 +145,9 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream { // bind non-`Ident` future exprs w/ `let` let mut future_let_bindings = Vec::with_capacity(parsed.normal_fut_exprs.len()); - let bound_future_names: Vec<_> = parsed.normal_fut_exprs.into_iter() + let bound_future_names: Vec<_> = parsed + .normal_fut_exprs + .into_iter() .zip(variant_names.iter()) .map(|(expr, variant_name)| { match expr { @@ -164,7 +163,7 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream { __futures_crate::async_await::assert_unpin(&#path); }); path - }, + } _ => { // Bind and pin the resulting Future on the stack. This is // necessary to support direct select! calls on !Unpin @@ -188,8 +187,8 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream { // For each future, make an `&mut dyn FnMut(&mut Context<'_>) -> Option>` // to use for polling that individual future. These will then be put in an array. - let poll_functions = bound_future_names.iter().zip(variant_names.iter()) - .map(|(bound_future_name, variant_name)| { + let poll_functions = bound_future_names.iter().zip(variant_names.iter()).map( + |(bound_future_name, variant_name)| { // Below we lazily create the Pin on the Future below. // This is done in order to avoid allocating memory in the generator // for the Pin variable. @@ -216,7 +215,8 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream { &mut __futures_crate::task::Context<'_> ) -> __futures_crate::Option<__futures_crate::task::Poll<_>> = &mut #variant_name; } - }); + }, + ); let none_polled = if parsed.complete.is_some() { quote! { @@ -229,13 +229,13 @@ fn select_inner(input: TokenStream, random: bool) -> TokenStream { } }; - let branches = parsed.normal_fut_handlers.into_iter() - .zip(variant_names.iter()) - .map(|((pat, expr), variant_name)| { + let branches = parsed.normal_fut_handlers.into_iter().zip(variant_names.iter()).map( + |((pat, expr), variant_name)| { quote! { - #enum_ident::#variant_name(#pat) => { #expr }, + #enum_ident::#variant_name(#pat) => #expr, } - }); + }, + ); let branches = quote! { #( #branches )* }; let complete_branch = parsed.complete.map(|complete_expr| { diff --git a/futures-macro/src/stream_select.rs b/futures-macro/src/stream_select.rs new file mode 100644 index 0000000000..9927b53073 --- /dev/null +++ b/futures-macro/src/stream_select.rs @@ -0,0 +1,113 @@ +use proc_macro2::TokenStream; +use quote::{format_ident, quote, ToTokens}; +use syn::{parse::Parser, punctuated::Punctuated, Expr, Index, Token}; + +/// The `stream_select!` macro. +pub(crate) fn stream_select(input: TokenStream) -> Result { + let args = Punctuated::::parse_terminated.parse2(input)?; + if args.len() < 2 { + return Ok(quote! { + compile_error!("stream select macro needs at least two arguments.") + }); + } + let generic_idents = (0..args.len()).map(|i| format_ident!("_{}", i)).collect::>(); + let field_idents = (0..args.len()).map(|i| format_ident!("__{}", i)).collect::>(); + let field_idents_2 = (0..args.len()).map(|i| format_ident!("___{}", i)).collect::>(); + let field_indices = (0..args.len()).map(Index::from).collect::>(); + let args = args.iter().map(|e| e.to_token_stream()); + + Ok(quote! { + { + #[derive(Debug)] + struct StreamSelect<#(#generic_idents),*> (#(Option<#generic_idents>),*); + + enum StreamEnum<#(#generic_idents),*> { + #( + #generic_idents(#generic_idents) + ),*, + None, + } + + impl __futures_crate::stream::Stream for StreamEnum<#(#generic_idents),*> + where #(#generic_idents: __futures_crate::stream::Stream + ::std::marker::Unpin,)* + { + type Item = ITEM; + + fn poll_next(mut self: ::std::pin::Pin<&mut Self>, cx: &mut __futures_crate::task::Context<'_>) -> __futures_crate::task::Poll> { + match self.get_mut() { + #( + Self::#generic_idents(#generic_idents) => ::std::pin::Pin::new(#generic_idents).poll_next(cx) + ),*, + Self::None => panic!("StreamEnum::None should never be polled!"), + } + } + } + + impl __futures_crate::stream::Stream for StreamSelect<#(#generic_idents),*> + where #(#generic_idents: __futures_crate::stream::Stream + ::std::marker::Unpin,)* + { + type Item = ITEM; + + fn poll_next(mut self: ::std::pin::Pin<&mut Self>, cx: &mut __futures_crate::task::Context<'_>) -> __futures_crate::task::Poll> { + let Self(#(ref mut #field_idents),*) = self.get_mut(); + #( + let mut #field_idents_2 = false; + )* + let mut any_pending = false; + { + let mut stream_array = [#(#field_idents.as_mut().map(|f| StreamEnum::#generic_idents(f)).unwrap_or(StreamEnum::None)),*]; + __futures_crate::async_await::shuffle(&mut stream_array); + + for mut s in stream_array { + if let StreamEnum::None = s { + continue; + } else { + match __futures_crate::stream::Stream::poll_next(::std::pin::Pin::new(&mut s), cx) { + r @ __futures_crate::task::Poll::Ready(Some(_)) => { + return r; + }, + __futures_crate::task::Poll::Pending => { + any_pending = true; + }, + __futures_crate::task::Poll::Ready(None) => { + match s { + #( + StreamEnum::#generic_idents(_) => { #field_idents_2 = true; } + ),*, + StreamEnum::None => panic!("StreamEnum::None should never be polled!"), + } + }, + } + } + } + } + #( + if #field_idents_2 { + *#field_idents = None; + } + )* + if any_pending { + __futures_crate::task::Poll::Pending + } else { + __futures_crate::task::Poll::Ready(None) + } + } + + fn size_hint(&self) -> (usize, Option) { + let mut s = (0, Some(0)); + #( + if let Some(new_hint) = self.#field_indices.as_ref().map(|s| s.size_hint()) { + s.0 += new_hint.0; + // We can change this out for `.zip` when the MSRV is 1.46.0 or higher. + s.1 = s.1.and_then(|a| new_hint.1.map(|b| a + b)); + } + )* + s + } + } + + StreamSelect(#(Some(#args)),*) + + } + }) +} diff --git a/futures-sink/Cargo.toml b/futures-sink/Cargo.toml index 59853a985d..21f8259155 100644 --- a/futures-sink/Cargo.toml +++ b/futures-sink/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-sink" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.36" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-sink/0.3" description = """ The asynchronous `Sink` trait for the futures-rs library. """ @@ -20,3 +19,6 @@ alloc = [] [package.metadata.docs.rs] all-features = true + +[lints] +workspace = true diff --git a/futures-sink/README.md b/futures-sink/README.md new file mode 100644 index 0000000000..1d683e95b5 --- /dev/null +++ b/futures-sink/README.md @@ -0,0 +1,23 @@ +# futures-sink + +The asynchronous `Sink` trait for the futures-rs library. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-sink = "0.3" +``` + +The current `futures-sink` requires Rust 1.36 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-sink/src/lib.rs b/futures-sink/src/lib.rs index 6193f51d6b..52394e8c2a 100644 --- a/futures-sink/src/lib.rs +++ b/futures-sink/src/lib.rs @@ -3,15 +3,20 @@ //! This crate contains the `Sink` trait which allows values to be sent //! asynchronously. -#![cfg_attr(not(feature = "std"), no_std)] -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, /* unsafe_op_in_unsafe_fn */)] // unsafe_op_in_unsafe_fn requires Rust 1.52 #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; use core::ops::DerefMut; use core::pin::Pin; @@ -207,7 +212,10 @@ mod if_alloc { impl + Unpin, Item> Sink for alloc::boxed::Box { type Error = S::Error; - fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_ready( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { Pin::new(&mut **self).poll_ready(cx) } @@ -215,11 +223,17 @@ mod if_alloc { Pin::new(&mut **self).start_send(item) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { Pin::new(&mut **self).poll_flush(cx) } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_close( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { Pin::new(&mut **self).poll_close(cx) } } diff --git a/futures-task/Cargo.toml b/futures-task/Cargo.toml index 764a692c24..f1848dacbd 100644 --- a/futures-task/Cargo.toml +++ b/futures-task/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-task" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.56" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-task/0.3" description = """ Tools for working with tasks. """ @@ -16,9 +15,8 @@ default = ["std"] std = ["alloc"] alloc = [] -# Unstable features -# These features are outside of the normal semver guarantees and require the -# `unstable` feature as an explicit opt-in to unstable API. +# These features are no longer used. +# TODO: remove in the next major version. unstable = [] cfg-target-has-atomic = [] @@ -29,3 +27,6 @@ futures = { path = "../futures" } [package.metadata.docs.rs] all-features = true + +[lints] +workspace = true diff --git a/futures-task/README.md b/futures-task/README.md new file mode 100644 index 0000000000..1ebec2d73d --- /dev/null +++ b/futures-task/README.md @@ -0,0 +1,23 @@ +# futures-task + +Tools for working with tasks. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-task = "0.3" +``` + +The current `futures-task` requires Rust 1.56 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-task/src/future_obj.rs b/futures-task/src/future_obj.rs index 373be244cd..a611382193 100644 --- a/futures-task/src/future_obj.rs +++ b/futures-task/src/future_obj.rs @@ -1,8 +1,8 @@ use core::{ - mem, fmt, future::Future, marker::PhantomData, + mem, pin::Pin, task::{Context, Poll}, }; @@ -26,17 +26,17 @@ impl Unpin for LocalFutureObj<'_, T> {} #[allow(single_use_lifetimes)] #[allow(clippy::transmute_ptr_to_ptr)] -unsafe fn remove_future_lifetime<'a, T>(ptr: *mut (dyn Future + 'a)) - -> *mut (dyn Future + 'static) -{ - mem::transmute(ptr) +unsafe fn remove_future_lifetime<'a, T>( + ptr: *mut (dyn Future + 'a), +) -> *mut (dyn Future + 'static) { + unsafe { mem::transmute(ptr) } } #[allow(single_use_lifetimes)] -unsafe fn remove_drop_lifetime<'a, T>(ptr: unsafe fn (*mut (dyn Future + 'a))) - -> unsafe fn(*mut (dyn Future + 'static)) -{ - mem::transmute(ptr) +unsafe fn remove_drop_lifetime<'a, T>( + ptr: unsafe fn(*mut (dyn Future + 'a)), +) -> unsafe fn(*mut (dyn Future + 'static)) { + unsafe { mem::transmute(ptr) } } impl<'a, T> LocalFutureObj<'a, T> { @@ -65,8 +65,7 @@ impl<'a, T> LocalFutureObj<'a, T> { impl fmt::Debug for LocalFutureObj<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("LocalFutureObj") - .finish() + f.debug_struct("LocalFutureObj").finish() } } @@ -82,17 +81,13 @@ impl Future for LocalFutureObj<'_, T> { #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - unsafe { - Pin::new_unchecked(&mut *self.future).poll(cx) - } + unsafe { Pin::new_unchecked(&mut *self.future).poll(cx) } } } impl Drop for LocalFutureObj<'_, T> { fn drop(&mut self) { - unsafe { - (self.drop_fn)(self.future) - } + unsafe { (self.drop_fn)(self.future) } } } @@ -120,8 +115,7 @@ impl<'a, T> FutureObj<'a, T> { impl fmt::Debug for FutureObj<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("FutureObj") - .finish() + f.debug_struct("FutureObj").finish() } } @@ -130,7 +124,7 @@ impl Future for FutureObj<'_, T> { #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Pin::new( &mut self.0 ).poll(cx) + Pin::new(&mut self.0).poll(cx) } } @@ -155,6 +149,7 @@ pub unsafe trait UnsafeFutureObj<'a, T>: 'a { /// provided `*mut (dyn Future + 'a)` into a `Pin<&mut (dyn /// Future + 'a)>` and call methods on it, non-reentrantly, /// until `UnsafeFutureObj::drop` is called with it. + #[allow(clippy::unnecessary_safety_doc)] fn into_raw(self) -> *mut (dyn Future + 'a); /// Drops the future represented by the given fat pointer. @@ -180,7 +175,7 @@ pub unsafe trait UnsafeFutureObj<'a, T>: 'a { unsafe impl<'a, T, F> UnsafeFutureObj<'a, T> for &'a mut F where - F: Future + Unpin + 'a + F: Future + Unpin + 'a, { fn into_raw(self) -> *mut (dyn Future + 'a) { self as *mut dyn Future @@ -189,8 +184,7 @@ where unsafe fn drop(_ptr: *mut (dyn Future + 'a)) {} } -unsafe impl<'a, T> UnsafeFutureObj<'a, T> for &'a mut (dyn Future + Unpin + 'a) -{ +unsafe impl<'a, T> UnsafeFutureObj<'a, T> for &'a mut (dyn Future + Unpin + 'a) { fn into_raw(self) -> *mut (dyn Future + 'a) { self as *mut dyn Future } @@ -200,7 +194,7 @@ unsafe impl<'a, T> UnsafeFutureObj<'a, T> for &'a mut (dyn Future + unsafe impl<'a, T, F> UnsafeFutureObj<'a, T> for Pin<&'a mut F> where - F: Future + 'a + F: Future + 'a, { fn into_raw(self) -> *mut (dyn Future + 'a) { unsafe { self.get_unchecked_mut() as *mut dyn Future } @@ -209,8 +203,7 @@ where unsafe fn drop(_ptr: *mut (dyn Future + 'a)) {} } -unsafe impl<'a, T> UnsafeFutureObj<'a, T> for Pin<&'a mut (dyn Future + 'a)> -{ +unsafe impl<'a, T> UnsafeFutureObj<'a, T> for Pin<&'a mut (dyn Future + 'a)> { fn into_raw(self) -> *mut (dyn Future + 'a) { unsafe { self.get_unchecked_mut() as *mut dyn Future } } @@ -224,14 +217,15 @@ mod if_alloc { use alloc::boxed::Box; unsafe impl<'a, T, F> UnsafeFutureObj<'a, T> for Box - where F: Future + 'a + where + F: Future + 'a, { fn into_raw(self) -> *mut (dyn Future + 'a) { Box::into_raw(self) } unsafe fn drop(ptr: *mut (dyn Future + 'a)) { - drop(Box::from_raw(ptr as *mut F)) + drop(unsafe { Box::from_raw(ptr.cast::()) }) } } @@ -241,7 +235,7 @@ mod if_alloc { } unsafe fn drop(ptr: *mut (dyn Future + 'a)) { - drop(Box::from_raw(ptr)) + drop(unsafe { Box::from_raw(ptr) }) } } @@ -251,46 +245,43 @@ mod if_alloc { } unsafe fn drop(ptr: *mut (dyn Future + 'a)) { - drop(Box::from_raw(ptr)) + drop(unsafe { Box::from_raw(ptr) }) } } unsafe impl<'a, T, F> UnsafeFutureObj<'a, T> for Pin> where - F: Future + 'a + F: Future + 'a, { - fn into_raw(mut self) -> *mut (dyn Future + 'a) { - let ptr = unsafe { self.as_mut().get_unchecked_mut() as *mut _ }; - mem::forget(self); - ptr + fn into_raw(self) -> *mut (dyn Future + 'a) { + let mut this = mem::ManuallyDrop::new(self); + unsafe { this.as_mut().get_unchecked_mut() as *mut _ } } unsafe fn drop(ptr: *mut (dyn Future + 'a)) { - drop(Pin::from(Box::from_raw(ptr))) + drop(Pin::from(unsafe { Box::from_raw(ptr) })) } } unsafe impl<'a, T: 'a> UnsafeFutureObj<'a, T> for Pin + 'a>> { - fn into_raw(mut self) -> *mut (dyn Future + 'a) { - let ptr = unsafe { self.as_mut().get_unchecked_mut() as *mut _ }; - mem::forget(self); - ptr + fn into_raw(self) -> *mut (dyn Future + 'a) { + let mut this = mem::ManuallyDrop::new(self); + unsafe { this.as_mut().get_unchecked_mut() as *mut _ } } unsafe fn drop(ptr: *mut (dyn Future + 'a)) { - drop(Pin::from(Box::from_raw(ptr))) + drop(Pin::from(unsafe { Box::from_raw(ptr) })) } } unsafe impl<'a, T: 'a> UnsafeFutureObj<'a, T> for Pin + Send + 'a>> { - fn into_raw(mut self) -> *mut (dyn Future + 'a) { - let ptr = unsafe { self.as_mut().get_unchecked_mut() as *mut _ }; - mem::forget(self); - ptr + fn into_raw(self) -> *mut (dyn Future + 'a) { + let mut this = mem::ManuallyDrop::new(self); + unsafe { this.as_mut().get_unchecked_mut() as *mut _ } } unsafe fn drop(ptr: *mut (dyn Future + 'a)) { - drop(Pin::from(Box::from_raw(ptr))) + drop(Pin::from(unsafe { Box::from_raw(ptr) })) } } diff --git a/futures-task/src/lib.rs b/futures-task/src/lib.rs index 5505e3ad49..c119b6b1e4 100644 --- a/futures-task/src/lib.rs +++ b/futures-task/src/lib.rs @@ -1,47 +1,43 @@ //! Tools for working with tasks. -#![cfg_attr(feature = "cfg-target-has-atomic", feature(cfg_target_has_atomic))] - -#![cfg_attr(not(feature = "std"), no_std)] - -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] - -#[cfg(all(feature = "cfg-target-has-atomic", not(feature = "unstable")))] -compile_error!("The `cfg-target-has-atomic` feature requires the `unstable` feature as an explicit opt-in to unstable features"); +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, unsafe_op_in_unsafe_fn)] #[cfg(feature = "alloc")] extern crate alloc; - -macro_rules! cfg_target_has_atomic { - ($($item:item)*) => {$( - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] - $item - )*}; -} +#[cfg(feature = "std")] +extern crate std; mod spawn; -pub use crate::spawn::{Spawn, SpawnError, LocalSpawn}; +pub use crate::spawn::{LocalSpawn, Spawn, SpawnError}; -cfg_target_has_atomic! { - #[cfg(feature = "alloc")] - mod arc_wake; - #[cfg(feature = "alloc")] - pub use crate::arc_wake::ArcWake; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod arc_wake; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use crate::arc_wake::ArcWake; - #[cfg(feature = "alloc")] - mod waker; - #[cfg(feature = "alloc")] - pub use crate::waker::waker; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod waker; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use crate::waker::waker; - #[cfg(feature = "alloc")] - mod waker_ref; - #[cfg(feature = "alloc")] - pub use crate::waker_ref::{waker_ref, WakerRef}; -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod waker_ref; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use crate::waker_ref::{waker_ref, WakerRef}; mod future_obj; pub use crate::future_obj::{FutureObj, LocalFutureObj, UnsafeFutureObj}; @@ -51,4 +47,4 @@ pub use crate::noop_waker::noop_waker; pub use crate::noop_waker::noop_waker_ref; #[doc(no_inline)] -pub use core::task::{Context, Poll, Waker, RawWaker, RawWakerVTable}; +pub use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; diff --git a/futures-task/src/spawn.rs b/futures-task/src/spawn.rs index a515dd4e18..4a9a45a446 100644 --- a/futures-task/src/spawn.rs +++ b/futures-task/src/spawn.rs @@ -126,7 +126,7 @@ impl LocalSpawn for &mut Sp { #[cfg(feature = "alloc")] mod if_alloc { use super::*; - use alloc::{ boxed::Box, rc::Rc }; + use alloc::{boxed::Box, rc::Rc}; impl Spawn for Box { fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { @@ -168,27 +168,25 @@ mod if_alloc { } } - cfg_target_has_atomic! { - use alloc::{ sync::Arc }; - - impl Spawn for Arc { - fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { - (**self).spawn_obj(future) - } + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + impl Spawn for alloc::sync::Arc { + fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError> { + (**self).spawn_obj(future) + } - fn status(&self) -> Result<(), SpawnError> { - (**self).status() - } + fn status(&self) -> Result<(), SpawnError> { + (**self).status() } + } - impl LocalSpawn for Arc { - fn spawn_local_obj(&self, future: LocalFutureObj<'static, ()>) -> Result<(), SpawnError> { - (**self).spawn_local_obj(future) - } + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + impl LocalSpawn for alloc::sync::Arc { + fn spawn_local_obj(&self, future: LocalFutureObj<'static, ()>) -> Result<(), SpawnError> { + (**self).spawn_local_obj(future) + } - fn status_local(&self) -> Result<(), SpawnError> { - (**self).status_local() - } + fn status_local(&self) -> Result<(), SpawnError> { + (**self).status_local() } } } diff --git a/futures-task/src/waker.rs b/futures-task/src/waker.rs index 265a445d91..ce90aa83d9 100644 --- a/futures-task/src/waker.rs +++ b/futures-task/src/waker.rs @@ -1,9 +1,9 @@ use super::arc_wake::ArcWake; -use core::mem; -use core::task::{Waker, RawWaker, RawWakerVTable}; use alloc::sync::Arc; +use core::mem; +use core::task::{RawWaker, RawWakerVTable, Waker}; -pub(super) fn waker_vtable() -> &'static RawWakerVTable { +pub(super) fn waker_vtable() -> &'static RawWakerVTable { &RawWakerVTable::new( clone_arc_raw::, wake_arc_raw::, @@ -20,42 +20,41 @@ pub fn waker(wake: Arc) -> Waker where W: ArcWake + 'static, { - let ptr = Arc::into_raw(wake) as *const (); + let ptr = Arc::into_raw(wake).cast::<()>(); - unsafe { - Waker::from_raw(RawWaker::new(ptr, waker_vtable::())) - } + unsafe { Waker::from_raw(RawWaker::new(ptr, waker_vtable::())) } } // FIXME: panics on Arc::clone / refcount changes could wreak havoc on the // code here. We should guard against this by aborting. #[allow(clippy::redundant_clone)] // The clone here isn't actually redundant. -unsafe fn increase_refcount(data: *const ()) { +unsafe fn increase_refcount(data: *const ()) { // Retain Arc, but don't touch refcount by wrapping in ManuallyDrop - let arc = mem::ManuallyDrop::new(Arc::::from_raw(data as *const T)); + let arc = mem::ManuallyDrop::new(unsafe { Arc::::from_raw(data.cast::()) }); // Now increase refcount, but don't drop new refcount either let _arc_clone: mem::ManuallyDrop<_> = arc.clone(); } // used by `waker_ref` -unsafe fn clone_arc_raw(data: *const ()) -> RawWaker { - increase_refcount::(data); +#[inline(always)] +unsafe fn clone_arc_raw(data: *const ()) -> RawWaker { + unsafe { increase_refcount::(data) } RawWaker::new(data, waker_vtable::()) } -unsafe fn wake_arc_raw(data: *const ()) { - let arc: Arc = Arc::from_raw(data as *const T); +unsafe fn wake_arc_raw(data: *const ()) { + let arc: Arc = unsafe { Arc::from_raw(data.cast::()) }; ArcWake::wake(arc); } // used by `waker_ref` -unsafe fn wake_by_ref_arc_raw(data: *const ()) { +unsafe fn wake_by_ref_arc_raw(data: *const ()) { // Retain Arc, but don't touch refcount by wrapping in ManuallyDrop - let arc = mem::ManuallyDrop::new(Arc::::from_raw(data as *const T)); + let arc = mem::ManuallyDrop::new(unsafe { Arc::::from_raw(data.cast::()) }); ArcWake::wake_by_ref(&arc); } -unsafe fn drop_arc_raw(data: *const ()) { - drop(Arc::::from_raw(data as *const T)) +unsafe fn drop_arc_raw(data: *const ()) { + drop(unsafe { Arc::::from_raw(data.cast::()) }) } diff --git a/futures-task/src/waker_ref.rs b/futures-task/src/waker_ref.rs index 76d849ac1b..5957b4d46a 100644 --- a/futures-task/src/waker_ref.rs +++ b/futures-task/src/waker_ref.rs @@ -1,10 +1,10 @@ -use super::arc_wake::{ArcWake}; +use super::arc_wake::ArcWake; use super::waker::waker_vtable; use alloc::sync::Arc; -use core::mem::ManuallyDrop; use core::marker::PhantomData; +use core::mem::ManuallyDrop; use core::ops::Deref; -use core::task::{Waker, RawWaker}; +use core::task::{RawWaker, Waker}; /// A [`Waker`] that is only valid for a given lifetime. /// @@ -18,14 +18,12 @@ pub struct WakerRef<'a> { impl<'a> WakerRef<'a> { /// Create a new [`WakerRef`] from a [`Waker`] reference. + #[inline] pub fn new(waker: &'a Waker) -> Self { // copy the underlying (raw) waker without calling a clone, // as we won't call Waker::drop either. let waker = ManuallyDrop::new(unsafe { core::ptr::read(waker) }); - Self { - waker, - _marker: PhantomData, - } + Self { waker, _marker: PhantomData } } /// Create a new [`WakerRef`] from a [`Waker`] that must not be dropped. @@ -34,17 +32,16 @@ impl<'a> WakerRef<'a> { /// an unsafe way (that will be valid only for a lifetime to be determined /// by the caller), and the [`Waker`] doesn't need to or must not be /// destroyed. + #[inline] pub fn new_unowned(waker: ManuallyDrop) -> Self { - Self { - waker, - _marker: PhantomData, - } + Self { waker, _marker: PhantomData } } } impl Deref for WakerRef<'_> { type Target = Waker; + #[inline] fn deref(&self) -> &Waker { &self.waker } @@ -57,14 +54,13 @@ impl Deref for WakerRef<'_> { #[inline] pub fn waker_ref(wake: &Arc) -> WakerRef<'_> where - W: ArcWake + W: ArcWake + 'static, { // simply copy the pointer instead of using Arc::into_raw, // as we don't actually keep a refcount by using ManuallyDrop.< - let ptr = (&**wake as *const W) as *const (); + let ptr = Arc::as_ptr(wake).cast::<()>(); - let waker = ManuallyDrop::new(unsafe { - Waker::from_raw(RawWaker::new(ptr, waker_vtable::())) - }); + let waker = + ManuallyDrop::new(unsafe { Waker::from_raw(RawWaker::new(ptr, waker_vtable::())) }); WakerRef::new_unowned(waker) } diff --git a/futures-test/Cargo.toml b/futures-test/Cargo.toml index d7a39212af..46d933415f 100644 --- a/futures-test/Cargo.toml +++ b/futures-test/Cargo.toml @@ -1,25 +1,24 @@ [package] name = "futures-test" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Wim Looman "] +rust-version = "1.56" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-test/0.3" description = """ Common utilities for testing components built off futures-rs. """ [dependencies] -futures-core = { version = "0.3.13", path = "../futures-core", default-features = false } -futures-task = { version = "0.3.13", path = "../futures-task", default-features = false } -futures-io = { version = "0.3.13", path = "../futures-io", default-features = false } -futures-util = { version = "0.3.13", path = "../futures-util", default-features = false } -futures-executor = { version = "0.3.13", path = "../futures-executor", default-features = false } -futures-sink = { version = "0.3.13", path = "../futures-sink", default-features = false } -pin-utils = { version = "0.1.0", default-features = false } -pin-project = "1.0.1" +futures-core = { version = "0.3.31", path = "../futures-core", default-features = false } +futures-task = { version = "0.3.31", path = "../futures-task", default-features = false } +futures-io = { version = "0.3.31", path = "../futures-io", default-features = false } +futures-util = { version = "0.3.31", path = "../futures-util", default-features = false } +futures-executor = { version = "0.3.31", path = "../futures-executor", default-features = false } +futures-sink = { version = "0.3.31", path = "../futures-sink", default-features = false } +futures-macro = { version = "=0.3.31", path = "../futures-macro", default-features = false } +pin-project = "1.0.11" [dev-dependencies] futures = { path = "../futures", default-features = false, features = ["std", "executor"] } @@ -30,3 +29,6 @@ std = ["futures-core/std", "futures-task/std", "futures-io/std", "futures-util/s [package.metadata.docs.rs] all-features = true + +[lints] +workspace = true diff --git a/futures-test/README.md b/futures-test/README.md new file mode 100644 index 0000000000..34595aaf30 --- /dev/null +++ b/futures-test/README.md @@ -0,0 +1,23 @@ +# futures-test + +Common utilities for testing components built off futures-rs. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-test = "0.3" +``` + +The current `futures-test` requires Rust 1.56 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-test/src/assert.rs b/futures-test/src/assert.rs index 4f1cc7d31a..75d7832482 100644 --- a/futures-test/src/assert.rs +++ b/futures-test/src/assert.rs @@ -30,9 +30,7 @@ macro_rules! assert_stream_pending { $crate::__private::assert::assert_is_unpin_stream(stream); let stream = $crate::__private::Pin::new(stream); let mut cx = $crate::task::noop_context(); - let poll = $crate::__private::stream::Stream::poll_next( - stream, &mut cx, - ); + let poll = $crate::__private::stream::Stream::poll_next(stream, &mut cx); if poll.is_ready() { panic!("assertion failed: stream is not pending"); } @@ -71,13 +69,15 @@ macro_rules! assert_stream_next { assert_eq!(x, $item); } $crate::__private::task::Poll::Ready($crate::__private::None) => { - panic!("assertion failed: expected stream to provide item but stream is at its end"); + panic!( + "assertion failed: expected stream to provide item but stream is at its end" + ); } $crate::__private::task::Poll::Pending => { panic!("assertion failed: expected stream to provide item but stream wasn't ready"); } } - }} + }}; } /// Assert that the next poll to the provided stream will return an empty @@ -117,5 +117,5 @@ macro_rules! assert_stream_done { panic!("assertion failed: expected stream to be done but was pending"); } } - }} + }}; } diff --git a/futures-test/src/assert_unmoved.rs b/futures-test/src/assert_unmoved.rs index 6e5116d228..baeaeb59ab 100644 --- a/futures-test/src/assert_unmoved.rs +++ b/futures-test/src/assert_unmoved.rs @@ -7,7 +7,6 @@ use futures_io::{ use futures_sink::Sink; use pin_project::{pin_project, pinned_drop}; use std::pin::Pin; -use std::ptr; use std::thread::panicking; /// Combinator that asserts that the underlying type is not moved after being polled. @@ -24,32 +23,21 @@ use std::thread::panicking; pub struct AssertUnmoved { #[pin] inner: T, - this_ptr: *const Self, + this_addr: usize, } -// Safety: having a raw pointer in a struct makes it `!Send`, however the -// pointer is never dereferenced so this is safe. -unsafe impl Send for AssertUnmoved {} -unsafe impl Sync for AssertUnmoved {} - impl AssertUnmoved { pub(crate) fn new(inner: T) -> Self { - Self { - inner, - this_ptr: ptr::null(), - } + Self { inner, this_addr: 0 } } fn poll_with<'a, U>(mut self: Pin<&'a mut Self>, f: impl FnOnce(Pin<&'a mut T>) -> U) -> U { - let cur_this = &*self as *const Self; - if self.this_ptr.is_null() { + let cur_this = &*self as *const Self as usize; + if self.this_addr == 0 { // First time being polled - *self.as_mut().project().this_ptr = cur_this; + *self.as_mut().project().this_addr = cur_this; } else { - assert_eq!( - self.this_ptr, cur_this, - "AssertUnmoved moved between poll calls" - ); + assert_eq!(self.this_addr, cur_this, "AssertUnmoved moved between poll calls"); } f(self.project().inner) } @@ -172,9 +160,9 @@ impl PinnedDrop for AssertUnmoved { fn drop(self: Pin<&mut Self>) { // If the thread is panicking then we can't panic again as that will // cause the process to be aborted. - if !panicking() && !self.this_ptr.is_null() { - let cur_this = &*self as *const Self; - assert_eq!(self.this_ptr, cur_this, "AssertUnmoved moved before drop"); + if !panicking() && self.this_addr != 0 { + let cur_this = &*self as *const Self as usize; + assert_eq!(self.this_addr, cur_this, "AssertUnmoved moved before drop"); } } } diff --git a/futures-test/src/future/mod.rs b/futures-test/src/future/mod.rs index ee5c6ddd5d..0f52f62bb9 100644 --- a/futures-test/src/future/mod.rs +++ b/futures-test/src/future/mod.rs @@ -68,6 +68,7 @@ pub trait FutureTestExt: Future { /// /// assert_eq!(rx.await, Ok(5)); /// # }); + /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 /// ``` fn run_in_background(self) where diff --git a/futures-test/src/future/pending_once.rs b/futures-test/src/future/pending_once.rs index b36af6fe39..0fc3ef0b34 100644 --- a/futures-test/src/future/pending_once.rs +++ b/futures-test/src/future/pending_once.rs @@ -1,7 +1,7 @@ -use futures_core::future::{Future, FusedFuture}; +use futures_core::future::{FusedFuture, Future}; use futures_core::task::{Context, Poll}; -use std::pin::Pin; use pin_project::pin_project; +use std::pin::Pin; /// Combinator that guarantees one [`Poll::Pending`] before polling its inner /// future. @@ -20,20 +20,14 @@ pub struct PendingOnce { impl PendingOnce { pub(super) fn new(future: Fut) -> Self { - Self { - future, - polled_before: false, - } + Self { future, polled_before: false } } } impl Future for PendingOnce { type Output = Fut::Output; - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); if *this.polled_before { this.future.poll(cx) diff --git a/futures-test/src/interleave_pending.rs b/futures-test/src/interleave_pending.rs index 7bc8706388..91640778b2 100644 --- a/futures-test/src/interleave_pending.rs +++ b/futures-test/src/interleave_pending.rs @@ -28,10 +28,7 @@ pub struct InterleavePending { impl InterleavePending { pub(crate) fn new(inner: T) -> Self { - Self { - inner, - pended: false, - } + Self { inner, pended: false } } /// Acquires a reference to the underlying I/O object that this adaptor is diff --git a/futures-test/src/io/limited.rs b/futures-test/src/io/limited.rs index a206160099..34b72a530e 100644 --- a/futures-test/src/io/limited.rs +++ b/futures-test/src/io/limited.rs @@ -59,17 +59,11 @@ impl AsyncWrite for Limited { this.io.poll_write(cx, &buf[..cmp::min(*this.limit, buf.len())]) } - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().io.poll_flush(cx) } - fn poll_close( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().io.poll_close(cx) } } @@ -87,10 +81,7 @@ impl AsyncRead for Limited { } impl AsyncBufRead for Limited { - fn poll_fill_buf( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().io.poll_fill_buf(cx) } diff --git a/futures-test/src/lib.rs b/futures-test/src/lib.rs index 6c3e60cbe1..4e420eac88 100644 --- a/futures-test/src/lib.rs +++ b/futures-test/src/lib.rs @@ -1,24 +1,31 @@ //! Utilities to make testing [`Future`s](futures_core::future::Future) easier -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, unsafe_op_in_unsafe_fn)] +#![allow(clippy::test_attr_in_doctest)] #[cfg(not(feature = "std"))] -compile_error!("`futures-test` must have the `std` feature activated, this is a default-active feature"); +compile_error!( + "`futures-test` must have the `std` feature activated, this is a default-active feature" +); // Not public API. #[doc(hidden)] #[cfg(feature = "std")] pub mod __private { + pub use futures_core::{future, stream, task}; + pub use futures_executor::block_on; pub use std::{ - option::Option::{Some, None}, + option::Option::{None, Some}, pin::Pin, result::Result::{Err, Ok}, }; - pub use futures_core::{future, stream, task}; pub mod assert { pub use crate::assert::*; @@ -47,3 +54,28 @@ pub mod io; mod assert_unmoved; mod interleave_pending; mod track_closed; + +/// Enables an `async` test function. The generated future will be run to completion with +/// [`futures_executor::block_on`]. +/// +/// ``` +/// #[futures_test::test] +/// async fn my_test() { +/// let fut = async { true }; +/// assert!(fut.await); +/// } +/// ``` +/// +/// This is equivalent to the following code: +/// +/// ``` +/// #[test] +/// fn my_test() { +/// futures::executor::block_on(async move { +/// let fut = async { true }; +/// assert!(fut.await); +/// }) +/// } +/// ``` +#[cfg(feature = "std")] +pub use futures_macro::test_internal as test; diff --git a/futures-test/src/task/context.rs b/futures-test/src/task/context.rs index 602127c657..b2b0dfe31e 100644 --- a/futures-test/src/task/context.rs +++ b/futures-test/src/task/context.rs @@ -1,4 +1,4 @@ -use crate::task::{panic_waker_ref, noop_waker_ref}; +use crate::task::{noop_waker_ref, panic_waker_ref}; use futures_core::task::Context; /// Create a new [`Context`](core::task::Context) where the diff --git a/futures-test/src/task/mod.rs b/futures-test/src/task/mod.rs index 89e6aef704..e10ecbb875 100644 --- a/futures-test/src/task/mod.rs +++ b/futures-test/src/task/mod.rs @@ -1,5 +1,5 @@ // TODO: note that paths like futures_core::task::Context actually get redirected to core::task::Context -// in the redered docs. Is this desirable? If so, should we change the paths here? +// in the rendered docs. Is this desirable? If so, should we change the paths here? // // Also, there is cross crate links in here. They are not going to work anytime soon. Do we put https links // in here? to here: https://rust-lang.github.io/futures-api-docs? The problem is these have a @@ -15,29 +15,29 @@ //! [`Spawn`](futures_task::Spawn) implementations. //! //! Test contexts: -//! - [`noop_context`](crate::task::noop_context) creates a context that ignores calls to +//! - [`noop_context`] creates a context that ignores calls to //! [`cx.waker().wake_by_ref()`](futures_core::task::Waker). -//! - [`panic_context`](crate::task::panic_context) creates a context that panics when +//! - [`panic_context`] creates a context that panics when //! [`cx.waker().wake_by_ref()`](futures_core::task::Waker) is called. //! //! Test wakers: -//! - [`noop_waker`](crate::task::noop_waker) creates a waker that ignores calls to +//! - [`noop_waker`] creates a waker that ignores calls to //! [`wake`](futures_core::task::Waker). -//! - [`panic_waker`](crate::task::panic_waker) creates a waker that panics when +//! - [`panic_waker`](panic_waker()) creates a waker that panics when //! [`wake`](futures_core::task::Waker) is called. -//! - [`new_count_waker`](crate::task::new_count_waker) creates a waker that increments a counter whenever +//! - [`new_count_waker`] creates a waker that increments a counter whenever //! [`wake`](futures_core::task::Waker) is called. //! //! Test spawners: -//! - [`NoopSpawner`](crate::task::NoopSpawner) ignores calls to +//! - [`NoopSpawner`] ignores calls to //! [`spawn`](futures_util::task::SpawnExt::spawn) -//! - [`PanicSpawner`](crate::task::PanicSpawner) panics if [`spawn`](futures_util::task::SpawnExt::spawn) is +//! - [`PanicSpawner`] panics if [`spawn`](futures_util::task::SpawnExt::spawn) is //! called. -//! - [`RecordSpawner`](crate::task::RecordSpawner) records the spawned futures. +//! - [`RecordSpawner`] records the spawned futures. //! //! For convenience there additionally exist various functions that directly -//! return waker/spawner references: [`noop_waker_ref`](crate::task::noop_waker_ref), -//! [`panic_waker_ref`](crate::task::panic_waker_ref), [`noop_spawner_mut`](crate::task::noop_spawner_mut) and [`panic_spawner_mut`](crate::task::panic_spawner_mut). +//! return waker/spawner references: [`noop_waker_ref`], [`panic_waker_ref`], +//! [`noop_spawner_mut`] and [`panic_spawner_mut`]. mod context; pub use self::context::{noop_context, panic_context}; @@ -57,4 +57,4 @@ mod record_spawner; pub use self::record_spawner::RecordSpawner; mod wake_counter; -pub use self::wake_counter::{AwokenCount, new_count_waker}; +pub use self::wake_counter::{new_count_waker, AwokenCount}; diff --git a/futures-test/src/task/wake_counter.rs b/futures-test/src/task/wake_counter.rs index cf496c2ddb..52c63e1cc9 100644 --- a/futures-test/src/task/wake_counter.rs +++ b/futures-test/src/task/wake_counter.rs @@ -1,7 +1,7 @@ use futures_core::task::Waker; use futures_util::task::{self, ArcWake}; -use std::sync::Arc; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::Arc; /// Number of times the waker was awoken. /// diff --git a/futures-test/src/track_closed.rs b/futures-test/src/track_closed.rs index 31e404c226..be883b1491 100644 --- a/futures-test/src/track_closed.rs +++ b/futures-test/src/track_closed.rs @@ -21,10 +21,7 @@ pub struct TrackClosed { impl TrackClosed { pub(crate) fn new(inner: T) -> Self { - Self { - inner, - closed: false, - } + Self { inner, closed: false } } /// Check whether this object has been closed. diff --git a/futures-util/Cargo.toml b/futures-util/Cargo.toml index b7cc19370e..825a204125 100644 --- a/futures-util/Cargo.toml +++ b/futures-util/Cargo.toml @@ -1,12 +1,11 @@ [package] name = "futures-util" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.56" license = "MIT OR Apache-2.0" repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures-util/0.3" description = """ Common utilities and extension traits for the futures-rs library. """ @@ -16,37 +15,38 @@ default = ["std", "async-await", "async-await-macro"] std = ["alloc", "futures-core/std", "futures-task/std", "slab"] alloc = ["futures-core/alloc", "futures-task/alloc"] async-await = [] -async-await-macro = ["async-await", "futures-macro", "proc-macro-hack", "proc-macro-nested"] +async-await-macro = ["async-await", "futures-macro"] compat = ["std", "futures_01"] io-compat = ["io", "compat", "tokio-io"] sink = ["futures-sink"] io = ["std", "futures-io", "memchr"] channel = ["std", "futures-channel"] +portable-atomic = ["futures-core/portable-atomic"] # Unstable features # These features are outside of the normal semver guarantees and require the # `unstable` feature as an explicit opt-in to unstable API. unstable = ["futures-core/unstable", "futures-task/unstable"] -cfg-target-has-atomic = ["futures-core/cfg-target-has-atomic", "futures-task/cfg-target-has-atomic"] bilock = [] -read-initializer = ["io", "futures-io/read-initializer", "futures-io/unstable"] write-all-vectored = ["io"] +# These features are no longer used. +# TODO: remove in the next major version. +cfg-target-has-atomic = [] + [dependencies] -futures-core = { path = "../futures-core", version = "0.3.13", default-features = false } -futures-task = { path = "../futures-task", version = "0.3.13", default-features = false } -futures-channel = { path = "../futures-channel", version = "0.3.13", default-features = false, features = ["std"], optional = true } -futures-io = { path = "../futures-io", version = "0.3.13", default-features = false, features = ["std"], optional = true } -futures-sink = { path = "../futures-sink", version = "0.3.13", default-features = false, optional = true } -futures-macro = { path = "../futures-macro", version = "=0.3.13", default-features = false, optional = true } -proc-macro-hack = { version = "0.5.19", optional = true } -proc-macro-nested = { version = "0.1.2", optional = true } +futures-core = { path = "../futures-core", version = "0.3.31", default-features = false } +futures-task = { path = "../futures-task", version = "0.3.31", default-features = false } +futures-channel = { path = "../futures-channel", version = "0.3.31", default-features = false, features = ["std"], optional = true } +futures-io = { path = "../futures-io", version = "0.3.31", default-features = false, features = ["std"], optional = true } +futures-sink = { path = "../futures-sink", version = "0.3.31", default-features = false, optional = true } +futures-macro = { path = "../futures-macro", version = "=0.3.31", default-features = false, optional = true } slab = { version = "0.4.2", optional = true } memchr = { version = "2.2", optional = true } futures_01 = { version = "0.1.25", optional = true, package = "futures" } tokio-io = { version = "0.1.9", optional = true } pin-utils = "0.1.0" -pin-project-lite = "0.2.4" +pin-project-lite = "0.2.6" [dev-dependencies] futures = { path = "../futures", features = ["async-await", "thread-pool"] } @@ -56,3 +56,6 @@ tokio = "0.1.11" [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] + +[lints] +workspace = true diff --git a/futures-util/README.md b/futures-util/README.md new file mode 100644 index 0000000000..60e2c2109a --- /dev/null +++ b/futures-util/README.md @@ -0,0 +1,23 @@ +# futures-util + +Common utilities and extension traits for the futures-rs library. + +## Usage + +Add this to your `Cargo.toml`: + +```toml +[dependencies] +futures-util = "0.3" +``` + +The current `futures-util` requires Rust 1.56 or later. + +## License + +Licensed under either of [Apache License, Version 2.0](LICENSE-APACHE) or +[MIT license](LICENSE-MIT) at your option. + +Unless you explicitly state otherwise, any contribution intentionally submitted +for inclusion in the work by you, as defined in the Apache-2.0 license, shall +be dual licensed as above, without any additional terms or conditions. diff --git a/futures-util/benches/bilock.rs b/futures-util/benches/bilock.rs new file mode 100644 index 0000000000..013f3351e4 --- /dev/null +++ b/futures-util/benches/bilock.rs @@ -0,0 +1,68 @@ +#![feature(test)] +#![cfg(feature = "bilock")] + +extern crate test; + +use futures::task::Poll; +use futures_test::task::noop_context; +use futures_util::lock::BiLock; + +use crate::test::Bencher; + +#[bench] +fn contended(b: &mut Bencher) { + let mut context = noop_context(); + + b.iter(|| { + let (x, y) = BiLock::new(1); + + for _ in 0..1000 { + let x_guard = match x.poll_lock(&mut context) { + Poll::Ready(guard) => guard, + _ => panic!(), + }; + + // Try poll second lock while first lock still holds the lock + match y.poll_lock(&mut context) { + Poll::Pending => (), + _ => panic!(), + }; + + drop(x_guard); + + let y_guard = match y.poll_lock(&mut context) { + Poll::Ready(guard) => guard, + _ => panic!(), + }; + + drop(y_guard); + } + (x, y) + }); +} + +#[bench] +fn lock_unlock(b: &mut Bencher) { + let mut context = noop_context(); + + b.iter(|| { + let (x, y) = BiLock::new(1); + + for _ in 0..1000 { + let x_guard = match x.poll_lock(&mut context) { + Poll::Ready(guard) => guard, + _ => panic!(), + }; + + drop(x_guard); + + let y_guard = match y.poll_lock(&mut context) { + Poll::Ready(guard) => guard, + _ => panic!(), + }; + + drop(y_guard); + } + (x, y) + }) +} diff --git a/futures-util/benches/flatten_unordered.rs b/futures-util/benches/flatten_unordered.rs new file mode 100644 index 0000000000..517b2816c3 --- /dev/null +++ b/futures-util/benches/flatten_unordered.rs @@ -0,0 +1,58 @@ +#![feature(test)] + +extern crate test; +use crate::test::Bencher; + +use futures::channel::oneshot; +use futures::executor::block_on; +use futures::future; +use futures::stream::{self, StreamExt}; +use futures::task::Poll; +use futures_util::FutureExt; +use std::collections::VecDeque; +use std::thread; + +#[bench] +fn oneshot_streams(b: &mut Bencher) { + const STREAM_COUNT: usize = 10_000; + const STREAM_ITEM_COUNT: usize = 1; + + b.iter(|| { + let mut txs = VecDeque::with_capacity(STREAM_COUNT); + let mut rxs = Vec::new(); + + for _ in 0..STREAM_COUNT { + let (tx, rx) = oneshot::channel(); + txs.push_back(tx); + rxs.push(rx); + } + + thread::spawn(move || { + let mut last = 1; + while let Some(tx) = txs.pop_front() { + let _ = tx.send(stream::iter(last..last + STREAM_ITEM_COUNT)); + last += STREAM_ITEM_COUNT; + } + }); + + let mut flatten = stream::iter(rxs) + .map(|recv| recv.into_stream().map(|val| val.unwrap()).flatten()) + .flatten_unordered(None); + + block_on(future::poll_fn(move |cx| { + let mut count = 0; + loop { + match flatten.poll_next_unpin(cx) { + Poll::Ready(None) => break, + Poll::Ready(Some(_)) => { + count += 1; + } + _ => {} + } + } + assert_eq!(count, STREAM_COUNT * STREAM_ITEM_COUNT); + + Poll::Ready(()) + })) + }); +} diff --git a/futures-util/benches/futures_unordered.rs b/futures-util/benches/futures_unordered.rs index 05e9eadb79..d5fe7a59de 100644 --- a/futures-util/benches/futures_unordered.rs +++ b/futures-util/benches/futures_unordered.rs @@ -6,7 +6,7 @@ use crate::test::Bencher; use futures::channel::oneshot; use futures::executor::block_on; use futures::future; -use futures::stream::{StreamExt, FuturesUnordered}; +use futures::stream::{FuturesUnordered, StreamExt}; use futures::task::Poll; use std::collections::VecDeque; use std::thread; @@ -34,7 +34,7 @@ fn oneshots(b: &mut Bencher) { block_on(future::poll_fn(move |cx| { loop { if let Poll::Ready(None) = rxs.poll_next_unpin(cx) { - break + break; } } Poll::Ready(()) diff --git a/futures-util/benches/select.rs b/futures-util/benches/select.rs new file mode 100644 index 0000000000..5410a95299 --- /dev/null +++ b/futures-util/benches/select.rs @@ -0,0 +1,35 @@ +#![feature(test)] + +extern crate test; +use crate::test::Bencher; + +use futures::executor::block_on; +use futures::stream::{repeat, select, StreamExt}; + +#[bench] +fn select_streams(b: &mut Bencher) { + const STREAM_COUNT: usize = 10_000; + + b.iter(|| { + let stream1 = repeat(1).take(STREAM_COUNT); + let stream2 = repeat(2).take(STREAM_COUNT); + let stream3 = repeat(3).take(STREAM_COUNT); + let stream4 = repeat(4).take(STREAM_COUNT); + let stream5 = repeat(5).take(STREAM_COUNT); + let stream6 = repeat(6).take(STREAM_COUNT); + let stream7 = repeat(7).take(STREAM_COUNT); + let count = block_on(async { + let count = select( + stream1, + select( + stream2, + select(stream3, select(stream4, select(stream5, select(stream6, stream7)))), + ), + ) + .count() + .await; + count + }); + assert_eq!(count, STREAM_COUNT * 7); + }); +} diff --git a/futures-util/benches_disabled/bilock.rs b/futures-util/benches_disabled/bilock.rs deleted file mode 100644 index 48afe3c551..0000000000 --- a/futures-util/benches_disabled/bilock.rs +++ /dev/null @@ -1,126 +0,0 @@ -#![feature(test)] - -#[cfg(feature = "bilock")] -mod bench { -use futures::task::{Context, Waker}; -use futures::executor::LocalPool; -use futures_util::lock::BiLock; -use futures_util::lock::BiLockAcquire; -use futures_util::lock::BiLockAcquired; -use futures_util::task::ArcWake; - -use std::sync::Arc; -use test::Bencher; - -fn notify_noop() -> Waker { - struct Noop; - - impl ArcWake for Noop { - fn wake(_: &Arc) {} - } - - ArcWake::into_waker(Arc::new(Noop)) -} - - -/// Pseudo-stream which simply calls `lock.poll()` on `poll` -struct LockStream { - lock: BiLockAcquire, -} - -impl LockStream { - fn new(lock: BiLock) -> Self { - Self { - lock: lock.lock() - } - } - - /// Release a lock after it was acquired in `poll`, - /// so `poll` could be called again. - fn release_lock(&mut self, guard: BiLockAcquired) { - self.lock = guard.unlock().lock() - } -} - -impl Stream for LockStream { - type Item = BiLockAcquired; - type Error = (); - - fn poll_next(&mut self, cx: &mut Context<'_>) -> Poll, Self::Error> { - self.lock.poll(cx).map(|a| a.map(Some)) - } -} - - -#[bench] -fn contended(b: &mut Bencher) { - let pool = LocalPool::new(); - let mut exec = pool.executor(); - let waker = notify_noop(); - let mut map = task::LocalMap::new(); - let mut waker = task::Context::new(&mut map, &waker, &mut exec); - - b.iter(|| { - let (x, y) = BiLock::new(1); - - let mut x = LockStream::new(x); - let mut y = LockStream::new(y); - - for _ in 0..1000 { - let x_guard = match x.poll_next(&mut waker) { - Ok(Poll::Ready(Some(guard))) => guard, - _ => panic!(), - }; - - // Try poll second lock while first lock still holds the lock - match y.poll_next(&mut waker) { - Ok(Poll::Pending) => (), - _ => panic!(), - }; - - x.release_lock(x_guard); - - let y_guard = match y.poll_next(&mut waker) { - Ok(Poll::Ready(Some(guard))) => guard, - _ => panic!(), - }; - - y.release_lock(y_guard); - } - (x, y) - }); -} - -#[bench] -fn lock_unlock(b: &mut Bencher) { - let pool = LocalPool::new(); - let mut exec = pool.executor(); - let waker = notify_noop(); - let mut map = task::LocalMap::new(); - let mut waker = task::Context::new(&mut map, &waker, &mut exec); - - b.iter(|| { - let (x, y) = BiLock::new(1); - - let mut x = LockStream::new(x); - let mut y = LockStream::new(y); - - for _ in 0..1000 { - let x_guard = match x.poll_next(&mut waker) { - Ok(Poll::Ready(Some(guard))) => guard, - _ => panic!(), - }; - - x.release_lock(x_guard); - - let y_guard = match y.poll_next(&mut waker) { - Ok(Poll::Ready(Some(guard))) => guard, - _ => panic!(), - }; - - y.release_lock(y_guard); - } - (x, y) - }) -} -} diff --git a/futures-util/src/abortable.rs b/futures-util/src/abortable.rs new file mode 100644 index 0000000000..e1e79e371d --- /dev/null +++ b/futures-util/src/abortable.rs @@ -0,0 +1,209 @@ +use crate::task::AtomicWaker; +use alloc::sync::Arc; +use core::fmt; +use core::pin::Pin; +use core::sync::atomic::{AtomicBool, Ordering}; +use futures_core::future::Future; +use futures_core::task::{Context, Poll}; +use futures_core::Stream; +use pin_project_lite::pin_project; + +pin_project! { + /// A future/stream which can be remotely short-circuited using an `AbortHandle`. + #[derive(Debug, Clone)] + #[must_use = "futures/streams do nothing unless you poll them"] + pub struct Abortable { + #[pin] + task: T, + inner: Arc, + } +} + +impl Abortable { + /// Creates a new `Abortable` future/stream using an existing `AbortRegistration`. + /// `AbortRegistration`s can be acquired through `AbortHandle::new`. + /// + /// When `abort` is called on the handle tied to `reg` or if `abort` has + /// already been called, the future/stream will complete immediately without making + /// any further progress. + /// + /// # Examples: + /// + /// Usage with futures: + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::future::{Abortable, AbortHandle, Aborted}; + /// + /// let (abort_handle, abort_registration) = AbortHandle::new_pair(); + /// let future = Abortable::new(async { 2 }, abort_registration); + /// abort_handle.abort(); + /// assert_eq!(future.await, Err(Aborted)); + /// # }); + /// ``` + /// + /// Usage with streams: + /// + /// ``` + /// # futures::executor::block_on(async { + /// # use futures::future::{Abortable, AbortHandle}; + /// # use futures::stream::{self, StreamExt}; + /// + /// let (abort_handle, abort_registration) = AbortHandle::new_pair(); + /// let mut stream = Abortable::new(stream::iter(vec![1, 2, 3]), abort_registration); + /// abort_handle.abort(); + /// assert_eq!(stream.next().await, None); + /// # }); + /// ``` + pub fn new(task: T, reg: AbortRegistration) -> Self { + Self { task, inner: reg.inner } + } + + /// Checks whether the task has been aborted. Note that all this + /// method indicates is whether [`AbortHandle::abort`] was *called*. + /// This means that it will return `true` even if: + /// * `abort` was called after the task had completed. + /// * `abort` was called while the task was being polled - the task may still be running and + /// will not be stopped until `poll` returns. + pub fn is_aborted(&self) -> bool { + self.inner.aborted.load(Ordering::Relaxed) + } +} + +/// A registration handle for an `Abortable` task. +/// Values of this type can be acquired from `AbortHandle::new` and are used +/// in calls to `Abortable::new`. +#[derive(Debug)] +pub struct AbortRegistration { + pub(crate) inner: Arc, +} + +impl AbortRegistration { + /// Create an [`AbortHandle`] from the given [`AbortRegistration`]. + /// + /// The created [`AbortHandle`] is functionally the same as any other + /// [`AbortHandle`]s that are associated with the same [`AbortRegistration`], + /// such as the one created by [`AbortHandle::new_pair`]. + pub fn handle(&self) -> AbortHandle { + AbortHandle { inner: self.inner.clone() } + } +} + +/// A handle to an `Abortable` task. +#[derive(Debug, Clone)] +pub struct AbortHandle { + inner: Arc, +} + +impl AbortHandle { + /// Creates an (`AbortHandle`, `AbortRegistration`) pair which can be used + /// to abort a running future or stream. + /// + /// This function is usually paired with a call to [`Abortable::new`]. + pub fn new_pair() -> (Self, AbortRegistration) { + let inner = + Arc::new(AbortInner { waker: AtomicWaker::new(), aborted: AtomicBool::new(false) }); + + (Self { inner: inner.clone() }, AbortRegistration { inner }) + } +} + +// Inner type storing the waker to awaken and a bool indicating that it +// should be aborted. +#[derive(Debug)] +pub(crate) struct AbortInner { + pub(crate) waker: AtomicWaker, + pub(crate) aborted: AtomicBool, +} + +/// Indicator that the `Abortable` task was aborted. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct Aborted; + +impl fmt::Display for Aborted { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "`Abortable` future has been aborted") + } +} + +#[cfg(feature = "std")] +impl std::error::Error for Aborted {} + +impl Abortable { + fn try_poll( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + poll: impl Fn(Pin<&mut T>, &mut Context<'_>) -> Poll, + ) -> Poll> { + // Check if the task has been aborted + if self.is_aborted() { + return Poll::Ready(Err(Aborted)); + } + + // attempt to complete the task + if let Poll::Ready(x) = poll(self.as_mut().project().task, cx) { + return Poll::Ready(Ok(x)); + } + + // Register to receive a wakeup if the task is aborted in the future + self.inner.waker.register(cx.waker()); + + // Check to see if the task was aborted between the first check and + // registration. + // Checking with `is_aborted` which uses `Relaxed` is sufficient because + // `register` introduces an `AcqRel` barrier. + if self.is_aborted() { + return Poll::Ready(Err(Aborted)); + } + + Poll::Pending + } +} + +impl Future for Abortable +where + Fut: Future, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.try_poll(cx, |fut, cx| fut.poll(cx)) + } +} + +impl Stream for Abortable +where + St: Stream, +{ + type Item = St::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.try_poll(cx, |stream, cx| stream.poll_next(cx)).map(Result::ok).map(Option::flatten) + } +} + +impl AbortHandle { + /// Abort the `Abortable` stream/future associated with this handle. + /// + /// Notifies the Abortable task associated with this handle that it + /// should abort. Note that if the task is currently being polled on + /// another thread, it will not immediately stop running. Instead, it will + /// continue to run until its poll method returns. + pub fn abort(&self) { + self.inner.aborted.store(true, Ordering::Relaxed); + self.inner.waker.wake(); + } + + /// Checks whether [`AbortHandle::abort`] was *called* on any associated + /// [`AbortHandle`]s, which includes all the [`AbortHandle`]s linked with + /// the same [`AbortRegistration`]. This means that it will return `true` + /// even if: + /// * `abort` was called after the task had completed. + /// * `abort` was called while the task was being polled - the task may still be running and + /// will not be stopped until `poll` returns. + /// + /// This operation has a Relaxed ordering. + pub fn is_aborted(&self) -> bool { + self.inner.aborted.load(Ordering::Relaxed) + } +} diff --git a/futures-util/src/async_await/join_mod.rs b/futures-util/src/async_await/join_mod.rs index 965d9fb236..28f3b232e7 100644 --- a/futures-util/src/async_await/join_mod.rs +++ b/futures-util/src/async_await/join_mod.rs @@ -1,7 +1,5 @@ //! The `join` macro. -use proc_macro_hack::proc_macro_hack; - macro_rules! document_join_macro { ($join:item $try_join:item) => { /// Polls multiple futures simultaneously, returning a tuple @@ -81,12 +79,12 @@ macro_rules! document_join_macro { } } +#[allow(unreachable_pub)] #[doc(hidden)] -#[proc_macro_hack(support_nested, only_hack_old_rustc)] pub use futures_macro::join_internal; +#[allow(unreachable_pub)] #[doc(hidden)] -#[proc_macro_hack(support_nested, only_hack_old_rustc)] pub use futures_macro::try_join_internal; document_join_macro! { diff --git a/futures-util/src/async_await/mod.rs b/futures-util/src/async_await/mod.rs index bdaed95b0c..7e3f12c99f 100644 --- a/futures-util/src/async_await/mod.rs +++ b/futures-util/src/async_await/mod.rs @@ -3,8 +3,8 @@ //! This module contains a number of functions and combinators for working //! with `async`/`await` code. -use futures_core::future::{Future, FusedFuture}; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::future::{FusedFuture, Future}; +use futures_core::stream::{FusedStream, Stream}; #[macro_use] mod poll; @@ -30,6 +30,15 @@ mod select_mod; #[cfg(feature = "async-await-macro")] pub use self::select_mod::*; +// Primary export is a macro +#[cfg(feature = "std")] +#[cfg(feature = "async-await-macro")] +mod stream_select_mod; +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/64762 +#[cfg(feature = "std")] +#[cfg(feature = "async-await-macro")] +pub use self::stream_select_mod::*; + #[cfg(feature = "std")] #[cfg(feature = "async-await-macro")] mod random; diff --git a/futures-util/src/async_await/pending.rs b/futures-util/src/async_await/pending.rs index e0cf341a8b..5d7a431811 100644 --- a/futures-util/src/async_await/pending.rs +++ b/futures-util/src/async_await/pending.rs @@ -16,7 +16,7 @@ use futures_core::task::{Context, Poll}; macro_rules! pending { () => { $crate::__private::async_await::pending_once().await - } + }; } #[doc(hidden)] diff --git a/futures-util/src/async_await/poll.rs b/futures-util/src/async_await/poll.rs index b5782df130..b62f45a943 100644 --- a/futures-util/src/async_await/poll.rs +++ b/futures-util/src/async_await/poll.rs @@ -17,7 +17,7 @@ use futures_core::task::{Context, Poll}; macro_rules! poll { ($x:expr $(,)?) => { $crate::__private::async_await::poll($x).await - } + }; } #[doc(hidden)] diff --git a/futures-util/src/async_await/random.rs b/futures-util/src/async_await/random.rs index 4f8c7254b4..2ac2f78a87 100644 --- a/futures-util/src/async_await/random.rs +++ b/futures-util/src/async_await/random.rs @@ -25,7 +25,7 @@ fn gen_index(n: usize) -> usize { /// /// [xorshift*]: https://en.wikipedia.org/wiki/Xorshift#xorshift* fn random() -> u64 { - thread_local! { + std::thread_local! { static RNG: Cell> = Cell::new(Wrapping(prng_seed())); } diff --git a/futures-util/src/async_await/select_mod.rs b/futures-util/src/async_await/select_mod.rs index 59bca0840a..80c3c7504a 100644 --- a/futures-util/src/async_await/select_mod.rs +++ b/futures-util/src/async_await/select_mod.rs @@ -1,10 +1,9 @@ //! The `select` macro. -use proc_macro_hack::proc_macro_hack; - macro_rules! document_select_macro { // This branch is required for `futures 0.3.1`, from before select_biased was introduced ($select:item) => { + #[allow(clippy::too_long_first_doc_paragraph)] /// Polls multiple futures and streams simultaneously, executing the branch /// for the future that finishes first. If multiple futures are ready, /// one will be pseudo-randomly selected at runtime. Futures directly @@ -31,9 +30,6 @@ macro_rules! document_select_macro { /// It is also gated behind the `async-await` feature of this library, which is /// activated by default. /// - /// Note that `select!` relies on `proc-macro-hack`, and may require to set the - /// compiler's recursion limit very high, e.g. `#![recursion_limit="1024"]`. - /// /// # Examples /// /// ``` @@ -158,6 +154,7 @@ macro_rules! document_select_macro { ($select:item $select_biased:item) => { document_select_macro!($select); + #[allow(clippy::too_long_first_doc_paragraph)] /// Polls multiple futures and streams simultaneously, executing the branch /// for the future that finishes first. Unlike [`select!`], if multiple futures are ready, /// one will be selected in order of declaration. Futures directly @@ -309,12 +306,12 @@ macro_rules! document_select_macro { } #[cfg(feature = "std")] +#[allow(unreachable_pub)] #[doc(hidden)] -#[proc_macro_hack(support_nested, only_hack_old_rustc)] pub use futures_macro::select_internal; +#[allow(unreachable_pub)] #[doc(hidden)] -#[proc_macro_hack(support_nested, only_hack_old_rustc)] pub use futures_macro::select_biased_internal; document_select_macro! { diff --git a/futures-util/src/async_await/stream_select_mod.rs b/futures-util/src/async_await/stream_select_mod.rs new file mode 100644 index 0000000000..c3e4f0052c --- /dev/null +++ b/futures-util/src/async_await/stream_select_mod.rs @@ -0,0 +1,39 @@ +//! The `stream_select` macro. + +#[allow(unreachable_pub)] +#[doc(hidden)] +pub use futures_macro::stream_select_internal; + +#[allow(clippy::too_long_first_doc_paragraph)] +/// Combines several streams, all producing the same `Item` type, into one stream. +/// This is similar to `select_all` but does not require the streams to all be the same type. +/// It also keeps the streams inline, and does not require `Box`s to be allocated. +/// Streams passed to this macro must be `Unpin`. +/// +/// If multiple streams are ready, one will be pseudo randomly selected at runtime. +/// +/// # Examples +/// +/// ``` +/// # futures::executor::block_on(async { +/// use futures::{stream, StreamExt, stream_select}; +/// let endless_ints = |i| stream::iter(vec![i].into_iter().cycle()).fuse(); +/// +/// let mut endless_numbers = stream_select!(endless_ints(1i32), endless_ints(2), endless_ints(3)); +/// match endless_numbers.next().await { +/// Some(1) => println!("Got a 1"), +/// Some(2) => println!("Got a 2"), +/// Some(3) => println!("Got a 3"), +/// _ => unreachable!(), +/// } +/// # }); +/// ``` +#[macro_export] +macro_rules! stream_select { + ($($tokens:tt)*) => {{ + use $crate::__private as __futures_crate; + $crate::stream_select_internal! { + $( $tokens )* + } + }} +} diff --git a/futures-util/src/compat/compat01as03.rs b/futures-util/src/compat/compat01as03.rs index bc3aee3c0a..0de2d53301 100644 --- a/futures-util/src/compat/compat01as03.rs +++ b/futures-util/src/compat/compat01as03.rs @@ -1,18 +1,16 @@ use futures_01::executor::{ - spawn as spawn01, Notify as Notify01, NotifyHandle as NotifyHandle01, - Spawn as Spawn01, UnsafeNotify as UnsafeNotify01, -}; -use futures_01::{ - Async as Async01, Future as Future01, - Stream as Stream01, + spawn as spawn01, Notify as Notify01, NotifyHandle as NotifyHandle01, Spawn as Spawn01, + UnsafeNotify as UnsafeNotify01, }; +use futures_01::{Async as Async01, Future as Future01, Stream as Stream01}; #[cfg(feature = "sink")] use futures_01::{AsyncSink as AsyncSink01, Sink as Sink01}; -use futures_core::{task as task03, future::Future as Future03, stream::Stream as Stream03}; -use std::pin::Pin; -use std::task::Context; +use futures_core::{future::Future as Future03, stream::Stream as Stream03, task as task03}; #[cfg(feature = "sink")] use futures_sink::Sink as Sink03; +use std::boxed::Box; +use std::pin::Pin; +use std::task::Context; #[cfg(feature = "io-compat")] #[cfg_attr(docsrs, doc(cfg(feature = "io-compat")))] @@ -33,9 +31,7 @@ impl Compat01As03 { /// Wraps a futures 0.1 Future, Stream, AsyncRead, or AsyncWrite /// object in a futures 0.3-compatible wrapper. pub fn new(object: T) -> Self { - Self { - inner: spawn01(object), - } + Self { inner: spawn01(object) } } fn in_notify(&mut self, cx: &mut Context<'_>, f: impl FnOnce(&mut T) -> R) -> R { @@ -69,6 +65,7 @@ pub trait Future01CompatExt: Future01 { /// [`Future>`](futures_core::future::Future). /// /// ``` + /// # if cfg!(miri) { return; } // https://github.com/rust-lang/futures-rs/issues/2514 /// # futures::executor::block_on(async { /// # // TODO: These should be all using `futures::compat`, but that runs up against Cargo /// # // feature issues @@ -95,6 +92,7 @@ pub trait Stream01CompatExt: Stream01 { /// [`Stream>`](futures_core::stream::Stream). /// /// ``` + /// # if cfg!(miri) { return; } // https://github.com/rust-lang/futures-rs/issues/2514 /// # futures::executor::block_on(async { /// use futures::stream::StreamExt; /// use futures_util::compat::Stream01CompatExt; @@ -124,6 +122,7 @@ pub trait Sink01CompatExt: Sink01 { /// [`Sink`](futures_sink::Sink). /// /// ``` + /// # if cfg!(miri) { return; } // https://github.com/rust-lang/futures-rs/issues/2514 /// # futures::executor::block_on(async { /// use futures::{sink::SinkExt, stream::StreamExt}; /// use futures_util::compat::{Stream01CompatExt, Sink01CompatExt}; @@ -157,10 +156,7 @@ fn poll_01_to_03(x: Result, E>) -> task03::Poll> { impl Future03 for Compat01As03 { type Output = Result; - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> task03::Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> task03::Poll { poll_01_to_03(self.in_notify(cx, Future01::poll)) } } @@ -198,18 +194,10 @@ impl Unpin for Compat01As03Sink {} impl Compat01As03Sink { /// Wraps a futures 0.1 Sink object in a futures 0.3-compatible wrapper. pub fn new(inner: S) -> Self { - Self { - inner: spawn01(inner), - buffer: None, - close_started: false - } + Self { inner: spawn01(inner), buffer: None, close_started: false } } - fn in_notify( - &mut self, - cx: &mut Context<'_>, - f: impl FnOnce(&mut S) -> R, - ) -> R { + fn in_notify(&mut self, cx: &mut Context<'_>, f: impl FnOnce(&mut S) -> R) -> R { let notify = &WakerToHandle(cx.waker()); self.inner.poll_fn_notify(notify, 0, f) } @@ -256,10 +244,7 @@ where { type Error = S::SinkError; - fn start_send( - mut self: Pin<&mut Self>, - item: SinkItem, - ) -> Result<(), Self::Error> { + fn start_send(mut self: Pin<&mut Self>, item: SinkItem) -> Result<(), Self::Error> { debug_assert!(self.buffer.is_none()); self.buffer = Some(item); Ok(()) @@ -289,9 +274,7 @@ where match self.in_notify(cx, |f| match item { Some(i) => match f.start_send(i)? { AsyncSink01::Ready => f.poll_complete().map(|i| (i, None)), - AsyncSink01::NotReady(t) => { - Ok((Async01::NotReady, Some(t))) - } + AsyncSink01::NotReady(t) => Ok((Async01::NotReady, Some(t))), }, None => f.poll_complete().map(|i| (i, None)), })? { @@ -364,7 +347,7 @@ unsafe impl UnsafeNotify01 for NotifyWaker { unsafe fn drop_raw(&self) { let ptr: *const dyn UnsafeNotify01 = self; - drop(Box::from_raw(ptr as *mut dyn UnsafeNotify01)); + drop(unsafe { Box::from_raw(ptr as *mut dyn UnsafeNotify01) }); } } @@ -372,8 +355,6 @@ unsafe impl UnsafeNotify01 for NotifyWaker { #[cfg_attr(docsrs, doc(cfg(feature = "io-compat")))] mod io { use super::*; - #[cfg(feature = "read-initializer")] - use futures_io::Initializer; use futures_io::{AsyncRead as AsyncRead03, AsyncWrite as AsyncWrite03}; use std::io::Error; use tokio_io::{AsyncRead as AsyncRead01, AsyncWrite as AsyncWrite01}; @@ -385,6 +366,7 @@ mod io { /// [`AsyncRead`](futures_io::AsyncRead). /// /// ``` + /// # if cfg!(miri) { return; } // https://github.com/rust-lang/futures-rs/issues/2514 /// # futures::executor::block_on(async { /// use futures::io::AsyncReadExt; /// use futures_util::compat::AsyncRead01CompatExt; @@ -414,6 +396,7 @@ mod io { /// [`AsyncWrite`](futures_io::AsyncWrite). /// /// ``` + /// # if cfg!(miri) { return; } // https://github.com/rust-lang/futures-rs/issues/2514 /// # futures::executor::block_on(async { /// use futures::io::AsyncWriteExt; /// use futures_util::compat::AsyncWrite01CompatExt; @@ -437,39 +420,35 @@ mod io { impl AsyncWrite01CompatExt for W {} impl AsyncRead03 for Compat01As03 { - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - // check if `prepare_uninitialized_buffer` needs zeroing - if self.inner.get_ref().prepare_uninitialized_buffer(&mut [1]) { - Initializer::zeroing() - } else { - Initializer::nop() - } - } - - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> task03::Poll> - { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> task03::Poll> { poll_01_to_03(self.in_notify(cx, |x| x.poll_read(buf))) } } impl AsyncWrite03 for Compat01As03 { - fn poll_write(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) - -> task03::Poll> - { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> task03::Poll> { poll_01_to_03(self.in_notify(cx, |x| x.poll_write(buf))) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) - -> task03::Poll> - { + fn poll_flush( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> task03::Poll> { poll_01_to_03(self.in_notify(cx, AsyncWrite01::poll_flush)) } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) - -> task03::Poll> - { + fn poll_close( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> task03::Poll> { poll_01_to_03(self.in_notify(cx, AsyncWrite01::shutdown)) } } diff --git a/futures-util/src/compat/compat03as01.rs b/futures-util/src/compat/compat03as01.rs index 3f1eebbf1d..3c5bb77b49 100644 --- a/futures-util/src/compat/compat03as01.rs +++ b/futures-util/src/compat/compat03as01.rs @@ -1,32 +1,21 @@ +use crate::task::{self as task03, ArcWake as ArcWake03, WakerRef}; use futures_01::{ - task as task01, Async as Async01, Future as Future01, Poll as Poll01, - Stream as Stream01, + task as task01, Async as Async01, Future as Future01, Poll as Poll01, Stream as Stream01, }; #[cfg(feature = "sink")] -use futures_01::{ - AsyncSink as AsyncSink01, Sink as Sink01, StartSend as StartSend01, -}; +use futures_01::{AsyncSink as AsyncSink01, Sink as Sink01, StartSend as StartSend01}; use futures_core::{ - task::{RawWaker, RawWakerVTable}, future::TryFuture as TryFuture03, stream::TryStream as TryStream03, + task::{RawWaker, RawWakerVTable}, }; #[cfg(feature = "sink")] use futures_sink::Sink as Sink03; -use crate::task::{ - self as task03, - ArcWake as ArcWake03, - WakerRef, -}; #[cfg(feature = "sink")] use std::marker::PhantomData; -use std::{ - mem, - pin::Pin, - sync::Arc, - task::Context, -}; +use std::{mem, pin::Pin, sync::Arc, task::Context}; +#[allow(clippy::too_long_first_doc_paragraph)] // clippy bug, see https://github.com/rust-lang/rust-clippy/issues/13315 /// Converts a futures 0.3 [`TryFuture`](futures_core::future::TryFuture) or /// [`TryStream`](futures_core::stream::TryStream) into a futures 0.1 /// [`Future`](futures_01::future::Future) or @@ -80,10 +69,7 @@ impl Compat { impl CompatSink { /// Creates a new [`CompatSink`]. pub fn new(inner: T) -> Self { - Self { - inner, - _phantom: PhantomData, - } + Self { inner, _phantom: PhantomData } } /// Get a reference to 0.3 Sink contained within. @@ -102,9 +88,7 @@ impl CompatSink { } } -fn poll_03_to_01(x: task03::Poll>) - -> Result, E> -{ +fn poll_03_to_01(x: task03::Poll>) -> Result, E> { match x? { task03::Poll::Ready(t) => Ok(Async01::Ready(t)), task03::Poll::Pending => Ok(Async01::NotReady), @@ -147,17 +131,10 @@ where type SinkItem = Item; type SinkError = T::Error; - fn start_send( - &mut self, - item: Self::SinkItem, - ) -> StartSend01 { - with_sink_context(self, |mut inner, cx| { - match inner.as_mut().poll_ready(cx)? { - task03::Poll::Ready(()) => { - inner.start_send(item).map(|()| AsyncSink01::Ready) - } - task03::Poll::Pending => Ok(AsyncSink01::NotReady(item)), - } + fn start_send(&mut self, item: Self::SinkItem) -> StartSend01 { + with_sink_context(self, |mut inner, cx| match inner.as_mut().poll_ready(cx)? { + task03::Poll::Ready(()) => inner.start_send(item).map(|()| AsyncSink01::Ready), + task03::Poll::Pending => Ok(AsyncSink01::NotReady(item)), }) } @@ -180,7 +157,7 @@ impl Current { fn as_waker(&self) -> WakerRef<'_> { unsafe fn ptr_to_current<'a>(ptr: *const ()) -> &'a Current { - &*(ptr as *const Current) + unsafe { &*(ptr as *const Current) } } fn current_to_ptr(current: &Current) -> *const () { current as *const Current as *const () @@ -190,13 +167,15 @@ impl Current { // Lazily create the `Arc` only when the waker is actually cloned. // FIXME: remove `transmute` when a `Waker` -> `RawWaker` conversion // function is landed in `core`. - mem::transmute::( - task03::waker(Arc::new(ptr_to_current(ptr).clone())) - ) + unsafe { + mem::transmute::(task03::waker(Arc::new( + ptr_to_current(ptr).clone(), + ))) + } } unsafe fn drop(_: *const ()) {} unsafe fn wake(ptr: *const ()) { - ptr_to_current(ptr).0.notify() + unsafe { ptr_to_current(ptr).0.notify() } } let ptr = current_to_ptr(self); @@ -243,9 +222,7 @@ mod io { use futures_io::{AsyncRead as AsyncRead03, AsyncWrite as AsyncWrite03}; use tokio_io::{AsyncRead as AsyncRead01, AsyncWrite as AsyncWrite01}; - fn poll_03_to_io(x: task03::Poll>) - -> Result - { + fn poll_03_to_io(x: task03::Poll>) -> Result { match x { task03::Poll::Ready(Ok(t)) => Ok(t), task03::Poll::Pending => Err(std::io::ErrorKind::WouldBlock.into()), @@ -262,17 +239,7 @@ mod io { } } - impl AsyncRead01 for Compat { - #[cfg(feature = "read-initializer")] - unsafe fn prepare_uninitialized_buffer(&self, buf: &mut [u8]) -> bool { - let initializer = self.inner.initializer(); - let does_init = initializer.should_initialize(); - if does_init { - initializer.initialize(buf); - } - does_init - } - } + impl AsyncRead01 for Compat {} impl std::io::Write for Compat { fn write(&mut self, buf: &[u8]) -> std::io::Result { diff --git a/futures-util/src/compat/executor.rs b/futures-util/src/compat/executor.rs index 82cb496a70..ea0c67a0ae 100644 --- a/futures-util/src/compat/executor.rs +++ b/futures-util/src/compat/executor.rs @@ -17,6 +17,7 @@ pub trait Executor01CompatExt: Executor01 + Clone + Send + 'st /// futures 0.3 [`Spawn`](futures_task::Spawn). /// /// ``` + /// # if cfg!(miri) { return; } // Miri does not support epoll /// use futures::task::SpawnExt; /// use futures::future::{FutureExt, TryFutureExt}; /// use futures_util::compat::Executor01CompatExt; @@ -66,9 +67,7 @@ where fn spawn_obj(&self, future: FutureObj<'static, ()>) -> Result<(), SpawnError03> { let future = future.unit_error().compat(); - self.executor01 - .execute(future) - .map_err(|_| SpawnError03::shutdown()) + self.executor01.execute(future).map_err(|_| SpawnError03::shutdown()) } } diff --git a/futures-util/src/compat/mod.rs b/futures-util/src/compat/mod.rs index c5edcc580c..4812803eb6 100644 --- a/futures-util/src/compat/mod.rs +++ b/futures-util/src/compat/mod.rs @@ -4,16 +4,16 @@ //! library is activated. mod executor; -pub use self::executor::{Executor01CompatExt, Executor01Future, Executor01As03}; +pub use self::executor::{Executor01As03, Executor01CompatExt, Executor01Future}; mod compat01as03; +#[cfg(feature = "io-compat")] +#[cfg_attr(docsrs, doc(cfg(feature = "io-compat")))] +pub use self::compat01as03::{AsyncRead01CompatExt, AsyncWrite01CompatExt}; pub use self::compat01as03::{Compat01As03, Future01CompatExt, Stream01CompatExt}; #[cfg(feature = "sink")] #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] pub use self::compat01as03::{Compat01As03Sink, Sink01CompatExt}; -#[cfg(feature = "io-compat")] -#[cfg_attr(docsrs, doc(cfg(feature = "io-compat")))] -pub use self::compat01as03::{AsyncRead01CompatExt, AsyncWrite01CompatExt}; mod compat03as01; pub use self::compat03as01::Compat; diff --git a/futures-util/src/fns.rs b/futures-util/src/fns.rs index cb623918dd..37ee03e6df 100644 --- a/futures-util/src/fns.rs +++ b/futures-util/src/fns.rs @@ -1,5 +1,5 @@ -use core::marker::PhantomData; use core::fmt::{self, Debug}; +use core::marker::PhantomData; pub trait FnOnce1 { type Output; @@ -8,7 +8,7 @@ pub trait FnOnce1 { impl FnOnce1 for T where - T: FnOnce(A) -> R + T: FnOnce(A) -> R, { type Output = R; fn call_once(self, arg: A) -> R { @@ -22,7 +22,7 @@ pub trait FnMut1: FnOnce1 { impl FnMut1 for T where - T: FnMut(A) -> R + T: FnMut(A) -> R, { fn call_mut(&mut self, arg: A) -> R { self(arg) @@ -37,7 +37,7 @@ pub trait Fn1: FnMut1 { impl Fn1 for T where - T: Fn(A) -> R + T: Fn(A) -> R, { fn call(&self, arg: A) -> R { self(arg) @@ -143,7 +143,7 @@ pub struct InspectFn(F); #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 impl FnOnce1 for InspectFn where - F: for<'a> FnOnce1<&'a A, Output=()>, + F: for<'a> FnOnce1<&'a A, Output = ()>, { type Output = A; fn call_once(self, arg: A) -> Self::Output { @@ -154,7 +154,7 @@ where #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 impl FnMut1 for InspectFn where - F: for<'a> FnMut1<&'a A, Output=()>, + F: for<'a> FnMut1<&'a A, Output = ()>, { fn call_mut(&mut self, arg: A) -> Self::Output { self.0.call_mut(&arg); @@ -164,7 +164,7 @@ where #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 impl Fn1 for InspectFn where - F: for<'a> Fn1<&'a A, Output=()>, + F: for<'a> Fn1<&'a A, Output = ()>, { fn call(&self, arg: A) -> Self::Output { self.0.call(&arg); @@ -244,27 +244,33 @@ pub struct InspectOkFn(F); impl<'a, F, T, E> FnOnce1<&'a Result> for InspectOkFn where - F: FnOnce1<&'a T, Output=()> + F: FnOnce1<&'a T, Output = ()>, { type Output = (); fn call_once(self, arg: &'a Result) -> Self::Output { - if let Ok(x) = arg { self.0.call_once(x) } + if let Ok(x) = arg { + self.0.call_once(x) + } } } impl<'a, F, T, E> FnMut1<&'a Result> for InspectOkFn where - F: FnMut1<&'a T, Output=()>, + F: FnMut1<&'a T, Output = ()>, { fn call_mut(&mut self, arg: &'a Result) -> Self::Output { - if let Ok(x) = arg { self.0.call_mut(x) } + if let Ok(x) = arg { + self.0.call_mut(x) + } } } impl<'a, F, T, E> Fn1<&'a Result> for InspectOkFn where - F: Fn1<&'a T, Output=()>, + F: Fn1<&'a T, Output = ()>, { fn call(&self, arg: &'a Result) -> Self::Output { - if let Ok(x) = arg { self.0.call(x) } + if let Ok(x) = arg { + self.0.call(x) + } } } pub(crate) fn inspect_ok_fn(f: F) -> InspectOkFn { @@ -276,27 +282,33 @@ pub struct InspectErrFn(F); impl<'a, F, T, E> FnOnce1<&'a Result> for InspectErrFn where - F: FnOnce1<&'a E, Output=()> + F: FnOnce1<&'a E, Output = ()>, { type Output = (); fn call_once(self, arg: &'a Result) -> Self::Output { - if let Err(x) = arg { self.0.call_once(x) } + if let Err(x) = arg { + self.0.call_once(x) + } } } impl<'a, F, T, E> FnMut1<&'a Result> for InspectErrFn where - F: FnMut1<&'a E, Output=()>, + F: FnMut1<&'a E, Output = ()>, { fn call_mut(&mut self, arg: &'a Result) -> Self::Output { - if let Err(x) = arg { self.0.call_mut(x) } + if let Err(x) = arg { + self.0.call_mut(x) + } } } impl<'a, F, T, E> Fn1<&'a Result> for InspectErrFn where - F: Fn1<&'a E, Output=()>, + F: Fn1<&'a E, Output = ()>, { fn call(&self, arg: &'a Result) -> Self::Output { - if let Err(x) = arg { self.0.call(x) } + if let Err(x) = arg { + self.0.call(x) + } } } pub(crate) fn inspect_err_fn(f: F) -> InspectErrFn { @@ -313,7 +325,7 @@ pub struct UnwrapOrElseFn(F); impl FnOnce1> for UnwrapOrElseFn where - F: FnOnce1, + F: FnOnce1, { type Output = T; fn call_once(self, arg: Result) -> Self::Output { @@ -322,7 +334,7 @@ where } impl FnMut1> for UnwrapOrElseFn where - F: FnMut1, + F: FnMut1, { fn call_mut(&mut self, arg: Result) -> Self::Output { arg.unwrap_or_else(|x| self.0.call_mut(x)) @@ -330,7 +342,7 @@ where } impl Fn1> for UnwrapOrElseFn where - F: Fn1, + F: Fn1, { fn call(&self, arg: Result) -> Self::Output { arg.unwrap_or_else(|x| self.0.call(x)) @@ -347,7 +359,10 @@ impl Default for IntoFn { Self(PhantomData) } } -impl FnOnce1 for IntoFn where A: Into { +impl FnOnce1 for IntoFn +where + A: Into, +{ type Output = T; fn call_once(self, arg: A) -> Self::Output { arg.into() diff --git a/futures-util/src/future/abortable.rs b/futures-util/src/future/abortable.rs index 3f2e5a064d..d017ab7340 100644 --- a/futures-util/src/future/abortable.rs +++ b/futures-util/src/future/abortable.rs @@ -1,110 +1,8 @@ use super::assert_future; -use crate::task::AtomicWaker; +use crate::future::{AbortHandle, Abortable, Aborted}; use futures_core::future::Future; -use futures_core::task::{Context, Poll}; -use core::fmt; -use core::pin::Pin; -use core::sync::atomic::{AtomicBool, Ordering}; -use alloc::sync::Arc; -use pin_project_lite::pin_project; -pin_project! { - /// A future which can be remotely short-circuited using an `AbortHandle`. - #[derive(Debug, Clone)] - #[must_use = "futures do nothing unless you `.await` or poll them"] - pub struct Abortable { - #[pin] - future: Fut, - inner: Arc, - } -} - -impl Abortable where Fut: Future { - /// Creates a new `Abortable` future using an existing `AbortRegistration`. - /// `AbortRegistration`s can be acquired through `AbortHandle::new`. - /// - /// When `abort` is called on the handle tied to `reg` or if `abort` has - /// already been called, the future will complete immediately without making - /// any further progress. - /// - /// Example: - /// - /// ``` - /// # futures::executor::block_on(async { - /// use futures::future::{Abortable, AbortHandle, Aborted}; - /// - /// let (abort_handle, abort_registration) = AbortHandle::new_pair(); - /// let future = Abortable::new(async { 2 }, abort_registration); - /// abort_handle.abort(); - /// assert_eq!(future.await, Err(Aborted)); - /// # }); - /// ``` - pub fn new(future: Fut, reg: AbortRegistration) -> Self { - assert_future::, _>(Self { - future, - inner: reg.inner, - }) - } -} - -/// A registration handle for a `Abortable` future. -/// Values of this type can be acquired from `AbortHandle::new` and are used -/// in calls to `Abortable::new`. -#[derive(Debug)] -pub struct AbortRegistration { - inner: Arc, -} - -/// A handle to a `Abortable` future. -#[derive(Debug, Clone)] -pub struct AbortHandle { - inner: Arc, -} - -impl AbortHandle { - /// Creates an (`AbortHandle`, `AbortRegistration`) pair which can be used - /// to abort a running future. - /// - /// This function is usually paired with a call to `Abortable::new`. - /// - /// Example: - /// - /// ``` - /// # futures::executor::block_on(async { - /// use futures::future::{Abortable, AbortHandle, Aborted}; - /// - /// let (abort_handle, abort_registration) = AbortHandle::new_pair(); - /// let future = Abortable::new(async { 2 }, abort_registration); - /// abort_handle.abort(); - /// assert_eq!(future.await, Err(Aborted)); - /// # }); - /// ``` - pub fn new_pair() -> (Self, AbortRegistration) { - let inner = Arc::new(AbortInner { - waker: AtomicWaker::new(), - cancel: AtomicBool::new(false), - }); - - ( - Self { - inner: inner.clone(), - }, - AbortRegistration { - inner, - }, - ) - } -} - -// Inner type storing the waker to awaken and a bool indicating that it -// should be cancelled. -#[derive(Debug)] -struct AbortInner { - waker: AtomicWaker, - cancel: AtomicBool, -} - -/// Creates a new `Abortable` future and a `AbortHandle` which can be used to stop it. +/// Creates a new `Abortable` future and an `AbortHandle` which can be used to stop it. /// /// This function is a convenient (but less flexible) alternative to calling /// `AbortHandle::new` and `Abortable::new` manually. @@ -112,66 +10,10 @@ struct AbortInner { /// This function is only available when the `std` or `alloc` feature of this /// library is activated, and it is activated by default. pub fn abortable(future: Fut) -> (Abortable, AbortHandle) - where Fut: Future +where + Fut: Future, { let (handle, reg) = AbortHandle::new_pair(); - ( - Abortable::new(future, reg), - handle, - ) -} - -/// Indicator that the `Abortable` future was aborted. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct Aborted; - -impl fmt::Display for Aborted { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "`Abortable` future has been aborted") - } -} - -#[cfg(feature = "std")] -impl std::error::Error for Aborted {} - -impl Future for Abortable where Fut: Future { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - // Check if the future has been aborted - if self.inner.cancel.load(Ordering::Relaxed) { - return Poll::Ready(Err(Aborted)) - } - - // attempt to complete the future - if let Poll::Ready(x) = self.as_mut().project().future.poll(cx) { - return Poll::Ready(Ok(x)) - } - - // Register to receive a wakeup if the future is aborted in the... future - self.inner.waker.register(cx.waker()); - - // Check to see if the future was aborted between the first check and - // registration. - // Checking with `Relaxed` is sufficient because `register` introduces an - // `AcqRel` barrier. - if self.inner.cancel.load(Ordering::Relaxed) { - return Poll::Ready(Err(Aborted)) - } - - Poll::Pending - } -} - -impl AbortHandle { - /// Abort the `Abortable` future associated with this handle. - /// - /// Notifies the Abortable future associated with this handle that it - /// should abort. Note that if the future is currently being polled on - /// another thread, it will not immediately stop running. Instead, it will - /// continue to run until its poll method returns. - pub fn abort(&self) { - self.inner.cancel.store(true, Ordering::Relaxed); - self.inner.waker.wake(); - } + let abortable = assert_future::, _>(Abortable::new(future, reg)); + (abortable, handle) } diff --git a/futures-util/src/future/always_ready.rs b/futures-util/src/future/always_ready.rs new file mode 100644 index 0000000000..28625e7272 --- /dev/null +++ b/futures-util/src/future/always_ready.rs @@ -0,0 +1,62 @@ +use super::assert_future; +use core::pin::Pin; +use futures_core::future::{FusedFuture, Future}; +use futures_core::task::{Context, Poll}; + +/// Future for the [`always_ready`](always_ready()) function. +#[must_use = "futures do nothing unless you `.await` or poll them"] +pub struct AlwaysReady T>(F); + +impl T> core::fmt::Debug for AlwaysReady { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + f.debug_tuple("AlwaysReady").finish() + } +} + +impl T + Clone> Clone for AlwaysReady { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} + +impl T + Copy> Copy for AlwaysReady {} + +impl T> Unpin for AlwaysReady {} + +impl T> FusedFuture for AlwaysReady { + fn is_terminated(&self) -> bool { + false + } +} + +impl T> Future for AlwaysReady { + type Output = T; + + #[inline] + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Ready(self.0()) + } +} + +/// Creates a future that is always immediately ready with a value. +/// +/// This is particularly useful in avoiding a heap allocation when an API needs [`Box>`], +/// as [`AlwaysReady`] does not have to store a boolean for `is_finished`. +/// +/// # Examples +/// +/// ``` +/// # futures::executor::block_on(async { +/// use std::mem::size_of_val; +/// +/// use futures::future; +/// +/// let a = future::always_ready(|| 1); +/// assert_eq!(size_of_val(&a), 0); +/// assert_eq!(a.await, 1); +/// assert_eq!(a.await, 1); +/// # }); +/// ``` +pub fn always_ready T>(prod: F) -> AlwaysReady { + assert_future::(AlwaysReady(prod)) +} diff --git a/futures-util/src/future/either.rs b/futures-util/src/future/either.rs index 5f5b614763..018e51e72f 100644 --- a/futures-util/src/future/either.rs +++ b/futures-util/src/future/either.rs @@ -5,8 +5,25 @@ use futures_core::stream::{FusedStream, Stream}; #[cfg(feature = "sink")] use futures_sink::Sink; -/// Combines two different futures, streams, or sinks having the same associated types into a single -/// type. +/// Combines two different futures, streams, or sinks having the same associated types into a single type. +/// +/// This is useful when conditionally choosing between two distinct future types: +/// +/// ```rust +/// use futures::future::Either; +/// +/// # futures::executor::block_on(async { +/// let cond = true; +/// +/// let fut = if cond { +/// Either::Left(async move { 12 }) +/// } else { +/// Either::Right(async move { 44 }) +/// }; +/// +/// assert_eq!(fut.await, 12); +/// # }) +/// ``` #[derive(Debug, Clone)] pub enum Either { /// First branch of the type @@ -16,11 +33,31 @@ pub enum Either { } impl Either { - fn project(self: Pin<&mut Self>) -> Either, Pin<&mut B>> { + /// Convert `Pin<&Either>` to `Either, Pin<&B>>`, + /// pinned projections of the inner variants. + pub fn as_pin_ref(self: Pin<&Self>) -> Either, Pin<&B>> { + // SAFETY: We can use `new_unchecked` because the `inner` parts are + // guaranteed to be pinned, as they come from `self` which is pinned. + unsafe { + match self.get_ref() { + Self::Left(inner) => Either::Left(Pin::new_unchecked(inner)), + Self::Right(inner) => Either::Right(Pin::new_unchecked(inner)), + } + } + } + + /// Convert `Pin<&mut Either>` to `Either, Pin<&mut B>>`, + /// pinned projections of the inner variants. + pub fn as_pin_mut(self: Pin<&mut Self>) -> Either, Pin<&mut B>> { + // SAFETY: `get_unchecked_mut` is fine because we don't move anything. + // We can use `new_unchecked` because the `inner` parts are guaranteed + // to be pinned, as they come from `self` which is pinned, and we never + // offer an unpinned `&mut A` or `&mut B` through `Pin<&mut Self>`. We + // also don't have an implementation of `Drop`, nor manual `Unpin`. unsafe { match self.get_unchecked_mut() { - Either::Left(a) => Either::Left(Pin::new_unchecked(a)), - Either::Right(b) => Either::Right(Pin::new_unchecked(b)), + Self::Left(inner) => Either::Left(Pin::new_unchecked(inner)), + Self::Right(inner) => Either::Right(Pin::new_unchecked(inner)), } } } @@ -32,8 +69,8 @@ impl Either<(T, A), (T, B)> { /// Here, the homogeneous type is the first element of the pairs. pub fn factor_first(self) -> (T, Either) { match self { - Either::Left((x, a)) => (x, Either::Left(a)), - Either::Right((x, b)) => (x, Either::Right(b)), + Self::Left((x, a)) => (x, Either::Left(a)), + Self::Right((x, b)) => (x, Either::Right(b)), } } } @@ -44,8 +81,8 @@ impl Either<(A, T), (B, T)> { /// Here, the homogeneous type is the second element of the pairs. pub fn factor_second(self) -> (Either, T) { match self { - Either::Left((a, x)) => (Either::Left(a), x), - Either::Right((b, x)) => (Either::Right(b), x), + Self::Left((a, x)) => (Either::Left(a), x), + Self::Right((b, x)) => (Either::Right(b), x), } } } @@ -54,8 +91,7 @@ impl Either { /// Extract the value of an either over two equivalent types. pub fn into_inner(self) -> T { match self { - Either::Left(x) => x, - Either::Right(x) => x, + Self::Left(x) | Self::Right(x) => x, } } } @@ -68,7 +104,7 @@ where type Output = A::Output; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll(cx), Either::Right(x) => x.poll(cx), } @@ -82,8 +118,8 @@ where { fn is_terminated(&self) -> bool { match self { - Either::Left(x) => x.is_terminated(), - Either::Right(x) => x.is_terminated(), + Self::Left(x) => x.is_terminated(), + Self::Right(x) => x.is_terminated(), } } } @@ -96,7 +132,7 @@ where type Item = A::Item; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_next(cx), Either::Right(x) => x.poll_next(cx), } @@ -104,8 +140,8 @@ where fn size_hint(&self) -> (usize, Option) { match self { - Either::Left(x) => x.size_hint(), - Either::Right(x) => x.size_hint(), + Self::Left(x) => x.size_hint(), + Self::Right(x) => x.size_hint(), } } } @@ -117,8 +153,8 @@ where { fn is_terminated(&self) -> bool { match self { - Either::Left(x) => x.is_terminated(), - Either::Right(x) => x.is_terminated(), + Self::Left(x) => x.is_terminated(), + Self::Right(x) => x.is_terminated(), } } } @@ -132,28 +168,28 @@ where type Error = A::Error; fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_ready(cx), Either::Right(x) => x.poll_ready(cx), } } fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.start_send(item), Either::Right(x) => x.start_send(item), } } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_flush(cx), Either::Right(x) => x.poll_flush(cx), } } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_close(cx), Either::Right(x) => x.poll_close(cx), } @@ -165,10 +201,6 @@ where mod if_std { use super::*; - use core::pin::Pin; - use core::task::{Context, Poll}; - #[cfg(feature = "read-initializer")] - use futures_io::Initializer; use futures_io::{ AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, IoSlice, IoSliceMut, Result, SeekFrom, }; @@ -178,20 +210,12 @@ mod if_std { A: AsyncRead, B: AsyncRead, { - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - match self { - Either::Left(x) => x.initializer(), - Either::Right(x) => x.initializer(), - } - } - fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_read(cx, buf), Either::Right(x) => x.poll_read(cx, buf), } @@ -202,7 +226,7 @@ mod if_std { cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>], ) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_read_vectored(cx, bufs), Either::Right(x) => x.poll_read_vectored(cx, bufs), } @@ -219,7 +243,7 @@ mod if_std { cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_write(cx, buf), Either::Right(x) => x.poll_write(cx, buf), } @@ -230,21 +254,21 @@ mod if_std { cx: &mut Context<'_>, bufs: &[IoSlice<'_>], ) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_write_vectored(cx, bufs), Either::Right(x) => x.poll_write_vectored(cx, bufs), } } fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_flush(cx), Either::Right(x) => x.poll_flush(cx), } } fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_close(cx), Either::Right(x) => x.poll_close(cx), } @@ -261,7 +285,7 @@ mod if_std { cx: &mut Context<'_>, pos: SeekFrom, ) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_seek(cx, pos), Either::Right(x) => x.poll_seek(cx, pos), } @@ -274,14 +298,14 @@ mod if_std { B: AsyncBufRead, { fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.poll_fill_buf(cx), Either::Right(x) => x.poll_fill_buf(cx), } } fn consume(self: Pin<&mut Self>, amt: usize) { - match self.project() { + match self.as_pin_mut() { Either::Left(x) => x.consume(amt), Either::Right(x) => x.consume(amt), } diff --git a/futures-util/src/future/future/catch_unwind.rs b/futures-util/src/future/future/catch_unwind.rs index 3f16577788..ed49e314d2 100644 --- a/futures-util/src/future/future/catch_unwind.rs +++ b/futures-util/src/future/future/catch_unwind.rs @@ -1,6 +1,7 @@ use core::any::Any; use core::pin::Pin; -use std::panic::{catch_unwind, UnwindSafe, AssertUnwindSafe}; +use std::boxed::Box; +use std::panic::{catch_unwind, AssertUnwindSafe, UnwindSafe}; use futures_core::future::Future; use futures_core::task::{Context, Poll}; @@ -16,14 +17,18 @@ pin_project! { } } -impl CatchUnwind where Fut: Future + UnwindSafe { +impl CatchUnwind +where + Fut: Future + UnwindSafe, +{ pub(super) fn new(future: Fut) -> Self { Self { future } } } impl Future for CatchUnwind - where Fut: Future + UnwindSafe, +where + Fut: Future + UnwindSafe, { type Output = Result>; diff --git a/futures-util/src/future/future/flatten.rs b/futures-util/src/future/future/flatten.rs index 0c48a4f1d4..bd767af344 100644 --- a/futures-util/src/future/future/flatten.rs +++ b/futures-util/src/future/future/flatten.rs @@ -2,9 +2,9 @@ use core::pin::Pin; use futures_core::future::{FusedFuture, Future}; use futures_core::ready; use futures_core::stream::{FusedStream, Stream}; +use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; -use futures_core::task::{Context, Poll}; use pin_project_lite::pin_project; pin_project! { @@ -24,8 +24,9 @@ impl Flatten { } impl FusedFuture for Flatten - where Fut: Future, - Fut::Output: Future, +where + Fut: Future, + Fut::Output: Future, { fn is_terminated(&self) -> bool { match self { @@ -36,8 +37,9 @@ impl FusedFuture for Flatten } impl Future for Flatten - where Fut: Future, - Fut::Output: Future, +where + Fut: Future, + Fut::Output: Future, { type Output = ::Output; @@ -47,12 +49,12 @@ impl Future for Flatten FlattenProj::First { f } => { let f = ready!(f.poll(cx)); self.set(Self::Second { f }); - }, + } FlattenProj::Second { f } => { let output = ready!(f.poll(cx)); self.set(Self::Empty); break output; - }, + } FlattenProj::Empty => panic!("Flatten polled after completion"), } }) @@ -60,8 +62,9 @@ impl Future for Flatten } impl FusedStream for Flatten - where Fut: Future, - Fut::Output: Stream, +where + Fut: Future, + Fut::Output: Stream, { fn is_terminated(&self) -> bool { match self { @@ -72,32 +75,32 @@ impl FusedStream for Flatten } impl Stream for Flatten - where Fut: Future, - Fut::Output: Stream, +where + Fut: Future, + Fut::Output: Stream, { type Item = ::Item; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Poll::Ready(loop { match self.as_mut().project() { - FlattenProj::First { f } => { + FlattenProj::First { f } => { let f = ready!(f.poll(cx)); self.set(Self::Second { f }); - }, + } FlattenProj::Second { f } => { let output = ready!(f.poll_next(cx)); if output.is_none() { self.set(Self::Empty); } break output; - }, + } FlattenProj::Empty => break None, } }) } } - #[cfg(feature = "sink")] impl Sink for Flatten where @@ -106,19 +109,16 @@ where { type Error = >::Error; - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Poll::Ready(loop { match self.as_mut().project() { FlattenProj::First { f } => { let f = ready!(f.poll(cx)); self.set(Self::Second { f }); - }, + } FlattenProj::Second { f } => { break ready!(f.poll_ready(cx)); - }, + } FlattenProj::Empty => panic!("poll_ready called after eof"), } }) @@ -140,10 +140,7 @@ where } } - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let res = match self.as_mut().project() { FlattenProj::Second { f } => f.poll_close(cx), _ => Poll::Ready(Ok(())), diff --git a/futures-util/src/future/future/fuse.rs b/futures-util/src/future/future/fuse.rs index f4284ba373..2257906726 100644 --- a/futures-util/src/future/future/fuse.rs +++ b/futures-util/src/future/future/fuse.rs @@ -1,6 +1,5 @@ use core::pin::Pin; -use futures_core::future::{Future, FusedFuture}; -use futures_core::ready; +use futures_core::future::{FusedFuture, Future}; use futures_core::task::{Context, Poll}; use pin_project_lite::pin_project; @@ -81,13 +80,12 @@ impl Future for Fuse { type Output = Fut::Output; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready(match self.as_mut().project().inner.as_pin_mut() { - Some(fut) => { - let output = ready!(fut.poll(cx)); + match self.as_mut().project().inner.as_pin_mut() { + Some(fut) => fut.poll(cx).map(|output| { self.project().inner.set(None); output - }, - None => return Poll::Pending, - }) + }), + None => Poll::Pending, + } } } diff --git a/futures-util/src/future/future/map.rs b/futures-util/src/future/future/map.rs index 7471aba000..6991a35022 100644 --- a/futures-util/src/future/future/map.rs +++ b/futures-util/src/future/future/map.rs @@ -53,7 +53,7 @@ where match self.as_mut().project() { MapProj::Incomplete { future, .. } => { let output = ready!(future.poll(cx)); - match self.project_replace(Map::Complete) { + match self.project_replace(Self::Complete) { MapProjReplace::Incomplete { f, .. } => Poll::Ready(f.call_once(output)), MapProjReplace::Complete => unreachable!(), } diff --git a/futures-util/src/future/future/mod.rs b/futures-util/src/future/future/mod.rs index c11d108207..955af3776a 100644 --- a/futures-util/src/future/future/mod.rs +++ b/futures-util/src/future/future/mod.rs @@ -463,10 +463,6 @@ pub trait FutureExt: Future { /// ``` /// /// ``` - /// // Note, unlike most examples this is written in the context of a - /// // synchronous function to better illustrate the cross-thread aspect of - /// // the `shared` combinator. - /// /// # futures::executor::block_on(async { /// use futures::future::FutureExt; /// use futures::executor::block_on; diff --git a/futures-util/src/future/future/remote_handle.rs b/futures-util/src/future/future/remote_handle.rs index 0d33ea5189..6c4b7698ae 100644 --- a/futures-util/src/future/future/remote_handle.rs +++ b/futures-util/src/future/future/remote_handle.rs @@ -1,23 +1,24 @@ use { crate::future::{CatchUnwind, FutureExt}, - futures_channel::oneshot::{self, Sender, Receiver}, + futures_channel::oneshot::{self, Receiver, Sender}, futures_core::{ future::Future, - task::{Context, Poll}, ready, + task::{Context, Poll}, }, + pin_project_lite::pin_project, std::{ any::Any, + boxed::Box, fmt, panic::{self, AssertUnwindSafe}, pin::Pin, sync::{ - Arc, atomic::{AtomicBool, Ordering}, + Arc, }, thread, }, - pin_project_lite::pin_project, }; /// The handle to a remote future returned by @@ -36,7 +37,7 @@ use { /// must be careful with regard to unwind safety because the thread in which the future /// is polled will keep running after the panic and the thread running the [RemoteHandle] /// will unwind. -#[must_use = "futures do nothing unless you `.await` or poll them"] +#[must_use = "dropping a remote handle cancels the underlying future"] #[derive(Debug)] #[cfg_attr(docsrs, doc(cfg(feature = "channel")))] pub struct RemoteHandle { @@ -85,9 +86,7 @@ pin_project! { impl fmt::Debug for Remote { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("Remote") - .field(&self.future) - .finish() + f.debug_tuple("Remote").field(&self.future).finish() } } diff --git a/futures-util/src/future/future/shared.rs b/futures-util/src/future/future/shared.rs index 74311a02c0..158fa70761 100644 --- a/futures-util/src/future/future/shared.rs +++ b/futures-util/src/future/future/shared.rs @@ -4,7 +4,9 @@ use futures_core::task::{Context, Poll, Waker}; use slab::Slab; use std::cell::UnsafeCell; use std::fmt; +use std::hash::Hasher; use std::pin::Pin; +use std::ptr; use std::sync::atomic::AtomicUsize; use std::sync::atomic::Ordering::{Acquire, SeqCst}; use std::sync::{Arc, Mutex, Weak}; @@ -29,9 +31,11 @@ struct Notifier { /// A weak reference to a [`Shared`] that can be upgraded much like an `Arc`. pub struct WeakShared(Weak>); -// The future itself is polled behind the `Arc`, so it won't be moved -// when `Shared` is moved. -impl Unpin for Shared {} +impl Clone for WeakShared { + fn clone(&self) -> Self { + Self(self.0.clone()) + } +} impl fmt::Debug for Shared { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { @@ -78,7 +82,7 @@ const POLLING: usize = 1; const COMPLETE: usize = 2; const POISONED: usize = 3; -const NULL_WAKER_KEY: usize = usize::max_value(); +const NULL_WAKER_KEY: usize = usize::MAX; impl Shared { pub(super) fn new(future: Fut) -> Self { @@ -90,17 +94,13 @@ impl Shared { }), }; - Self { - inner: Some(Arc::new(inner)), - waker_key: NULL_WAKER_KEY, - } + Self { inner: Some(Arc::new(inner)), waker_key: NULL_WAKER_KEY } } } impl Shared where Fut: Future, - Fut::Output: Clone, { /// Returns [`Some`] containing a reference to this [`Shared`]'s output if /// it has already been computed by a clone or [`None`] if it hasn't been @@ -136,6 +136,7 @@ where /// This method by itself is safe, but using it correctly requires extra care. Another thread /// can change the strong count at any time, including potentially between calling this method /// and acting on the result. + #[allow(clippy::unnecessary_safety_doc)] pub fn strong_count(&self) -> Option { self.inner.as_ref().map(|arc| Arc::strong_count(arc)) } @@ -149,24 +150,60 @@ where /// This method by itself is safe, but using it correctly requires extra care. Another thread /// can change the weak count at any time, including potentially between calling this method /// and acting on the result. + #[allow(clippy::unnecessary_safety_doc)] pub fn weak_count(&self) -> Option { self.inner.as_ref().map(|arc| Arc::weak_count(arc)) } + + /// Hashes the internal state of this `Shared` in a way that's compatible with `ptr_eq`. + pub fn ptr_hash(&self, state: &mut H) { + match self.inner.as_ref() { + Some(arc) => { + state.write_u8(1); + ptr::hash(Arc::as_ptr(arc), state); + } + None => { + state.write_u8(0); + } + } + } + + /// Returns `true` if the two `Shared`s point to the same future (in a vein similar to + /// `Arc::ptr_eq`). + /// + /// Returns `false` if either `Shared` has terminated. + pub fn ptr_eq(&self, rhs: &Self) -> bool { + let lhs = match self.inner.as_ref() { + Some(lhs) => lhs, + None => return false, + }; + let rhs = match rhs.inner.as_ref() { + Some(rhs) => rhs, + None => return false, + }; + Arc::ptr_eq(lhs, rhs) + } } impl Inner where Fut: Future, - Fut::Output: Clone, { /// Safety: callers must first ensure that `self.inner.state` /// is `COMPLETE` unsafe fn output(&self) -> &Fut::Output { - match &*self.future_or_output.get() { - FutureOrOutput::Output(ref item) => item, + match unsafe { &*self.future_or_output.get() } { + FutureOrOutput::Output(item) => item, FutureOrOutput::Future(_) => unreachable!(), } } +} + +impl Inner +where + Fut: Future, + Fut::Output: Clone, +{ /// Registers the current task to receive a wakeup when we are awoken. fn record_waker(&self, waker_key: &mut usize, cx: &mut Context<'_>) { let mut wakers_guard = self.notifier.wakers.lock().unwrap(); @@ -198,7 +235,7 @@ where FutureOrOutput::Output(item) => item, FutureOrOutput::Future(_) => unreachable!(), }, - Err(inner) => inner.output().clone(), + Err(inner) => unsafe { inner.output().clone() }, } } } @@ -223,10 +260,7 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = &mut *self; - let inner = this - .inner - .take() - .expect("Shared future polled again after completion"); + let inner = this.inner.take().expect("Shared future polled again after completion"); // Fast path for when the wrapped future has already completed if inner.notifier.state.load(Acquire) == COMPLETE { @@ -262,19 +296,20 @@ where let waker = waker_ref(&inner.notifier); let mut cx = Context::from_waker(&waker); - struct Reset<'a>(&'a AtomicUsize); + struct Reset<'a> { + state: &'a AtomicUsize, + did_not_panic: bool, + } impl Drop for Reset<'_> { fn drop(&mut self) { - use std::thread; - - if thread::panicking() { - self.0.store(POISONED, SeqCst); + if !self.did_not_panic { + self.state.store(POISONED, SeqCst); } } } - let _reset = Reset(&inner.notifier.state); + let mut reset = Reset { state: &inner.notifier.state, did_not_panic: false }; let output = { let future = unsafe { @@ -284,16 +319,15 @@ where } }; - match future.poll(&mut cx) { + let poll_result = future.poll(&mut cx); + reset.did_not_panic = true; + + match poll_result { Poll::Pending => { - if inner - .notifier - .state - .compare_exchange(POLLING, IDLE, SeqCst, SeqCst) - .is_ok() + if inner.notifier.state.compare_exchange(POLLING, IDLE, SeqCst, SeqCst).is_ok() { // Success - drop(_reset); + drop(reset); this.inner = Some(inner); return Poll::Pending; } else { @@ -317,7 +351,7 @@ where waker.wake(); } - drop(_reset); // Make borrow checker happy + drop(reset); // Make borrow checker happy drop(wakers_guard); // Safety: We're in the COMPLETE state @@ -330,10 +364,7 @@ where Fut: Future, { fn clone(&self) -> Self { - Self { - inner: self.inner.clone(), - waker_key: NULL_WAKER_KEY, - } + Self { inner: self.inner.clone(), waker_key: NULL_WAKER_KEY } } } @@ -367,16 +398,12 @@ impl ArcWake for Notifier { } } -impl WeakShared -{ +impl WeakShared { /// Attempts to upgrade this [`WeakShared`] into a [`Shared`]. /// /// Returns [`None`] if all clones of the [`Shared`] have been dropped or polled /// to completion. pub fn upgrade(&self) -> Option> { - Some(Shared { - inner: Some(self.0.upgrade()?), - waker_key: NULL_WAKER_KEY, - }) + Some(Shared { inner: Some(self.0.upgrade()?), waker_key: NULL_WAKER_KEY }) } } diff --git a/futures-util/src/future/join.rs b/futures-util/src/future/join.rs index a8183433df..740ffbc988 100644 --- a/futures-util/src/future/join.rs +++ b/futures-util/src/future/join.rs @@ -213,14 +213,5 @@ where Fut5: Future, { let f = Join5::new(future1, future2, future3, future4, future5); - assert_future::< - ( - Fut1::Output, - Fut2::Output, - Fut3::Output, - Fut4::Output, - Fut5::Output, - ), - _, - >(f) + assert_future::<(Fut1::Output, Fut2::Output, Fut3::Output, Fut4::Output, Fut5::Output), _>(f) } diff --git a/futures-util/src/future/join_all.rs b/futures-util/src/future/join_all.rs index 7ccf869042..79eee8dffc 100644 --- a/futures-util/src/future/join_all.rs +++ b/futures-util/src/future/join_all.rs @@ -1,33 +1,50 @@ //! Definition of the `JoinAll` combinator, waiting for all of a list of futures //! to finish. +use alloc::boxed::Box; +use alloc::vec::Vec; use core::fmt; use core::future::Future; use core::iter::FromIterator; use core::mem; use core::pin::Pin; use core::task::{Context, Poll}; -use alloc::boxed::Box; -use alloc::vec::Vec; -use super::{MaybeDone, assert_future}; +use super::{assert_future, MaybeDone}; + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +use crate::stream::{Collect, FuturesOrdered, StreamExt}; -fn iter_pin_mut(slice: Pin<&mut [T]>) -> impl Iterator> { +pub(crate) fn iter_pin_mut(slice: Pin<&mut [T]>) -> impl Iterator> { // Safety: `std` _could_ make this unsound if it were to decide Pin's // invariants aren't required to transmit through slices. Otherwise this has // the same safety as a normal field pin projection. - unsafe { slice.get_unchecked_mut() } - .iter_mut() - .map(|t| unsafe { Pin::new_unchecked(t) }) + unsafe { slice.get_unchecked_mut() }.iter_mut().map(|t| unsafe { Pin::new_unchecked(t) }) } -/// Future for the [`join_all`] function. #[must_use = "futures do nothing unless you `.await` or poll them"] +/// Future for the [`join_all`] function. pub struct JoinAll where F: Future, { - elems: Pin]>>, + kind: JoinAllKind, +} + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +pub(crate) const SMALL: usize = 30; + +enum JoinAllKind +where + F: Future, +{ + Small { + elems: Pin]>>, + }, + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + Big { + fut: Collect, Vec>, + }, } impl fmt::Debug for JoinAll @@ -36,9 +53,13 @@ where F::Output: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("JoinAll") - .field("elems", &self.elems) - .finish() + match self.kind { + JoinAllKind::Small { ref elems } => { + f.debug_struct("JoinAll").field("elems", elems).finish() + } + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + JoinAllKind::Big { ref fut, .. } => fmt::Debug::fmt(fut, f), + } } } @@ -54,10 +75,9 @@ where /// /// # See Also /// -/// This is purposefully a very simple API for basic use-cases. In a lot of -/// cases you will want to use the more powerful -/// [`FuturesOrdered`][crate::stream::FuturesOrdered] APIs, or, if order does -/// not matter, [`FuturesUnordered`][crate::stream::FuturesUnordered]. +/// `join_all` will switch to the more powerful [`FuturesOrdered`] for performance +/// reasons if the number of futures is large. You may want to look into using it or +/// its counterpart [`FuturesUnordered`][crate::stream::FuturesUnordered] directly. /// /// Some examples for additional functionality provided by these are: /// @@ -79,13 +99,33 @@ where /// assert_eq!(join_all(futures).await, [1, 2, 3]); /// # }); /// ``` -pub fn join_all(i: I) -> JoinAll +pub fn join_all(iter: I) -> JoinAll where I: IntoIterator, I::Item: Future, { - let elems: Box<[_]> = i.into_iter().map(MaybeDone::Future).collect(); - assert_future::::Output>, _>(JoinAll { elems: elems.into() }) + let iter = iter.into_iter(); + + #[cfg(target_os = "none")] + #[cfg_attr(target_os = "none", cfg(not(target_has_atomic = "ptr")))] + { + let kind = + JoinAllKind::Small { elems: iter.map(MaybeDone::Future).collect::>().into() }; + + assert_future::::Output>, _>(JoinAll { kind }) + } + + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + { + let kind = match iter.size_hint().1 { + Some(max) if max <= SMALL => JoinAllKind::Small { + elems: iter.map(MaybeDone::Future).collect::>().into(), + }, + _ => JoinAllKind::Big { fut: iter.collect::>().collect() }, + }; + + assert_future::::Output>, _>(JoinAll { kind }) + } } impl Future for JoinAll @@ -95,22 +135,27 @@ where type Output = Vec; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut all_done = true; + match &mut self.kind { + JoinAllKind::Small { elems } => { + let mut all_done = true; - for elem in iter_pin_mut(self.elems.as_mut()) { - if elem.poll(cx).is_pending() { - all_done = false; - } - } + for elem in iter_pin_mut(elems.as_mut()) { + if elem.poll(cx).is_pending() { + all_done = false; + } + } - if all_done { - let mut elems = mem::replace(&mut self.elems, Box::pin([])); - let result = iter_pin_mut(elems.as_mut()) - .map(|e| e.take_output().unwrap()) - .collect(); - Poll::Ready(result) - } else { - Poll::Pending + if all_done { + let mut elems = mem::replace(elems, Box::pin([])); + let result = + iter_pin_mut(elems.as_mut()).map(|e| e.take_output().unwrap()).collect(); + Poll::Ready(result) + } else { + Poll::Pending + } + } + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + JoinAllKind::Big { fut } => Pin::new(fut).poll(cx), } } } diff --git a/futures-util/src/future/lazy.rs b/futures-util/src/future/lazy.rs index 42812d3893..e9a8cf2fa9 100644 --- a/futures-util/src/future/lazy.rs +++ b/futures-util/src/future/lazy.rs @@ -7,7 +7,7 @@ use futures_core::task::{Context, Poll}; #[derive(Debug)] #[must_use = "futures do nothing unless you `.await` or poll them"] pub struct Lazy { - f: Option + f: Option, } // safe because we never generate `Pin<&mut F>` @@ -33,19 +33,24 @@ impl Unpin for Lazy {} /// # }); /// ``` pub fn lazy(f: F) -> Lazy - where F: FnOnce(&mut Context<'_>) -> R, +where + F: FnOnce(&mut Context<'_>) -> R, { assert_future::(Lazy { f: Some(f) }) } impl FusedFuture for Lazy - where F: FnOnce(&mut Context<'_>) -> R, +where + F: FnOnce(&mut Context<'_>) -> R, { - fn is_terminated(&self) -> bool { self.f.is_none() } + fn is_terminated(&self) -> bool { + self.f.is_none() + } } impl Future for Lazy - where F: FnOnce(&mut Context<'_>) -> R, +where + F: FnOnce(&mut Context<'_>) -> R, { type Output = R; diff --git a/futures-util/src/future/maybe_done.rs b/futures-util/src/future/maybe_done.rs index 26e6c27588..1e21e66f5e 100644 --- a/futures-util/src/future/maybe_done.rs +++ b/futures-util/src/future/maybe_done.rs @@ -53,7 +53,7 @@ impl MaybeDone { pub fn output_mut(self: Pin<&mut Self>) -> Option<&mut Fut::Output> { unsafe { match self.get_unchecked_mut() { - MaybeDone::Done(res) => Some(res), + Self::Done(res) => Some(res), _ => None, } } @@ -69,7 +69,7 @@ impl MaybeDone { } unsafe { match mem::replace(self.get_unchecked_mut(), Self::Gone) { - MaybeDone::Done(output) => Some(output), + Self::Done(output) => Some(output), _ => unreachable!(), } } @@ -91,12 +91,12 @@ impl Future for MaybeDone { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { unsafe { match self.as_mut().get_unchecked_mut() { - MaybeDone::Future(f) => { + Self::Future(f) => { let res = ready!(Pin::new_unchecked(f).poll(cx)); self.set(Self::Done(res)); } - MaybeDone::Done(_) => {} - MaybeDone::Gone => panic!("MaybeDone polled after value taken"), + Self::Done(_) => {} + Self::Gone => panic!("MaybeDone polled after value taken"), } } Poll::Ready(()) diff --git a/futures-util/src/future/mod.rs b/futures-util/src/future/mod.rs index 84e457c2f5..873d2b2d10 100644 --- a/futures-util/src/future/mod.rs +++ b/futures-util/src/future/mod.rs @@ -21,7 +21,7 @@ pub use futures_task::{FutureObj, LocalFutureObj, UnsafeFutureObj}; #[allow(clippy::module_inception)] mod future; pub use self::future::{ - Flatten, Fuse, FutureExt, Inspect, IntoStream, Map, NeverError, Then, UnitError, MapInto, + Flatten, Fuse, FutureExt, Inspect, IntoStream, Map, MapInto, NeverError, Then, UnitError, }; #[deprecated(note = "This is now an alias for [Flatten](Flatten)")] @@ -40,8 +40,8 @@ pub use self::future::{Shared, WeakShared}; mod try_future; pub use self::try_future::{ - AndThen, ErrInto, OkInto, InspectErr, InspectOk, IntoFuture, MapErr, MapOk, OrElse, TryFlattenStream, - TryFutureExt, UnwrapOrElse, MapOkOrElse, TryFlatten, + AndThen, ErrInto, InspectErr, InspectOk, IntoFuture, MapErr, MapOk, MapOkOrElse, OkInto, + OrElse, TryFlatten, TryFlattenStream, TryFutureExt, UnwrapOrElse, }; #[cfg(feature = "sink")] @@ -68,9 +68,15 @@ pub use self::option::OptionFuture; mod poll_fn; pub use self::poll_fn::{poll_fn, PollFn}; +mod poll_immediate; +pub use self::poll_immediate::{poll_immediate, PollImmediate}; + mod ready; pub use self::ready::{err, ok, ready, Ready}; +mod always_ready; +pub use self::always_ready::{always_ready, AlwaysReady}; + mod join; pub use self::join::{join, join3, join4, join5, Join, Join3, Join4, Join5}; @@ -108,12 +114,15 @@ pub use self::select_ok::{select_ok, SelectOk}; mod either; pub use self::either::Either; -cfg_target_has_atomic! { - #[cfg(feature = "alloc")] - mod abortable; - #[cfg(feature = "alloc")] - pub use self::abortable::{abortable, Abortable, AbortHandle, AbortRegistration, Aborted}; -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod abortable; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use crate::abortable::{AbortHandle, AbortRegistration, Abortable, Aborted}; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use abortable::abortable; // Just a helper function to ensure the futures we're returning all have the // right implementations. diff --git a/futures-util/src/future/option.rs b/futures-util/src/future/option.rs index 85939d6b1a..0bc377758a 100644 --- a/futures-util/src/future/option.rs +++ b/futures-util/src/future/option.rs @@ -1,7 +1,7 @@ //! Definition of the `Option` (optional step) combinator use core::pin::Pin; -use futures_core::future::{Future, FusedFuture}; +use futures_core::future::{FusedFuture, Future}; use futures_core::task::{Context, Poll}; use pin_project_lite::pin_project; @@ -31,13 +31,16 @@ pin_project! { } } +impl Default for OptionFuture { + fn default() -> Self { + Self { inner: None } + } +} + impl Future for OptionFuture { type Output = Option; - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project().inner.as_pin_mut() { Some(x) => x.poll(cx).map(Some), None => Poll::Ready(None), diff --git a/futures-util/src/future/pending.rs b/futures-util/src/future/pending.rs index 4311b9a228..b8e28686e1 100644 --- a/futures-util/src/future/pending.rs +++ b/futures-util/src/future/pending.rs @@ -33,10 +33,9 @@ impl FusedFuture for Pending { /// unreachable!(); /// # }); /// ``` +#[cfg_attr(docsrs, doc(alias = "never"))] pub fn pending() -> Pending { - assert_future::(Pending { - _data: marker::PhantomData, - }) + assert_future::(Pending { _data: marker::PhantomData }) } impl Future for Pending { @@ -47,8 +46,7 @@ impl Future for Pending { } } -impl Unpin for Pending { -} +impl Unpin for Pending {} impl Clone for Pending { fn clone(&self) -> Self { diff --git a/futures-util/src/future/poll_fn.rs b/futures-util/src/future/poll_fn.rs index 6ac1ab818e..19311570b5 100644 --- a/futures-util/src/future/poll_fn.rs +++ b/futures-util/src/future/poll_fn.rs @@ -35,7 +35,7 @@ impl Unpin for PollFn {} /// ``` pub fn poll_fn(f: F) -> PollFn where - F: FnMut(&mut Context<'_>) -> Poll + F: FnMut(&mut Context<'_>) -> Poll, { assert_future::(PollFn { f }) } @@ -47,7 +47,8 @@ impl fmt::Debug for PollFn { } impl Future for PollFn - where F: FnMut(&mut Context<'_>) -> Poll, +where + F: FnMut(&mut Context<'_>) -> Poll, { type Output = T; diff --git a/futures-util/src/future/poll_immediate.rs b/futures-util/src/future/poll_immediate.rs new file mode 100644 index 0000000000..5ae555c73e --- /dev/null +++ b/futures-util/src/future/poll_immediate.rs @@ -0,0 +1,126 @@ +use super::assert_future; +use core::pin::Pin; +use futures_core::task::{Context, Poll}; +use futures_core::{FusedFuture, Future, Stream}; +use pin_project_lite::pin_project; + +pin_project! { + /// Future for the [`poll_immediate`](poll_immediate()) function. + /// + /// It will never return [Poll::Pending](core::task::Poll::Pending) + #[derive(Debug, Clone)] + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct PollImmediate { + #[pin] + future: Option + } +} + +impl Future for PollImmediate +where + F: Future, +{ + type Output = Option; + + #[inline] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + let inner = + this.future.as_mut().as_pin_mut().expect("PollImmediate polled after completion"); + match inner.poll(cx) { + Poll::Ready(t) => { + this.future.set(None); + Poll::Ready(Some(t)) + } + Poll::Pending => Poll::Ready(None), + } + } +} + +impl FusedFuture for PollImmediate { + fn is_terminated(&self) -> bool { + self.future.is_none() + } +} + +/// A [Stream](crate::stream::Stream) implementation that can be polled repeatedly until the future is done. +/// The stream will never return [Poll::Pending](core::task::Poll::Pending) +/// so polling it in a tight loop is worse than using a blocking synchronous function. +/// ``` +/// # futures::executor::block_on(async { +/// use futures::task::Poll; +/// use futures::{StreamExt, future, pin_mut}; +/// use future::FusedFuture; +/// +/// let f = async { 1_u32 }; +/// pin_mut!(f); +/// let mut r = future::poll_immediate(f); +/// assert_eq!(r.next().await, Some(Poll::Ready(1))); +/// +/// let f = async {futures::pending!(); 42_u8}; +/// pin_mut!(f); +/// let mut p = future::poll_immediate(f); +/// assert_eq!(p.next().await, Some(Poll::Pending)); +/// assert!(!p.is_terminated()); +/// assert_eq!(p.next().await, Some(Poll::Ready(42))); +/// assert!(p.is_terminated()); +/// assert_eq!(p.next().await, None); +/// # }); +/// ``` +impl Stream for PollImmediate +where + F: Future, +{ + type Item = Poll; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + match this.future.as_mut().as_pin_mut() { + // inner is gone, so we can signal that the stream is closed. + None => Poll::Ready(None), + Some(fut) => Poll::Ready(Some(fut.poll(cx).map(|t| { + this.future.set(None); + t + }))), + } + } +} + +/// Creates a future that is immediately ready with an Option of a value. +/// Specifically this means that [poll](core::future::Future::poll()) always returns [Poll::Ready](core::task::Poll::Ready). +/// +/// # Caution +/// +/// When consuming the future by this function, note the following: +/// +/// - This function does not guarantee that the future will run to completion, so it is generally incompatible with passing the non-cancellation-safe future by value. +/// - Even if the future is cancellation-safe, creating and dropping new futures frequently may lead to performance problems. +/// +/// # Examples +/// +/// ``` +/// # futures::executor::block_on(async { +/// use futures::future; +/// +/// let r = future::poll_immediate(async { 1_u32 }); +/// assert_eq!(r.await, Some(1)); +/// +/// let p = future::poll_immediate(future::pending::()); +/// assert_eq!(p.await, None); +/// # }); +/// ``` +/// +/// ### Reusing a future +/// +/// ``` +/// # futures::executor::block_on(async { +/// use futures::{future, pin_mut}; +/// let f = async {futures::pending!(); 42_u8}; +/// pin_mut!(f); +/// assert_eq!(None, future::poll_immediate(&mut f).await); +/// assert_eq!(42, f.await); +/// # }); +/// ``` +pub fn poll_immediate(f: F) -> PollImmediate { + assert_future::, PollImmediate>(PollImmediate { future: Some(f) }) +} diff --git a/futures-util/src/future/select.rs b/futures-util/src/future/select.rs index 043ed178e7..7e33d195f7 100644 --- a/futures-util/src/future/select.rs +++ b/futures-util/src/future/select.rs @@ -1,8 +1,8 @@ use super::assert_future; +use crate::future::{Either, FutureExt}; use core::pin::Pin; -use futures_core::future::{Future, FusedFuture}; +use futures_core::future::{FusedFuture, Future}; use futures_core::task::{Context, Poll}; -use crate::future::{Either, FutureExt}; /// Future for the [`select()`] function. #[must_use = "futures do nothing unless you `.await` or poll them"] @@ -37,13 +37,13 @@ impl Unpin for Select {} /// future::Either, /// future::self, /// }; -/// +/// /// // These two futures have different types even though their outputs have the same type. /// let future1 = async { /// future::pending::<()>().await; // will never finish /// 1 /// }; -/// let future2 = async { +/// let future2 = async { /// future::ready(2).await /// }; /// @@ -82,9 +82,13 @@ impl Unpin for Select {} /// } /// ``` pub fn select(future1: A, future2: B) -> Select - where A: Future + Unpin, B: Future + Unpin +where + A: Future + Unpin, + B: Future + Unpin, { - assert_future::, _>(Select { inner: Some((future1, future2)) }) + assert_future::, _>(Select { + inner: Some((future1, future2)), + }) } impl Future for Select @@ -95,17 +99,27 @@ where type Output = Either<(A::Output, B), (B::Output, A)>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let (mut a, mut b) = self.inner.take().expect("cannot poll Select twice"); - match a.poll_unpin(cx) { - Poll::Ready(x) => Poll::Ready(Either::Left((x, b))), - Poll::Pending => match b.poll_unpin(cx) { - Poll::Ready(x) => Poll::Ready(Either::Right((x, a))), - Poll::Pending => { - self.inner = Some((a, b)); - Poll::Pending - } + /// When compiled with `-C opt-level=z`, this function will help the compiler eliminate the `None` branch, where + /// `Option::unwrap` does not. + #[inline(always)] + fn unwrap_option(value: Option) -> T { + match value { + None => unreachable!(), + Some(value) => value, } } + + let (a, b) = self.inner.as_mut().expect("cannot poll Select twice"); + + if let Poll::Ready(val) = a.poll_unpin(cx) { + return Poll::Ready(Either::Left((val, unwrap_option(self.inner.take()).1))); + } + + if let Poll::Ready(val) = b.poll_unpin(cx) { + return Poll::Ready(Either::Right((val, unwrap_option(self.inner.take()).0))); + } + + Poll::Pending } } diff --git a/futures-util/src/future/select_all.rs b/futures-util/src/future/select_all.rs index 0db90a750e..0a51d0da6c 100644 --- a/futures-util/src/future/select_all.rs +++ b/futures-util/src/future/select_all.rs @@ -1,9 +1,9 @@ use super::assert_future; use crate::future::FutureExt; +use alloc::vec::Vec; use core::iter::FromIterator; use core::mem; use core::pin::Pin; -use alloc::vec::Vec; use futures_core::future::Future; use futures_core::task::{Context, Poll}; @@ -32,30 +32,35 @@ impl Unpin for SelectAll {} /// /// This function will panic if the iterator specified contains no items. pub fn select_all(iter: I) -> SelectAll - where I: IntoIterator, - I::Item: Future + Unpin, +where + I: IntoIterator, + I::Item: Future + Unpin, { - let ret = SelectAll { - inner: iter.into_iter().collect() - }; + let ret = SelectAll { inner: iter.into_iter().collect() }; assert!(!ret.inner.is_empty()); assert_future::<(::Output, usize, Vec), _>(ret) } +impl SelectAll { + /// Consumes this combinator, returning the underlying futures. + pub fn into_inner(self) -> Vec { + self.inner + } +} + impl Future for SelectAll { type Output = (Fut::Output, usize, Vec); fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let item = self.inner.iter_mut().enumerate().find_map(|(i, f)| { - match f.poll_unpin(cx) { - Poll::Pending => None, - Poll::Ready(e) => Some((i, e)), - } + let item = self.inner.iter_mut().enumerate().find_map(|(i, f)| match f.poll_unpin(cx) { + Poll::Pending => None, + Poll::Ready(e) => Some((i, e)), }); match item { Some((idx, res)) => { + #[allow(clippy::let_underscore_future)] let _ = self.inner.swap_remove(idx); - let rest = mem::replace(&mut self.inner, Vec::new()); + let rest = mem::take(&mut self.inner); Poll::Ready((res, idx, rest)) } None => Poll::Pending, diff --git a/futures-util/src/future/select_ok.rs b/futures-util/src/future/select_ok.rs index 52d393c28e..5d5579930b 100644 --- a/futures-util/src/future/select_ok.rs +++ b/futures-util/src/future/select_ok.rs @@ -1,9 +1,9 @@ use super::assert_future; use crate::future::TryFutureExt; +use alloc::vec::Vec; use core::iter::FromIterator; use core::mem; use core::pin::Pin; -use alloc::vec::Vec; use futures_core::future::{Future, TryFuture}; use futures_core::task::{Context, Poll}; @@ -30,14 +30,16 @@ impl Unpin for SelectOk {} /// /// This function will panic if the iterator specified contains no items. pub fn select_ok(iter: I) -> SelectOk - where I: IntoIterator, - I::Item: TryFuture + Unpin, +where + I: IntoIterator, + I::Item: TryFuture + Unpin, { - let ret = SelectOk { - inner: iter.into_iter().collect() - }; + let ret = SelectOk { inner: iter.into_iter().collect() }; assert!(!ret.inner.is_empty(), "iterator provided to select_ok was empty"); - assert_future::::Ok, Vec), ::Error>, _>(ret) + assert_future::< + Result<(::Ok, Vec), ::Error>, + _, + >(ret) } impl Future for SelectOk { @@ -46,31 +48,30 @@ impl Future for SelectOk { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // loop until we've either exhausted all errors, a success was hit, or nothing is ready loop { - let item = self.inner.iter_mut().enumerate().find_map(|(i, f)| { - match f.try_poll_unpin(cx) { + let item = + self.inner.iter_mut().enumerate().find_map(|(i, f)| match f.try_poll_unpin(cx) { Poll::Pending => None, Poll::Ready(e) => Some((i, e)), - } - }); + }); match item { Some((idx, res)) => { // always remove Ok or Err, if it's not the last Err continue looping drop(self.inner.remove(idx)); match res { Ok(e) => { - let rest = mem::replace(&mut self.inner, Vec::new()); - return Poll::Ready(Ok((e, rest))) + let rest = mem::take(&mut self.inner); + return Poll::Ready(Ok((e, rest))); } Err(e) => { if self.inner.is_empty() { - return Poll::Ready(Err(e)) + return Poll::Ready(Err(e)); } } } } None => { // based on the filter above, nothing is ready, return - return Poll::Pending + return Poll::Pending; } } } diff --git a/futures-util/src/future/try_future/into_future.rs b/futures-util/src/future/try_future/into_future.rs index e88d603c0f..9f093d0e2e 100644 --- a/futures-util/src/future/try_future/into_future.rs +++ b/futures-util/src/future/try_future/into_future.rs @@ -21,17 +21,16 @@ impl IntoFuture { } impl FusedFuture for IntoFuture { - fn is_terminated(&self) -> bool { self.future.is_terminated() } + fn is_terminated(&self) -> bool { + self.future.is_terminated() + } } impl Future for IntoFuture { type Output = Result; #[inline] - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.project().future.try_poll(cx) } } diff --git a/futures-util/src/future/try_future/mod.rs b/futures-util/src/future/try_future/mod.rs index fb3bdd8a02..e5bc700714 100644 --- a/futures-util/src/future/try_future/mod.rs +++ b/futures-util/src/future/try_future/mod.rs @@ -302,6 +302,9 @@ pub trait TryFutureExt: TryFuture { /// assert_eq!(future.await, Ok(1)); /// # }); /// ``` + /// + /// [`join!`]: crate::join + /// [`select!`]: crate::select fn map_err(self, f: F) -> MapErr where F: FnOnce(Self::Error) -> E, @@ -332,6 +335,9 @@ pub trait TryFutureExt: TryFuture { /// let future_err_i32 = future_err_u8.err_into::(); /// # }); /// ``` + /// + /// [`join!`]: crate::join + /// [`select!`]: crate::select fn err_into(self) -> ErrInto where Self: Sized, diff --git a/futures-util/src/future/try_future/try_flatten.rs b/futures-util/src/future/try_future/try_flatten.rs index 5241b2750d..1ce4559ac2 100644 --- a/futures-util/src/future/try_future/try_flatten.rs +++ b/futures-util/src/future/try_future/try_flatten.rs @@ -2,9 +2,9 @@ use core::pin::Pin; use futures_core::future::{FusedFuture, Future, TryFuture}; use futures_core::ready; use futures_core::stream::{FusedStream, Stream, TryStream}; +use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; -use futures_core::task::{Context, Poll}; use pin_project_lite::pin_project; pin_project! { @@ -24,8 +24,9 @@ impl TryFlatten { } impl FusedFuture for TryFlatten - where Fut: TryFuture, - Fut::Ok: TryFuture, +where + Fut: TryFuture, + Fut::Ok: TryFuture, { fn is_terminated(&self) -> bool { match self { @@ -36,28 +37,27 @@ impl FusedFuture for TryFlatten } impl Future for TryFlatten - where Fut: TryFuture, - Fut::Ok: TryFuture, +where + Fut: TryFuture, + Fut::Ok: TryFuture, { type Output = Result<::Ok, Fut::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Poll::Ready(loop { match self.as_mut().project() { - TryFlattenProj::First { f } => { - match ready!(f.try_poll(cx)) { - Ok(f) => self.set(Self::Second { f }), - Err(e) => { - self.set(Self::Empty); - break Err(e); - } + TryFlattenProj::First { f } => match ready!(f.try_poll(cx)) { + Ok(f) => self.set(Self::Second { f }), + Err(e) => { + self.set(Self::Empty); + break Err(e); } }, TryFlattenProj::Second { f } => { let output = ready!(f.try_poll(cx)); self.set(Self::Empty); break output; - }, + } TryFlattenProj::Empty => panic!("TryFlatten polled after completion"), } }) @@ -65,8 +65,9 @@ impl Future for TryFlatten } impl FusedStream for TryFlatten - where Fut: TryFuture, - Fut::Ok: TryStream, +where + Fut: TryFuture, + Fut::Ok: TryStream, { fn is_terminated(&self) -> bool { match self { @@ -77,21 +78,20 @@ impl FusedStream for TryFlatten } impl Stream for TryFlatten - where Fut: TryFuture, - Fut::Ok: TryStream, +where + Fut: TryFuture, + Fut::Ok: TryStream, { type Item = Result<::Ok, Fut::Error>; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Poll::Ready(loop { match self.as_mut().project() { - TryFlattenProj::First { f } => { - match ready!(f.try_poll(cx)) { - Ok(f) => self.set(Self::Second { f }), - Err(e) => { - self.set(Self::Empty); - break Some(Err(e)); - } + TryFlattenProj::First { f } => match ready!(f.try_poll(cx)) { + Ok(f) => self.set(Self::Second { f }), + Err(e) => { + self.set(Self::Empty); + break Some(Err(e)); } }, TryFlattenProj::Second { f } => { @@ -100,40 +100,34 @@ impl Stream for TryFlatten self.set(Self::Empty); } break output; - }, + } TryFlattenProj::Empty => break None, } }) } } - #[cfg(feature = "sink")] impl Sink for TryFlatten where Fut: TryFuture, - Fut::Ok: Sink, + Fut::Ok: Sink, { type Error = Fut::Error; - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Poll::Ready(loop { match self.as_mut().project() { - TryFlattenProj::First { f } => { - match ready!(f.try_poll(cx)) { - Ok(f) => self.set(Self::Second { f }), - Err(e) => { - self.set(Self::Empty); - break Err(e); - } + TryFlattenProj::First { f } => match ready!(f.try_poll(cx)) { + Ok(f) => self.set(Self::Second { f }), + Err(e) => { + self.set(Self::Empty); + break Err(e); } }, TryFlattenProj::Second { f } => { break ready!(f.poll_ready(cx)); - }, + } TryFlattenProj::Empty => panic!("poll_ready called after eof"), } }) @@ -155,10 +149,7 @@ where } } - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let res = match self.as_mut().project() { TryFlattenProj::Second { f } => f.poll_close(cx), _ => Poll::Ready(Ok(())), diff --git a/futures-util/src/future/try_future/try_flatten_err.rs b/futures-util/src/future/try_future/try_flatten_err.rs index 2e67f1104e..39b7d9f5f6 100644 --- a/futures-util/src/future/try_future/try_flatten_err.rs +++ b/futures-util/src/future/try_future/try_flatten_err.rs @@ -21,8 +21,9 @@ impl TryFlattenErr { } impl FusedFuture for TryFlattenErr - where Fut: TryFuture, - Fut::Error: TryFuture, +where + Fut: TryFuture, + Fut::Error: TryFuture, { fn is_terminated(&self) -> bool { match self { @@ -33,28 +34,27 @@ impl FusedFuture for TryFlattenErr } impl Future for TryFlattenErr - where Fut: TryFuture, - Fut::Error: TryFuture, +where + Fut: TryFuture, + Fut::Error: TryFuture, { type Output = Result::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Poll::Ready(loop { match self.as_mut().project() { - TryFlattenErrProj::First { f } => { - match ready!(f.try_poll(cx)) { - Err(f) => self.set(Self::Second { f }), - Ok(e) => { - self.set(Self::Empty); - break Ok(e); - } + TryFlattenErrProj::First { f } => match ready!(f.try_poll(cx)) { + Err(f) => self.set(Self::Second { f }), + Ok(e) => { + self.set(Self::Empty); + break Ok(e); } }, TryFlattenErrProj::Second { f } => { let output = ready!(f.try_poll(cx)); self.set(Self::Empty); break output; - }, + } TryFlattenErrProj::Empty => panic!("TryFlattenErr polled after completion"), } }) diff --git a/futures-util/src/future/try_join_all.rs b/futures-util/src/future/try_join_all.rs index 371f753f3f..2d6a2a02cb 100644 --- a/futures-util/src/future/try_join_all.rs +++ b/futures-util/src/future/try_join_all.rs @@ -1,30 +1,25 @@ //! Definition of the `TryJoinAll` combinator, waiting for all of a list of //! futures to finish with either success or error. +use alloc::boxed::Box; +use alloc::vec::Vec; use core::fmt; use core::future::Future; use core::iter::FromIterator; use core::mem; use core::pin::Pin; use core::task::{Context, Poll}; -use alloc::boxed::Box; -use alloc::vec::Vec; -use super::{assert_future, TryFuture, TryMaybeDone}; +use super::{assert_future, join_all, IntoFuture, TryFuture, TryMaybeDone}; -fn iter_pin_mut(slice: Pin<&mut [T]>) -> impl Iterator> { - // Safety: `std` _could_ make this unsound if it were to decide Pin's - // invariants aren't required to transmit through slices. Otherwise this has - // the same safety as a normal field pin projection. - unsafe { slice.get_unchecked_mut() } - .iter_mut() - .map(|t| unsafe { Pin::new_unchecked(t) }) -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +use crate::stream::{FuturesOrdered, TryCollect, TryStreamExt}; +use crate::TryFutureExt; enum FinalState { Pending, AllDone, - Error(E) + Error(E), } /// Future for the [`try_join_all`] function. @@ -33,7 +28,20 @@ pub struct TryJoinAll where F: TryFuture, { - elems: Pin]>>, + kind: TryJoinAllKind, +} + +enum TryJoinAllKind +where + F: TryFuture, +{ + Small { + elems: Pin>]>>, + }, + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + Big { + fut: TryCollect>, Vec>, + }, } impl fmt::Debug for TryJoinAll @@ -41,11 +49,16 @@ where F: TryFuture + fmt::Debug, F::Ok: fmt::Debug, F::Error: fmt::Debug, + F::Output: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TryJoinAll") - .field("elems", &self.elems) - .finish() + match self.kind { + TryJoinAllKind::Small { ref elems } => { + f.debug_struct("TryJoinAll").field("elems", elems).finish() + } + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + TryJoinAllKind::Big { ref fut, .. } => fmt::Debug::fmt(fut, f), + } } } @@ -64,6 +77,20 @@ where /// This function is only available when the `std` or `alloc` feature of this /// library is activated, and it is activated by default. /// +/// # See Also +/// +/// `try_join_all` will switch to the more powerful [`FuturesOrdered`] for performance +/// reasons if the number of futures is large. You may want to look into using it or +/// it's counterpart [`FuturesUnordered`][crate::stream::FuturesUnordered] directly. +/// +/// Some examples for additional functionality provided by these are: +/// +/// * Adding new futures to the set even after it has been started. +/// +/// * Only polling the specific futures that have been woken. In cases where +/// you have a lot of futures this will result in much more efficient polling. +/// +/// /// # Examples /// /// ``` @@ -87,15 +114,38 @@ where /// assert_eq!(try_join_all(futures).await, Err(2)); /// # }); /// ``` -pub fn try_join_all(i: I) -> TryJoinAll +pub fn try_join_all(iter: I) -> TryJoinAll where I: IntoIterator, I::Item: TryFuture, { - let elems: Box<[_]> = i.into_iter().map(TryMaybeDone::Future).collect(); - assert_future::::Ok>, ::Error>, _>(TryJoinAll { - elems: elems.into(), - }) + let iter = iter.into_iter().map(TryFutureExt::into_future); + + #[cfg(target_os = "none")] + #[cfg_attr(target_os = "none", cfg(not(target_has_atomic = "ptr")))] + { + let kind = TryJoinAllKind::Small { + elems: iter.map(TryMaybeDone::Future).collect::>().into(), + }; + + assert_future::::Ok>, ::Error>, _>( + TryJoinAll { kind }, + ) + } + + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + { + let kind = match iter.size_hint().1 { + Some(max) if max <= join_all::SMALL => TryJoinAllKind::Small { + elems: iter.map(TryMaybeDone::Future).collect::>().into(), + }, + _ => TryJoinAllKind::Big { fut: iter.collect::>().try_collect() }, + }; + + assert_future::::Ok>, ::Error>, _>( + TryJoinAll { kind }, + ) + } } impl Future for TryJoinAll @@ -105,37 +155,46 @@ where type Output = Result, F::Error>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut state = FinalState::AllDone; - - for elem in iter_pin_mut(self.elems.as_mut()) { - match elem.try_poll(cx) { - Poll::Pending => state = FinalState::Pending, - Poll::Ready(Ok(())) => {}, - Poll::Ready(Err(e)) => { - state = FinalState::Error(e); - break; + match &mut self.kind { + TryJoinAllKind::Small { elems } => { + let mut state = FinalState::AllDone; + + for elem in join_all::iter_pin_mut(elems.as_mut()) { + match elem.try_poll(cx) { + Poll::Pending => state = FinalState::Pending, + Poll::Ready(Ok(())) => {} + Poll::Ready(Err(e)) => { + state = FinalState::Error(e); + break; + } + } } - } - } - match state { - FinalState::Pending => Poll::Pending, - FinalState::AllDone => { - let mut elems = mem::replace(&mut self.elems, Box::pin([])); - let results = iter_pin_mut(elems.as_mut()) - .map(|e| e.take_output().unwrap()) - .collect(); - Poll::Ready(Ok(results)) - }, - FinalState::Error(e) => { - let _ = mem::replace(&mut self.elems, Box::pin([])); - Poll::Ready(Err(e)) - }, + match state { + FinalState::Pending => Poll::Pending, + FinalState::AllDone => { + let mut elems = mem::replace(elems, Box::pin([])); + let results = join_all::iter_pin_mut(elems.as_mut()) + .map(|e| e.take_output().unwrap()) + .collect(); + Poll::Ready(Ok(results)) + } + FinalState::Error(e) => { + let _ = mem::replace(elems, Box::pin([])); + Poll::Ready(Err(e)) + } + } + } + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + TryJoinAllKind::Big { fut } => Pin::new(fut).poll(cx), } } } -impl FromIterator for TryJoinAll { +impl FromIterator for TryJoinAll +where + F: TryFuture, +{ fn from_iter>(iter: T) -> Self { try_join_all(iter) } diff --git a/futures-util/src/future/try_maybe_done.rs b/futures-util/src/future/try_maybe_done.rs index dfd290065d..97af6ad437 100644 --- a/futures-util/src/future/try_maybe_done.rs +++ b/futures-util/src/future/try_maybe_done.rs @@ -38,7 +38,7 @@ impl TryMaybeDone { pub fn output_mut(self: Pin<&mut Self>) -> Option<&mut Fut::Ok> { unsafe { match self.get_unchecked_mut() { - TryMaybeDone::Done(res) => Some(res), + Self::Done(res) => Some(res), _ => None, } } @@ -49,13 +49,13 @@ impl TryMaybeDone { #[inline] pub fn take_output(self: Pin<&mut Self>) -> Option { match &*self { - Self::Done(_) => {}, + Self::Done(_) => {} Self::Future(_) | Self::Gone => return None, } unsafe { match mem::replace(self.get_unchecked_mut(), Self::Gone) { - TryMaybeDone::Done(output) => Some(output), - _ => unreachable!() + Self::Done(output) => Some(output), + _ => unreachable!(), } } } @@ -76,17 +76,15 @@ impl Future for TryMaybeDone { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { unsafe { match self.as_mut().get_unchecked_mut() { - TryMaybeDone::Future(f) => { - match ready!(Pin::new_unchecked(f).try_poll(cx)) { - Ok(res) => self.set(Self::Done(res)), - Err(e) => { - self.set(Self::Gone); - return Poll::Ready(Err(e)); - } + Self::Future(f) => match ready!(Pin::new_unchecked(f).try_poll(cx)) { + Ok(res) => self.set(Self::Done(res)), + Err(e) => { + self.set(Self::Gone); + return Poll::Ready(Err(e)); } }, - TryMaybeDone::Done(_) => {}, - TryMaybeDone::Gone => panic!("TryMaybeDone polled after value taken"), + Self::Done(_) => {} + Self::Gone => panic!("TryMaybeDone polled after value taken"), } } Poll::Ready(Ok(())) diff --git a/futures-util/src/future/try_select.rs b/futures-util/src/future/try_select.rs index b26eed35f0..bc282f7db1 100644 --- a/futures-util/src/future/try_select.rs +++ b/futures-util/src/future/try_select.rs @@ -1,7 +1,7 @@ +use crate::future::{Either, TryFutureExt}; use core::pin::Pin; use futures_core::future::{Future, TryFuture}; use futures_core::task::{Context, Poll}; -use crate::future::{Either, TryFutureExt}; /// Future for the [`try_select()`] function. #[must_use = "futures do nothing unless you `.await` or poll them"] @@ -12,6 +12,9 @@ pub struct TrySelect { impl Unpin for TrySelect {} +type EitherOk = Either<(::Ok, B), (::Ok, A)>; +type EitherErr = Either<(::Error, B), (::Error, A)>; + /// Waits for either one of two differently-typed futures to complete. /// /// This function will return a new future which awaits for either one of both @@ -48,22 +51,21 @@ impl Unpin for TrySelect {} /// } /// ``` pub fn try_select(future1: A, future2: B) -> TrySelect - where A: TryFuture + Unpin, B: TryFuture + Unpin +where + A: TryFuture + Unpin, + B: TryFuture + Unpin, { - super::assert_future::, - Either<(A::Error, B), (B::Error, A)>, - >, _>(TrySelect { inner: Some((future1, future2)) }) + super::assert_future::, EitherErr>, _>(TrySelect { + inner: Some((future1, future2)), + }) } impl Future for TrySelect - where A: TryFuture, B: TryFuture +where + A: TryFuture, + B: TryFuture, { - #[allow(clippy::type_complexity)] - type Output = Result< - Either<(A::Ok, B), (B::Ok, A)>, - Either<(A::Error, B), (B::Error, A)>, - >; + type Output = Result, EitherErr>; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let (mut a, mut b) = self.inner.take().expect("cannot poll Select twice"); @@ -77,7 +79,7 @@ impl Future for TrySelect self.inner = Some((a, b)); Poll::Pending } - } + }, } } } diff --git a/futures-util/src/io/allow_std.rs b/futures-util/src/io/allow_std.rs index 9aa8eb4e71..96133cbc6f 100644 --- a/futures-util/src/io/allow_std.rs +++ b/futures-util/src/io/allow_std.rs @@ -1,9 +1,9 @@ use futures_core::task::{Context, Poll}; -#[cfg(feature = "read-initializer")] -use futures_io::Initializer; -use futures_io::{AsyncRead, AsyncWrite, AsyncSeek, AsyncBufRead, IoSlice, IoSliceMut, SeekFrom}; -use std::{fmt, io}; +use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, IoSlice, IoSliceMut, SeekFrom}; use std::pin::Pin; +use std::string::String; +use std::vec::Vec; +use std::{fmt, io}; /// A simple wrapper type which allows types which implement only /// implement `std::io::Read` or `std::io::Write` @@ -35,7 +35,7 @@ macro_rules! try_with_interrupt { } } } - } + }; } impl AllowStdIo { @@ -60,7 +60,10 @@ impl AllowStdIo { } } -impl io::Write for AllowStdIo where T: io::Write { +impl io::Write for AllowStdIo +where + T: io::Write, +{ fn write(&mut self, buf: &[u8]) -> io::Result { self.0.write(buf) } @@ -78,16 +81,23 @@ impl io::Write for AllowStdIo where T: io::Write { } } -impl AsyncWrite for AllowStdIo where T: io::Write { - fn poll_write(mut self: Pin<&mut Self>, _: &mut Context<'_>, buf: &[u8]) - -> Poll> - { +impl AsyncWrite for AllowStdIo +where + T: io::Write, +{ + fn poll_write( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { Poll::Ready(Ok(try_with_interrupt!(self.0.write(buf)))) } - fn poll_write_vectored(mut self: Pin<&mut Self>, _: &mut Context<'_>, bufs: &[IoSlice<'_>]) - -> Poll> - { + fn poll_write_vectored( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { Poll::Ready(Ok(try_with_interrupt!(self.0.write_vectored(bufs)))) } @@ -101,17 +111,16 @@ impl AsyncWrite for AllowStdIo where T: io::Write { } } -impl io::Read for AllowStdIo where T: io::Read { +impl io::Read for AllowStdIo +where + T: io::Read, +{ fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } fn read_vectored(&mut self, bufs: &mut [IoSliceMut<'_>]) -> io::Result { self.0.read_vectored(bufs) } - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - self.0.initializer() - } fn read_to_end(&mut self, buf: &mut Vec) -> io::Result { self.0.read_to_end(buf) } @@ -123,40 +132,53 @@ impl io::Read for AllowStdIo where T: io::Read { } } -impl AsyncRead for AllowStdIo where T: io::Read { - fn poll_read(mut self: Pin<&mut Self>, _: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { +impl AsyncRead for AllowStdIo +where + T: io::Read, +{ + fn poll_read( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { Poll::Ready(Ok(try_with_interrupt!(self.0.read(buf)))) } - fn poll_read_vectored(mut self: Pin<&mut Self>, _: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) - -> Poll> - { + fn poll_read_vectored( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + bufs: &mut [IoSliceMut<'_>], + ) -> Poll> { Poll::Ready(Ok(try_with_interrupt!(self.0.read_vectored(bufs)))) } - - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - self.0.initializer() - } } -impl io::Seek for AllowStdIo where T: io::Seek { +impl io::Seek for AllowStdIo +where + T: io::Seek, +{ fn seek(&mut self, pos: SeekFrom) -> io::Result { self.0.seek(pos) } } -impl AsyncSeek for AllowStdIo where T: io::Seek { - fn poll_seek(mut self: Pin<&mut Self>, _: &mut Context<'_>, pos: SeekFrom) - -> Poll> - { +impl AsyncSeek for AllowStdIo +where + T: io::Seek, +{ + fn poll_seek( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + pos: SeekFrom, + ) -> Poll> { Poll::Ready(Ok(try_with_interrupt!(self.0.seek(pos)))) } } -impl io::BufRead for AllowStdIo where T: io::BufRead { +impl io::BufRead for AllowStdIo +where + T: io::BufRead, +{ fn fill_buf(&mut self) -> io::Result<&[u8]> { self.0.fill_buf() } @@ -165,10 +187,11 @@ impl io::BufRead for AllowStdIo where T: io::BufRead { } } -impl AsyncBufRead for AllowStdIo where T: io::BufRead { - fn poll_fill_buf(mut self: Pin<&mut Self>, _: &mut Context<'_>) - -> Poll> - { +impl AsyncBufRead for AllowStdIo +where + T: io::BufRead, +{ + fn poll_fill_buf(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { let this: *mut Self = &mut *self as *mut _; Poll::Ready(Ok(try_with_interrupt!(unsafe { &mut *this }.0.fill_buf()))) } diff --git a/futures-util/src/io/buf_reader.rs b/futures-util/src/io/buf_reader.rs index 270a086cf8..9a919f7183 100644 --- a/futures-util/src/io/buf_reader.rs +++ b/futures-util/src/io/buf_reader.rs @@ -1,13 +1,14 @@ +use super::DEFAULT_BUF_SIZE; +use futures_core::future::Future; use futures_core::ready; use futures_core::task::{Context, Poll}; -#[cfg(feature = "read-initializer")] -use futures_io::Initializer; use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, IoSliceMut, SeekFrom}; use pin_project_lite::pin_project; +use std::boxed::Box; use std::io::{self, Read}; use std::pin::Pin; +use std::vec; use std::{cmp, fmt}; -use super::DEFAULT_BUF_SIZE; pin_project! { /// The `BufReader` struct adds buffering to any reader. @@ -47,19 +48,13 @@ impl BufReader { /// Creates a new `BufReader` with the specified buffer capacity. pub fn with_capacity(capacity: usize, inner: R) -> Self { - unsafe { - let mut buffer = Vec::with_capacity(capacity); - buffer.set_len(capacity); - super::initialize(&inner, &mut buffer); - Self { - inner, - buffer: buffer.into_boxed_slice(), - pos: 0, - cap: 0, - } - } + // TODO: consider using Box<[u8]>::new_uninit_slice once it stabilized + let buffer = vec![0; capacity]; + Self { inner, buffer: buffer.into_boxed_slice(), pos: 0, cap: 0 } } +} +impl BufReader { delegate_access_inner!(inner, R, ()); /// Returns a reference to the internally buffered data. @@ -78,6 +73,40 @@ impl BufReader { } } +impl BufReader { + /// Seeks relative to the current position. If the new position lies within the buffer, + /// the buffer will not be flushed, allowing for more efficient seeks. + /// This method does not return the location of the underlying reader, so the caller + /// must track this information themselves if it is required. + pub fn seek_relative(self: Pin<&mut Self>, offset: i64) -> SeeKRelative<'_, R> { + SeeKRelative { inner: self, offset, first: true } + } + + /// Attempts to seek relative to the current position. If the new position lies within the buffer, + /// the buffer will not be flushed, allowing for more efficient seeks. + /// This method does not return the location of the underlying reader, so the caller + /// must track this information themselves if it is required. + pub fn poll_seek_relative( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + offset: i64, + ) -> Poll> { + let pos = self.pos as u64; + if offset < 0 { + if let Some(new_pos) = pos.checked_sub((-offset) as u64) { + *self.project().pos = new_pos as usize; + return Poll::Ready(Ok(())); + } + } else if let Some(new_pos) = pos.checked_add(offset as u64) { + if new_pos <= self.cap as u64 { + *self.project().pos = new_pos as usize; + return Poll::Ready(Ok(())); + } + } + self.poll_seek(cx, SeekFrom::Current(offset)).map(|res| res.map(|_| ())) + } +} + impl AsyncRead for BufReader { fn poll_read( mut self: Pin<&mut Self>, @@ -114,19 +143,10 @@ impl AsyncRead for BufReader { self.consume(nread); Poll::Ready(Ok(nread)) } - - // we can't skip unconditionally because of the large buffer case in read. - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - self.inner.initializer() - } } impl AsyncBufRead for BufReader { - fn poll_fill_buf( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); // If we've reached the end of our internal buffer then we need to fetch @@ -171,6 +191,10 @@ impl AsyncSeek for BufReader { /// `.into_inner()` immediately after a seek yields the underlying reader /// at the same position. /// + /// To seek without discarding the internal buffer, use + /// [`BufReader::seek_relative`](BufReader::seek_relative) or + /// [`BufReader::poll_seek_relative`](BufReader::poll_seek_relative). + /// /// See [`AsyncSeek`](futures_io::AsyncSeek) for more details. /// /// Note: In the edge case where you're seeking with `SeekFrom::Current(n)` @@ -189,10 +213,11 @@ impl AsyncSeek for BufReader { // it should be safe to assume that remainder fits within an i64 as the alternative // means we managed to allocate 8 exbibytes and that's absurd. // But it's not out of the realm of possibility for some weird underlying reader to - // support seeking by i64::min_value() so we need to handle underflow when subtracting + // support seeking by i64::MIN so we need to handle underflow when subtracting // remainder. if let Some(offset) = n.checked_sub(remainder) { - result = ready!(self.as_mut().project().inner.poll_seek(cx, SeekFrom::Current(offset)))?; + result = + ready!(self.as_mut().project().inner.poll_seek(cx, SeekFrom::Current(offset)))?; } else { // seek backwards by our remainder, and then by the offset ready!(self.as_mut().project().inner.poll_seek(cx, SeekFrom::Current(-remainder)))?; @@ -207,3 +232,33 @@ impl AsyncSeek for BufReader { Poll::Ready(Ok(result)) } } + +/// Future for the [`BufReader::seek_relative`](self::BufReader::seek_relative) method. +#[derive(Debug)] +#[must_use = "futures do nothing unless polled"] +pub struct SeeKRelative<'a, R> { + inner: Pin<&'a mut BufReader>, + offset: i64, + first: bool, +} + +impl Future for SeeKRelative<'_, R> +where + R: AsyncRead + AsyncSeek, +{ + type Output = io::Result<()>; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let offset = self.offset; + if self.first { + self.first = false; + self.inner.as_mut().poll_seek_relative(cx, offset) + } else { + self.inner + .as_mut() + .as_mut() + .poll_seek(cx, SeekFrom::Current(offset)) + .map(|res| res.map(|_| ())) + } + } +} diff --git a/futures-util/src/io/buf_writer.rs b/futures-util/src/io/buf_writer.rs index 991a365a1c..ddcf5e1b4d 100644 --- a/futures-util/src/io/buf_writer.rs +++ b/futures-util/src/io/buf_writer.rs @@ -1,3 +1,4 @@ +use super::DEFAULT_BUF_SIZE; use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, IoSlice, SeekFrom}; @@ -5,7 +6,8 @@ use pin_project_lite::pin_project; use std::fmt; use std::io::{self, Write}; use std::pin::Pin; -use super::DEFAULT_BUF_SIZE; +use std::ptr; +use std::vec::Vec; pin_project! { /// Wraps a writer and buffers its output. @@ -46,14 +48,10 @@ impl BufWriter { /// Creates a new `BufWriter` with the specified buffer capacity. pub fn with_capacity(cap: usize, inner: W) -> Self { - Self { - inner, - buf: Vec::with_capacity(cap), - written: 0, - } + Self { inner, buf: Vec::with_capacity(cap), written: 0 } } - fn flush_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + pub(super) fn flush_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); let len = this.buf.len(); @@ -81,12 +79,78 @@ impl BufWriter { Poll::Ready(ret) } + /// Write directly using `inner`, bypassing buffering + pub(super) fn inner_poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + self.project().inner.poll_write(cx, buf) + } + + /// Write directly using `inner`, bypassing buffering + pub(super) fn inner_poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + self.project().inner.poll_write_vectored(cx, bufs) + } +} + +impl BufWriter { delegate_access_inner!(inner, W, ()); /// Returns a reference to the internally buffered data. pub fn buffer(&self) -> &[u8] { &self.buf } + + /// Capacity of `buf`. how many chars can be held in buffer + pub(super) fn capacity(&self) -> usize { + self.buf.capacity() + } + + /// Remaining number of bytes to reach `buf` 's capacity + #[inline] + pub(super) fn spare_capacity(&self) -> usize { + self.buf.capacity() - self.buf.len() + } + + /// Write a byte slice directly into buffer + /// + /// Will truncate the number of bytes written to `spare_capacity()` so you want to + /// calculate the size of your slice to avoid losing bytes + /// + /// Based on `std::io::BufWriter` + pub(super) fn write_to_buf(self: Pin<&mut Self>, buf: &[u8]) -> usize { + let available = self.spare_capacity(); + let amt_to_buffer = available.min(buf.len()); + + // SAFETY: `amt_to_buffer` is <= buffer's spare capacity by construction. + unsafe { + self.write_to_buffer_unchecked(&buf[..amt_to_buffer]); + } + + amt_to_buffer + } + + /// Write byte slice directly into `self.buf` + /// + /// Based on `std::io::BufWriter` + #[inline] + unsafe fn write_to_buffer_unchecked(self: Pin<&mut Self>, buf: &[u8]) { + debug_assert!(buf.len() <= self.spare_capacity()); + let this = self.project(); + let old_len = this.buf.len(); + let buf_len = buf.len(); + let src = buf.as_ptr(); + unsafe { + let dst = this.buf.as_mut_ptr().add(old_len); + ptr::copy_nonoverlapping(src, dst, buf_len); + this.buf.set_len(old_len + buf_len); + } + } } impl AsyncWrite for BufWriter { diff --git a/futures-util/src/io/chain.rs b/futures-util/src/io/chain.rs index 1b6a335569..728a3d2dc0 100644 --- a/futures-util/src/io/chain.rs +++ b/futures-util/src/io/chain.rs @@ -1,7 +1,5 @@ use futures_core::ready; use futures_core::task::{Context, Poll}; -#[cfg(feature = "read-initializer")] -use futures_io::Initializer; use futures_io::{AsyncBufRead, AsyncRead, IoSliceMut}; use pin_project_lite::pin_project; use std::fmt; @@ -26,11 +24,7 @@ where U: AsyncRead, { pub(super) fn new(first: T, second: U) -> Self { - Self { - first, - second, - done_first: false, - } + Self { first, second, done_first: false } } /// Gets references to the underlying readers in this `Chain`. @@ -115,16 +109,6 @@ where } this.second.poll_read_vectored(cx, bufs) } - - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - let initializer = self.first.initializer(); - if initializer.should_initialize() { - initializer - } else { - self.second.initializer() - } - } } impl AsyncBufRead for Chain diff --git a/futures-util/src/io/copy.rs b/futures-util/src/io/copy.rs index bc592552e9..c80add271b 100644 --- a/futures-util/src/io/copy.rs +++ b/futures-util/src/io/copy.rs @@ -1,10 +1,10 @@ +use super::{copy_buf, BufReader, CopyBuf}; use futures_core::future::Future; use futures_core::task::{Context, Poll}; use futures_io::{AsyncRead, AsyncWrite}; +use pin_project_lite::pin_project; use std::io; use std::pin::Pin; -use super::{BufReader, copy_buf, CopyBuf}; -use pin_project_lite::pin_project; /// Creates a future which copies all the bytes from one object to another. /// @@ -36,9 +36,7 @@ where R: AsyncRead, W: AsyncWrite + Unpin + ?Sized, { - Copy { - inner: copy_buf(BufReader::new(reader), writer), - } + Copy { inner: copy_buf(BufReader::new(reader), writer) } } pin_project! { diff --git a/futures-util/src/io/copy_buf.rs b/futures-util/src/io/copy_buf.rs index 6adf594d54..50f7abdca9 100644 --- a/futures-util/src/io/copy_buf.rs +++ b/futures-util/src/io/copy_buf.rs @@ -2,9 +2,9 @@ use futures_core::future::Future; use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::{AsyncBufRead, AsyncWrite}; +use pin_project_lite::pin_project; use std::io; use std::pin::Pin; -use pin_project_lite::pin_project; /// Creates a future which copies all the bytes from one object to another. /// @@ -36,11 +36,7 @@ where R: AsyncBufRead, W: AsyncWrite + Unpin + ?Sized, { - CopyBuf { - reader, - writer, - amt: 0, - } + CopyBuf { reader, writer, amt: 0 } } pin_project! { @@ -56,8 +52,9 @@ pin_project! { } impl Future for CopyBuf<'_, R, W> - where R: AsyncBufRead, - W: AsyncWrite + Unpin + ?Sized, +where + R: AsyncBufRead, + W: AsyncWrite + Unpin + ?Sized, { type Output = io::Result; @@ -72,7 +69,7 @@ impl Future for CopyBuf<'_, R, W> let i = ready!(Pin::new(&mut this.writer).poll_write(cx, buffer))?; if i == 0 { - return Poll::Ready(Err(io::ErrorKind::WriteZero.into())) + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); } *this.amt += i as u64; this.reader.as_mut().consume(i); diff --git a/futures-util/src/io/copy_buf_abortable.rs b/futures-util/src/io/copy_buf_abortable.rs new file mode 100644 index 0000000000..ed22d62338 --- /dev/null +++ b/futures-util/src/io/copy_buf_abortable.rs @@ -0,0 +1,124 @@ +use crate::abortable::{AbortHandle, AbortInner, Aborted}; +use futures_core::future::Future; +use futures_core::task::{Context, Poll}; +use futures_io::{AsyncBufRead, AsyncWrite}; +use pin_project_lite::pin_project; +use std::io; +use std::pin::Pin; +use std::sync::atomic::Ordering; +use std::sync::Arc; + +/// Creates a future which copies all the bytes from one object to another, with its `AbortHandle`. +/// +/// The returned future will copy all the bytes read from this `AsyncBufRead` into the +/// `writer` specified. This future will only complete once abort has been requested or the `reader` has hit +/// EOF and all bytes have been written to and flushed from the `writer` +/// provided. +/// +/// On success the number of bytes is returned. If aborted, `Aborted` is returned. Otherwise, the underlying error is returned. +/// +/// # Examples +/// +/// ``` +/// # futures::executor::block_on(async { +/// use futures::io::{self, AsyncWriteExt, Cursor}; +/// use futures::future::Aborted; +/// +/// let reader = Cursor::new([1, 2, 3, 4]); +/// let mut writer = Cursor::new(vec![0u8; 5]); +/// +/// let (fut, abort_handle) = io::copy_buf_abortable(reader, &mut writer); +/// let bytes = fut.await; +/// abort_handle.abort(); +/// writer.close().await.unwrap(); +/// match bytes { +/// Ok(Ok(n)) => { +/// assert_eq!(n, 4); +/// assert_eq!(writer.into_inner(), [1, 2, 3, 4, 0]); +/// Ok(n) +/// }, +/// Ok(Err(a)) => { +/// Err::(a) +/// } +/// Err(e) => panic!("{}", e) +/// } +/// # }).unwrap(); +/// ``` +pub fn copy_buf_abortable( + reader: R, + writer: &mut W, +) -> (CopyBufAbortable<'_, R, W>, AbortHandle) +where + R: AsyncBufRead, + W: AsyncWrite + Unpin + ?Sized, +{ + let (handle, reg) = AbortHandle::new_pair(); + (CopyBufAbortable { reader, writer, amt: 0, inner: reg.inner }, handle) +} + +pin_project! { + /// Future for the [`copy_buf_abortable()`] function. + #[derive(Debug)] + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct CopyBufAbortable<'a, R, W: ?Sized> { + #[pin] + reader: R, + writer: &'a mut W, + amt: u64, + inner: Arc + } +} + +macro_rules! ready_or_break { + ($e:expr $(,)?) => { + match $e { + $crate::task::Poll::Ready(t) => t, + $crate::task::Poll::Pending => break, + } + }; +} + +impl Future for CopyBufAbortable<'_, R, W> +where + R: AsyncBufRead, + W: AsyncWrite + Unpin + Sized, +{ + type Output = Result, io::Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + loop { + // Check if the task has been aborted + if this.inner.aborted.load(Ordering::Relaxed) { + return Poll::Ready(Ok(Err(Aborted))); + } + + // Read some bytes from the reader, and if we have reached EOF, return total bytes read + let buffer = ready_or_break!(this.reader.as_mut().poll_fill_buf(cx))?; + if buffer.is_empty() { + ready_or_break!(Pin::new(&mut this.writer).poll_flush(cx))?; + return Poll::Ready(Ok(Ok(*this.amt))); + } + + // Pass the buffer to the writer, and update the amount written + let i = ready_or_break!(Pin::new(&mut this.writer).poll_write(cx, buffer))?; + if i == 0 { + return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); + } + *this.amt += i as u64; + this.reader.as_mut().consume(i); + } + // Schedule the task to be woken up again. + // Never called unless Poll::Pending is returned from io objects. + this.inner.waker.register(cx.waker()); + + // Check to see if the task was aborted between the first check and + // registration. + // Checking with `Relaxed` is sufficient because + // `register` introduces an `AcqRel` barrier. + if this.inner.aborted.load(Ordering::Relaxed) { + return Poll::Ready(Ok(Err(Aborted))); + } + Poll::Pending + } +} diff --git a/futures-util/src/io/cursor.rs b/futures-util/src/io/cursor.rs index 084fb0863b..01c8e5f4dc 100644 --- a/futures-util/src/io/cursor.rs +++ b/futures-util/src/io/cursor.rs @@ -1,9 +1,9 @@ use futures_core::task::{Context, Poll}; -#[cfg(feature = "read_initializer")] -use futures_io::Initializer; use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite, IoSlice, IoSliceMut, SeekFrom}; +use std::boxed::Box; use std::io; use std::pin::Pin; +use std::vec::Vec; /// A `Cursor` wraps an in-memory buffer and provides it with a /// [`AsyncSeek`] implementation. @@ -43,9 +43,7 @@ impl Cursor { /// # force_inference(&buff); /// ``` pub fn new(inner: T) -> Self { - Self { - inner: io::Cursor::new(inner), - } + Self { inner: io::Cursor::new(inner) } } /// Consumes this cursor, returning the underlying value. @@ -161,12 +159,6 @@ where } impl + Unpin> AsyncRead for Cursor { - #[cfg(feature = "read_initializer")] - #[inline] - unsafe fn initializer(&self) -> Initializer { - io::Read::initializer(&self.inner) - } - fn poll_read( mut self: Pin<&mut Self>, _cx: &mut Context<'_>, @@ -199,15 +191,19 @@ where macro_rules! delegate_async_write_to_stdio { () => { - fn poll_write(mut self: Pin<&mut Self>, _: &mut Context<'_>, buf: &[u8]) - -> Poll> - { + fn poll_write( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { Poll::Ready(io::Write::write(&mut self.inner, buf)) } - fn poll_write_vectored(mut self: Pin<&mut Self>, _: &mut Context<'_>, bufs: &[IoSlice<'_>]) - -> Poll> - { + fn poll_write_vectored( + mut self: Pin<&mut Self>, + _: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { Poll::Ready(io::Write::write_vectored(&mut self.inner, bufs)) } @@ -218,7 +214,7 @@ macro_rules! delegate_async_write_to_stdio { fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.poll_flush(cx) } - } + }; } impl AsyncWrite for Cursor<&mut [u8]> { diff --git a/futures-util/src/io/empty.rs b/futures-util/src/io/empty.rs index ab2395a8af..02f6103f54 100644 --- a/futures-util/src/io/empty.rs +++ b/futures-util/src/io/empty.rs @@ -1,6 +1,4 @@ use futures_core::task::{Context, Poll}; -#[cfg(feature = "read-initializer")] -use futures_io::Initializer; use futures_io::{AsyncBufRead, AsyncRead}; use std::fmt; use std::io; @@ -43,12 +41,6 @@ impl AsyncRead for Empty { ) -> Poll> { Poll::Ready(Ok(0)) } - - #[cfg(feature = "read-initializer")] - #[inline] - unsafe fn initializer(&self) -> Initializer { - Initializer::nop() - } } impl AsyncBufRead for Empty { diff --git a/futures-util/src/io/fill_buf.rs b/futures-util/src/io/fill_buf.rs index 6fb3ec73aa..45862b8e29 100644 --- a/futures-util/src/io/fill_buf.rs +++ b/futures-util/src/io/fill_buf.rs @@ -3,6 +3,7 @@ use futures_core::task::{Context, Poll}; use futures_io::AsyncBufRead; use std::io; use std::pin::Pin; +use std::slice; /// Future for the [`fill_buf`](super::AsyncBufReadExt::fill_buf) method. #[derive(Debug)] @@ -20,7 +21,8 @@ impl<'a, R: AsyncBufRead + ?Sized + Unpin> FillBuf<'a, R> { } impl<'a, R> Future for FillBuf<'a, R> - where R: AsyncBufRead + ?Sized + Unpin, +where + R: AsyncBufRead + ?Sized + Unpin, { type Output = io::Result<&'a [u8]>; @@ -29,17 +31,12 @@ impl<'a, R> Future for FillBuf<'a, R> let reader = this.reader.take().expect("Polled FillBuf after completion"); match Pin::new(&mut *reader).poll_fill_buf(cx) { - // With polinius it is possible to remove this inner match and just have the correct - // lifetime of the reference inferred based on which branch is taken - Poll::Ready(Ok(_)) => match Pin::new(reader).poll_fill_buf(cx) { - Poll::Ready(Ok(slice)) => Poll::Ready(Ok(slice)), - Poll::Ready(Err(err)) => { - unreachable!("reader indicated readiness but then returned an error: {:?}", err) - } - Poll::Pending => { - unreachable!("reader indicated readiness but then returned pending") - } - }, + Poll::Ready(Ok(slice)) => { + // With polonius it is possible to remove this lifetime transmutation and just have + // the correct lifetime of the reference inferred based on which branch is taken + let slice: &'a [u8] = unsafe { slice::from_raw_parts(slice.as_ptr(), slice.len()) }; + Poll::Ready(Ok(slice)) + } Poll::Ready(Err(err)) => Poll::Ready(Err(err)), Poll::Pending => { this.reader = Some(reader); diff --git a/futures-util/src/io/flush.rs b/futures-util/src/io/flush.rs index ece0a7cdc0..b75d14c5d3 100644 --- a/futures-util/src/io/flush.rs +++ b/futures-util/src/io/flush.rs @@ -20,7 +20,8 @@ impl<'a, W: AsyncWrite + ?Sized + Unpin> Flush<'a, W> { } impl Future for Flush<'_, W> - where W: AsyncWrite + ?Sized + Unpin, +where + W: AsyncWrite + ?Sized + Unpin, { type Output = io::Result<()>; diff --git a/futures-util/src/io/into_sink.rs b/futures-util/src/io/into_sink.rs index 885ba2fb8d..6a41ee2269 100644 --- a/futures-util/src/io/into_sink.rs +++ b/futures-util/src/io/into_sink.rs @@ -2,9 +2,9 @@ use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::AsyncWrite; use futures_sink::Sink; +use pin_project_lite::pin_project; use std::io; use std::pin::Pin; -use pin_project_lite::pin_project; #[derive(Debug)] struct Block { @@ -36,8 +36,7 @@ impl> IntoSink { fn poll_flush_buffer( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll> - { + ) -> Poll> { let mut this = self.project(); if let Some(buffer) = this.buffer { @@ -53,47 +52,29 @@ impl> IntoSink { *this.buffer = None; Poll::Ready(Ok(())) } - } impl> Sink for IntoSink { type Error = io::Error; - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> - { + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.poll_flush_buffer(cx))?; Poll::Ready(Ok(())) } - #[allow(clippy::debug_assert_with_mut_call)] - fn start_send( - self: Pin<&mut Self>, - item: Item, - ) -> Result<(), Self::Error> - { + fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error> { debug_assert!(self.buffer.is_none()); *self.project().buffer = Some(Block { offset: 0, bytes: item }); Ok(()) } - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> - { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().poll_flush_buffer(cx))?; ready!(self.project().writer.poll_flush(cx))?; Poll::Ready(Ok(())) } - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> - { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().poll_flush_buffer(cx))?; ready!(self.project().writer.poll_close(cx))?; Poll::Ready(Ok(())) diff --git a/futures-util/src/io/line_writer.rs b/futures-util/src/io/line_writer.rs new file mode 100644 index 0000000000..de1bb53924 --- /dev/null +++ b/futures-util/src/io/line_writer.rs @@ -0,0 +1,155 @@ +use super::buf_writer::BufWriter; +use futures_core::ready; +use futures_core::task::{Context, Poll}; +use futures_io::AsyncWrite; +use futures_io::IoSlice; +use pin_project_lite::pin_project; +use std::io; +use std::pin::Pin; + +pin_project! { +/// Wrap a writer, like [`BufWriter`] does, but prioritizes buffering lines +/// +/// This was written based on `std::io::LineWriter` which goes into further details +/// explaining the code. +/// +/// Buffering is actually done using `BufWriter`. This class will leverage `BufWriter` +/// to write on-each-line. +#[derive(Debug)] +pub struct LineWriter { + #[pin] + buf_writer: BufWriter, +} +} + +impl LineWriter { + /// Create a new `LineWriter` with default buffer capacity. The default is currently 1KB + /// which was taken from `std::io::LineWriter` + pub fn new(inner: W) -> Self { + Self::with_capacity(1024, inner) + } + + /// Creates a new `LineWriter` with the specified buffer capacity. + pub fn with_capacity(capacity: usize, inner: W) -> Self { + Self { buf_writer: BufWriter::with_capacity(capacity, inner) } + } + + /// Flush `buf_writer` if last char is "new line" + fn flush_if_completed_line(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + match this.buf_writer.buffer().last().copied() { + Some(b'\n') => this.buf_writer.flush_buf(cx), + _ => Poll::Ready(Ok(())), + } + } + + /// Returns a reference to `buf_writer`'s internally buffered data. + pub fn buffer(&self) -> &[u8] { + self.buf_writer.buffer() + } + + /// Acquires a reference to the underlying sink or stream that this combinator is + /// pulling from. + pub fn get_ref(&self) -> &W { + self.buf_writer.get_ref() + } +} + +impl AsyncWrite for LineWriter { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + let mut this = self.as_mut().project(); + let newline_index = match memchr::memrchr(b'\n', buf) { + None => { + ready!(self.as_mut().flush_if_completed_line(cx)?); + return self.project().buf_writer.poll_write(cx, buf); + } + Some(newline_index) => newline_index + 1, + }; + + ready!(this.buf_writer.as_mut().poll_flush(cx)?); + + let lines = &buf[..newline_index]; + + let flushed = { ready!(this.buf_writer.as_mut().inner_poll_write(cx, lines))? }; + + if flushed == 0 { + return Poll::Ready(Ok(0)); + } + + let tail = if flushed >= newline_index { + &buf[flushed..] + } else if newline_index - flushed <= this.buf_writer.capacity() { + &buf[flushed..newline_index] + } else { + let scan_area = &buf[flushed..]; + let scan_area = &scan_area[..this.buf_writer.capacity()]; + match memchr::memrchr(b'\n', scan_area) { + Some(newline_index) => &scan_area[..newline_index + 1], + None => scan_area, + } + }; + + let buffered = this.buf_writer.as_mut().write_to_buf(tail); + Poll::Ready(Ok(flushed + buffered)) + } + + fn poll_write_vectored( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { + let mut this = self.as_mut().project(); + // `is_write_vectored()` is handled in original code, but not in this crate + // see https://github.com/rust-lang/rust/issues/70436 + + let last_newline_buf_idx = bufs + .iter() + .enumerate() + .rev() + .find_map(|(i, buf)| memchr::memchr(b'\n', buf).map(|_| i)); + let last_newline_buf_idx = match last_newline_buf_idx { + None => { + ready!(self.as_mut().flush_if_completed_line(cx)?); + return self.project().buf_writer.poll_write_vectored(cx, bufs); + } + Some(i) => i, + }; + + ready!(this.buf_writer.as_mut().poll_flush(cx)?); + + let (lines, tail) = bufs.split_at(last_newline_buf_idx + 1); + + let flushed = { ready!(this.buf_writer.as_mut().inner_poll_write_vectored(cx, lines))? }; + if flushed == 0 { + return Poll::Ready(Ok(0)); + } + + let lines_len = lines.iter().map(|buf| buf.len()).sum(); + if flushed < lines_len { + return Poll::Ready(Ok(flushed)); + } + + let buffered: usize = tail + .iter() + .filter(|buf| !buf.is_empty()) + .map(|buf| this.buf_writer.as_mut().write_to_buf(buf)) + .take_while(|&n| n > 0) + .sum(); + + Poll::Ready(Ok(flushed + buffered)) + } + + /// Forward to `buf_writer` 's `BufWriter::poll_flush()` + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.as_mut().project().buf_writer.poll_flush(cx) + } + + /// Forward to `buf_writer` 's `BufWriter::poll_close()` + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.as_mut().project().buf_writer.poll_close(cx) + } +} diff --git a/futures-util/src/io/lines.rs b/futures-util/src/io/lines.rs index 6ae7392245..0a1abf4bf6 100644 --- a/futures-util/src/io/lines.rs +++ b/futures-util/src/io/lines.rs @@ -1,12 +1,14 @@ +use super::read_line::read_line_internal; use futures_core::ready; use futures_core::stream::Stream; use futures_core::task::{Context, Poll}; use futures_io::AsyncBufRead; +use pin_project_lite::pin_project; use std::io; use std::mem; use std::pin::Pin; -use super::read_line::read_line_internal; -use pin_project_lite::pin_project; +use std::string::String; +use std::vec::Vec; pin_project! { /// Stream for the [`lines`](super::AsyncBufReadExt::lines) method. @@ -23,12 +25,7 @@ pin_project! { impl Lines { pub(super) fn new(reader: R) -> Self { - Self { - reader, - buf: String::new(), - bytes: Vec::new(), - read: 0, - } + Self { reader, buf: String::new(), bytes: Vec::new(), read: 0 } } } @@ -38,8 +35,9 @@ impl Stream for Lines { fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); let n = ready!(read_line_internal(this.reader, cx, this.buf, this.bytes, this.read))?; + *this.read = 0; if n == 0 && this.buf.is_empty() { - return Poll::Ready(None) + return Poll::Ready(None); } if this.buf.ends_with('\n') { this.buf.pop(); @@ -47,6 +45,6 @@ impl Stream for Lines { this.buf.pop(); } } - Poll::Ready(Some(Ok(mem::replace(this.buf, String::new())))) + Poll::Ready(Some(Ok(mem::take(this.buf)))) } } diff --git a/futures-util/src/io/mod.rs b/futures-util/src/io/mod.rs index 1437930f66..c07fe3fe47 100644 --- a/futures-util/src/io/mod.rs +++ b/futures-util/src/io/mod.rs @@ -21,18 +21,14 @@ use crate::compat::Compat; use crate::future::assert_future; use crate::stream::assert_stream; -use std::{ptr, pin::Pin}; +use std::{pin::Pin, ptr, string::String, vec::Vec}; // Re-export some types from `std::io` so that users don't have to deal // with conflicts when `use`ing `futures::io` and `std::io`. #[doc(no_inline)] pub use std::io::{Error, ErrorKind, IoSlice, IoSliceMut, Result, SeekFrom}; -#[doc(no_inline)] -#[cfg(feature = "read-initializer")] -#[cfg_attr(docsrs, doc(cfg(feature = "read-initializer")))] -pub use std::io::Initializer; -pub use futures_io::{AsyncRead, AsyncWrite, AsyncSeek, AsyncBufRead}; +pub use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite}; // used by `BufReader` and `BufWriter` // https://github.com/rust-lang/rust/blob/master/src/libstd/sys_common/io.rs#L1 @@ -40,27 +36,24 @@ const DEFAULT_BUF_SIZE: usize = 8 * 1024; /// Initializes a buffer if necessary. /// -/// A buffer is always initialized if `read-initializer` feature is disabled. +/// A buffer is currently always initialized. #[inline] unsafe fn initialize(_reader: &R, buf: &mut [u8]) { - #[cfg(feature = "read-initializer")] - { - if !_reader.initializer().should_initialize() { - return; - } - } - ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()) + unsafe { ptr::write_bytes(buf.as_mut_ptr(), 0, buf.len()) } } mod allow_std; pub use self::allow_std::AllowStdIo; mod buf_reader; -pub use self::buf_reader::BufReader; +pub use self::buf_reader::{BufReader, SeeKRelative}; mod buf_writer; pub use self::buf_writer::BufWriter; +mod line_writer; +pub use self::line_writer::LineWriter; + mod chain; pub use self::chain::Chain; @@ -73,6 +66,9 @@ pub use self::copy::{copy, Copy}; mod copy_buf; pub use self::copy_buf::{copy_buf, CopyBuf}; +mod copy_buf_abortable; +pub use self::copy_buf_abortable::{copy_buf_abortable, CopyBufAbortable}; + mod cursor; pub use self::cursor::Cursor; @@ -126,7 +122,7 @@ mod sink; pub use self::sink::{sink, Sink}; mod split; -pub use self::split::{ReadHalf, WriteHalf, ReuniteError}; +pub use self::split::{ReadHalf, ReuniteError, WriteHalf}; mod take; pub use self::take::Take; @@ -206,7 +202,8 @@ pub trait AsyncReadExt: AsyncRead { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn read<'a>(&'a mut self, buf: &'a mut [u8]) -> Read<'a, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(Read::new(self, buf)) } @@ -217,7 +214,8 @@ pub trait AsyncReadExt: AsyncRead { /// The returned future will resolve to the number of bytes read once the read /// operation is completed. fn read_vectored<'a>(&'a mut self, bufs: &'a mut [IoSliceMut<'a>]) -> ReadVectored<'a, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(ReadVectored::new(self, bufs)) } @@ -259,11 +257,9 @@ pub trait AsyncReadExt: AsyncRead { /// assert_eq!(result.unwrap_err().kind(), io::ErrorKind::UnexpectedEof); /// # }); /// ``` - fn read_exact<'a>( - &'a mut self, - buf: &'a mut [u8], - ) -> ReadExact<'a, Self> - where Self: Unpin, + fn read_exact<'a>(&'a mut self, buf: &'a mut [u8]) -> ReadExact<'a, Self> + where + Self: Unpin, { assert_future::, _>(ReadExact::new(self, buf)) } @@ -287,11 +283,9 @@ pub trait AsyncReadExt: AsyncRead { /// assert_eq!(output, vec![1, 2, 3, 4]); /// # Ok::<(), Box>(()) }).unwrap(); /// ``` - fn read_to_end<'a>( - &'a mut self, - buf: &'a mut Vec, - ) -> ReadToEnd<'a, Self> - where Self: Unpin, + fn read_to_end<'a>(&'a mut self, buf: &'a mut Vec) -> ReadToEnd<'a, Self> + where + Self: Unpin, { assert_future::, _>(ReadToEnd::new(self, buf)) } @@ -315,11 +309,9 @@ pub trait AsyncReadExt: AsyncRead { /// assert_eq!(buffer, String::from("1234")); /// # Ok::<(), Box>(()) }).unwrap(); /// ``` - fn read_to_string<'a>( - &'a mut self, - buf: &'a mut String, - ) -> ReadToString<'a, Self> - where Self: Unpin, + fn read_to_string<'a>(&'a mut self, buf: &'a mut String) -> ReadToString<'a, Self> + where + Self: Unpin, { assert_future::, _>(ReadToString::new(self, buf)) } @@ -354,7 +346,8 @@ pub trait AsyncReadExt: AsyncRead { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn split(self) -> (ReadHalf, WriteHalf) - where Self: AsyncWrite + Sized, + where + Self: AsyncWrite + Sized, { let (r, w) = split::split(self); (assert_read(r), assert_write(w)) @@ -380,7 +373,8 @@ pub trait AsyncReadExt: AsyncRead { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn take(self, limit: u64) -> Take - where Self: Sized + where + Self: Sized, { assert_read(Take::new(self, limit)) } @@ -394,7 +388,8 @@ pub trait AsyncReadExt: AsyncRead { #[cfg(feature = "io-compat")] #[cfg_attr(docsrs, doc(cfg(feature = "io-compat")))] fn compat(self) -> Compat - where Self: Sized + Unpin, + where + Self: Sized + Unpin, { Compat::new(self) } @@ -427,14 +422,16 @@ pub trait AsyncWriteExt: AsyncWrite { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn flush(&mut self) -> Flush<'_, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(Flush::new(self)) } /// Creates a future which will entirely close this `AsyncWrite`. fn close(&mut self) -> Close<'_, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(Close::new(self)) } @@ -444,7 +441,8 @@ pub trait AsyncWriteExt: AsyncWrite { /// The returned future will resolve to the number of bytes written once the write /// operation is completed. fn write<'a>(&'a mut self, buf: &'a [u8]) -> Write<'a, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(Write::new(self, buf)) } @@ -455,7 +453,8 @@ pub trait AsyncWriteExt: AsyncWrite { /// The returned future will resolve to the number of bytes written once the write /// operation is completed. fn write_vectored<'a>(&'a mut self, bufs: &'a [IoSlice<'a>]) -> WriteVectored<'a, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(WriteVectored::new(self, bufs)) } @@ -481,7 +480,8 @@ pub trait AsyncWriteExt: AsyncWrite { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn write_all<'a>(&'a mut self, buf: &'a [u8]) -> WriteAll<'a, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(WriteAll::new(self, buf)) } @@ -547,7 +547,8 @@ pub trait AsyncWriteExt: AsyncWrite { #[cfg(feature = "io-compat")] #[cfg_attr(docsrs, doc(cfg(feature = "io-compat")))] fn compat_write(self) -> Compat - where Self: Sized + Unpin, + where + Self: Sized + Unpin, { Compat::new(self) } @@ -581,7 +582,8 @@ pub trait AsyncWriteExt: AsyncWrite { #[cfg(feature = "sink")] #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] fn into_sink>(self) -> IntoSink - where Self: Sized, + where + Self: Sized, { crate::sink::assert_sink::(IntoSink::new(self)) } @@ -597,10 +599,22 @@ pub trait AsyncSeekExt: AsyncSeek { /// In the case of an error the buffer and the object will be discarded, with /// the error yielded. fn seek(&mut self, pos: SeekFrom) -> Seek<'_, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(Seek::new(self, pos)) } + + /// Creates a future which will return the current seek position from the + /// start of the stream. + /// + /// This is equivalent to `self.seek(SeekFrom::Current(0))`. + fn stream_position(&mut self) -> Seek<'_, Self> + where + Self: Unpin, + { + self.seek(SeekFrom::Current(0)) + } } impl AsyncSeekExt for S {} @@ -631,7 +645,8 @@ pub trait AsyncBufReadExt: AsyncBufRead { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn fill_buf(&mut self) -> FillBuf<'_, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(FillBuf::new(self)) } @@ -654,7 +669,8 @@ pub trait AsyncBufReadExt: AsyncBufRead { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn consume_unpin(&mut self, amt: usize) - where Self: Unpin, + where + Self: Unpin, { Pin::new(self).consume(amt) } @@ -700,12 +716,9 @@ pub trait AsyncBufReadExt: AsyncBufRead { /// assert_eq!(buf, b""); /// # Ok::<(), Box>(()) }).unwrap(); /// ``` - fn read_until<'a>( - &'a mut self, - byte: u8, - buf: &'a mut Vec, - ) -> ReadUntil<'a, Self> - where Self: Unpin, + fn read_until<'a>(&'a mut self, byte: u8, buf: &'a mut Vec) -> ReadUntil<'a, Self> + where + Self: Unpin, { assert_future::, _>(ReadUntil::new(self, byte, buf)) } @@ -762,7 +775,8 @@ pub trait AsyncBufReadExt: AsyncBufRead { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn read_line<'a>(&'a mut self, buf: &'a mut String) -> ReadLine<'a, Self> - where Self: Unpin, + where + Self: Unpin, { assert_future::, _>(ReadLine::new(self, buf)) } @@ -790,17 +804,18 @@ pub trait AsyncBufReadExt: AsyncBufRead { /// use futures::io::{AsyncBufReadExt, Cursor}; /// use futures::stream::StreamExt; /// - /// let cursor = Cursor::new(b"lorem\nipsum\r\ndolor"); + /// let cursor = Cursor::new(b"lorem\nipsum\xc2\r\ndolor"); /// - /// let mut lines_stream = cursor.lines().map(|l| l.unwrap()); + /// let mut lines_stream = cursor.lines().map(|l| l.unwrap_or(String::from("invalid UTF_8"))); /// assert_eq!(lines_stream.next().await, Some(String::from("lorem"))); - /// assert_eq!(lines_stream.next().await, Some(String::from("ipsum"))); + /// assert_eq!(lines_stream.next().await, Some(String::from("invalid UTF_8"))); /// assert_eq!(lines_stream.next().await, Some(String::from("dolor"))); /// assert_eq!(lines_stream.next().await, None); /// # Ok::<(), Box>(()) }).unwrap(); /// ``` fn lines(self) -> Lines - where Self: Sized, + where + Self: Sized, { assert_stream::, _>(Lines::new(self)) } diff --git a/futures-util/src/io/read_exact.rs b/futures-util/src/io/read_exact.rs index f2e0440890..cd0b20e597 100644 --- a/futures-util/src/io/read_exact.rs +++ b/futures-util/src/io/read_exact.rs @@ -1,6 +1,6 @@ use crate::io::AsyncRead; -use futures_core::ready; use futures_core::future::Future; +use futures_core::ready; use futures_core::task::{Context, Poll}; use std::io; use std::mem; @@ -30,11 +30,11 @@ impl Future for ReadExact<'_, R> { while !this.buf.is_empty() { let n = ready!(Pin::new(&mut this.reader).poll_read(cx, this.buf))?; { - let (_, rest) = mem::replace(&mut this.buf, &mut []).split_at_mut(n); + let (_, rest) = mem::take(&mut this.buf).split_at_mut(n); this.buf = rest; } if n == 0 { - return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())) + return Poll::Ready(Err(io::ErrorKind::UnexpectedEof.into())); } } Poll::Ready(Ok(())) diff --git a/futures-util/src/io/read_line.rs b/futures-util/src/io/read_line.rs index d402c96605..43942add54 100644 --- a/futures-util/src/io/read_line.rs +++ b/futures-util/src/io/read_line.rs @@ -1,12 +1,14 @@ -use futures_core::ready; +use super::read_until::read_until_internal; use futures_core::future::Future; +use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::AsyncBufRead; use std::io; use std::mem; use std::pin::Pin; use std::str; -use super::read_until::read_until_internal; +use std::string::String; +use std::vec::Vec; /// Future for the [`read_line`](super::AsyncBufReadExt::read_line) method. #[derive(Debug)] @@ -16,18 +18,14 @@ pub struct ReadLine<'a, R: ?Sized> { buf: &'a mut String, bytes: Vec, read: usize, + finished: bool, } impl Unpin for ReadLine<'_, R> {} impl<'a, R: AsyncBufRead + ?Sized + Unpin> ReadLine<'a, R> { pub(super) fn new(reader: &'a mut R, buf: &'a mut String) -> Self { - Self { - reader, - bytes: mem::replace(buf, String::new()).into_bytes(), - buf, - read: 0, - } + Self { reader, bytes: mem::take(buf).into_bytes(), buf, read: 0, finished: false } } } @@ -38,25 +36,42 @@ pub(super) fn read_line_internal( bytes: &mut Vec, read: &mut usize, ) -> Poll> { - let ret = ready!(read_until_internal(reader, cx, b'\n', bytes, read)); - if str::from_utf8(bytes).is_err() { - Poll::Ready(ret.and_then(|_| { - Err(io::Error::new(io::ErrorKind::InvalidData, "stream did not contain valid UTF-8")) - })) - } else { - debug_assert!(buf.is_empty()); - debug_assert_eq!(*read, 0); - // Safety: `bytes` is a valid UTF-8 because `str::from_utf8` returned `Ok`. - mem::swap(unsafe { buf.as_mut_vec() }, bytes); - Poll::Ready(ret) + let mut ret = ready!(read_until_internal(reader, cx, b'\n', bytes, read)); + if str::from_utf8(&bytes[bytes.len() - *read..bytes.len()]).is_err() { + bytes.truncate(bytes.len() - *read); + if ret.is_ok() { + ret = Err(io::Error::new( + io::ErrorKind::InvalidData, + "stream did not contain valid UTF-8", + )); + } } + *read = 0; + // Safety: `bytes` is valid UTF-8 because it was taken from a String + // and the newly read bytes are either valid UTF-8 or have been removed. + mem::swap(unsafe { buf.as_mut_vec() }, bytes); + Poll::Ready(ret) } impl Future for ReadLine<'_, R> { type Output = io::Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Self { reader, buf, bytes, read } = &mut *self; - read_line_internal(Pin::new(reader), cx, buf, bytes, read) + let Self { reader, buf, bytes, read, finished: _ } = &mut *self; + let ret = ready!(read_line_internal(Pin::new(reader), cx, buf, bytes, read)); + self.finished = true; + Poll::Ready(ret) + } +} + +impl Drop for ReadLine<'_, R> { + fn drop(&mut self) { + // restore old string contents + if !self.finished { + self.bytes.truncate(self.bytes.len() - self.read); + // Safety: `bytes` is valid UTF-8 because it was taken from a String + // and the newly read bytes have been removed. + mem::swap(unsafe { self.buf.as_mut_vec() }, &mut self.bytes); + } } } diff --git a/futures-util/src/io/read_to_end.rs b/futures-util/src/io/read_to_end.rs index 7bd2c89914..919d7d13c7 100644 --- a/futures-util/src/io/read_to_end.rs +++ b/futures-util/src/io/read_to_end.rs @@ -20,11 +20,7 @@ impl Unpin for ReadToEnd<'_, R> {} impl<'a, R: AsyncRead + ?Sized + Unpin> ReadToEnd<'a, R> { pub(super) fn new(reader: &'a mut R, buf: &'a mut Vec) -> Self { let start_len = buf.len(); - Self { - reader, - buf, - start_len, - } + Self { reader, buf, start_len } } } @@ -56,10 +52,7 @@ pub(super) fn read_to_end_internal( buf: &mut Vec, start_len: usize, ) -> Poll> { - let mut g = Guard { - len: buf.len(), - buf, - }; + let mut g = Guard { len: buf.len(), buf }; loop { if g.len == g.buf.len() { unsafe { diff --git a/futures-util/src/io/read_to_string.rs b/futures-util/src/io/read_to_string.rs index 9242654ff1..a075ca2662 100644 --- a/futures-util/src/io/read_to_string.rs +++ b/futures-util/src/io/read_to_string.rs @@ -1,9 +1,10 @@ use super::read_to_end::read_to_end_internal; -use futures_core::ready; use futures_core::future::Future; +use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::AsyncRead; use std::pin::Pin; +use std::string::String; use std::vec::Vec; use std::{io, mem, str}; @@ -22,12 +23,7 @@ impl Unpin for ReadToString<'_, R> {} impl<'a, R: AsyncRead + ?Sized + Unpin> ReadToString<'a, R> { pub(super) fn new(reader: &'a mut R, buf: &'a mut String) -> Self { let start_len = buf.len(); - Self { - reader, - bytes: mem::replace(buf, String::new()).into_bytes(), - buf, - start_len, - } + Self { reader, bytes: mem::take(buf).into_bytes(), buf, start_len } } } @@ -41,10 +37,7 @@ fn read_to_string_internal( let ret = ready!(read_to_end_internal(reader, cx, bytes, start_len)); if str::from_utf8(bytes).is_err() { Poll::Ready(ret.and_then(|_| { - Err(io::Error::new( - io::ErrorKind::InvalidData, - "stream did not contain valid UTF-8", - )) + Err(io::Error::new(io::ErrorKind::InvalidData, "stream did not contain valid UTF-8")) })) } else { debug_assert!(buf.is_empty()); diff --git a/futures-util/src/io/read_until.rs b/futures-util/src/io/read_until.rs index 72b59eab13..adc359db51 100644 --- a/futures-util/src/io/read_until.rs +++ b/futures-util/src/io/read_until.rs @@ -3,8 +3,8 @@ use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::AsyncBufRead; use std::io; -use std::mem; use std::pin::Pin; +use std::vec::Vec; /// Future for the [`read_until`](super::AsyncBufReadExt::read_until) method. #[derive(Debug)] @@ -45,7 +45,7 @@ pub(super) fn read_until_internal( reader.as_mut().consume(used); *read += used; if done || used == 0 { - return Poll::Ready(Ok(mem::replace(read, 0))); + return Poll::Ready(Ok(*read)); } } } diff --git a/futures-util/src/io/repeat.rs b/futures-util/src/io/repeat.rs index 4cefcb2a28..2828bf0114 100644 --- a/futures-util/src/io/repeat.rs +++ b/futures-util/src/io/repeat.rs @@ -1,7 +1,5 @@ use futures_core::ready; use futures_core::task::{Context, Poll}; -#[cfg(feature = "read-initializer")] -use futures_io::Initializer; use futures_io::{AsyncRead, IoSliceMut}; use std::fmt; use std::io; @@ -59,12 +57,6 @@ impl AsyncRead for Repeat { } Poll::Ready(Ok(nwritten)) } - - #[cfg(feature = "read-initializer")] - #[inline] - unsafe fn initializer(&self) -> Initializer { - Initializer::nop() - } } impl fmt::Debug for Repeat { diff --git a/futures-util/src/io/split.rs b/futures-util/src/io/split.rs index 185c21c7d4..5b6bc18937 100644 --- a/futures-util/src/io/split.rs +++ b/futures-util/src/io/split.rs @@ -1,8 +1,8 @@ use crate::lock::BiLock; +use core::fmt; use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::{AsyncRead, AsyncWrite, IoSlice, IoSliceMut}; -use core::fmt; use std::io; use std::pin::Pin; @@ -18,12 +18,9 @@ pub struct WriteHalf { handle: BiLock, } -fn lock_and_then( - lock: &BiLock, - cx: &mut Context<'_>, - f: F -) -> Poll> - where F: FnOnce(Pin<&mut T>, &mut Context<'_>) -> Poll> +fn lock_and_then(lock: &BiLock, cx: &mut Context<'_>, f: F) -> Poll> +where + F: FnOnce(Pin<&mut T>, &mut Context<'_>) -> Poll>, { let mut l = ready!(lock.poll_lock(cx)); f(l.as_pin_mut(), cx) @@ -34,14 +31,28 @@ pub(super) fn split(t: T) -> (ReadHalf, WriteHalf< (ReadHalf { handle: a }, WriteHalf { handle: b }) } +impl ReadHalf { + /// Checks if this `ReadHalf` and some `WriteHalf` were split from the same stream. + pub fn is_pair_of(&self, other: &WriteHalf) -> bool { + self.handle.is_pair_of(&other.handle) + } +} + impl ReadHalf { /// Attempts to put the two "halves" of a split `AsyncRead + AsyncWrite` back /// together. Succeeds only if the `ReadHalf` and `WriteHalf` are /// a matching pair originating from the same call to `AsyncReadExt::split`. pub fn reunite(self, other: WriteHalf) -> Result> { - self.handle.reunite(other.handle).map_err(|err| { - ReuniteError(ReadHalf { handle: err.0 }, WriteHalf { handle: err.1 }) - }) + self.handle + .reunite(other.handle) + .map_err(|err| ReuniteError(Self { handle: err.0 }, WriteHalf { handle: err.1 })) + } +} + +impl WriteHalf { + /// Checks if this `WriteHalf` and some `ReadHalf` were split from the same stream. + pub fn is_pair_of(&self, other: &ReadHalf) -> bool { + self.handle.is_pair_of(&other.handle) } } @@ -55,29 +66,37 @@ impl WriteHalf { } impl AsyncRead for ReadHalf { - fn poll_read(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { lock_and_then(&self.handle, cx, |l, cx| l.poll_read(cx, buf)) } - fn poll_read_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &mut [IoSliceMut<'_>]) - -> Poll> - { + fn poll_read_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &mut [IoSliceMut<'_>], + ) -> Poll> { lock_and_then(&self.handle, cx, |l, cx| l.poll_read_vectored(cx, bufs)) } } impl AsyncWrite for WriteHalf { - fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) - -> Poll> - { + fn poll_write( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { lock_and_then(&self.handle, cx, |l, cx| l.poll_write(cx, buf)) } - fn poll_write_vectored(self: Pin<&mut Self>, cx: &mut Context<'_>, bufs: &[IoSlice<'_>]) - -> Poll> - { + fn poll_write_vectored( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + bufs: &[IoSlice<'_>], + ) -> Poll> { lock_and_then(&self.handle, cx, |l, cx| l.poll_write_vectored(cx, bufs)) } @@ -96,9 +115,7 @@ pub struct ReuniteError(pub ReadHalf, pub WriteHalf); impl fmt::Debug for ReuniteError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("ReuniteError") - .field(&"...") - .finish() + f.debug_tuple("ReuniteError").field(&"...").finish() } } diff --git a/futures-util/src/io/take.rs b/futures-util/src/io/take.rs index 687a69744f..2c494804d9 100644 --- a/futures-util/src/io/take.rs +++ b/futures-util/src/io/take.rs @@ -1,11 +1,9 @@ use futures_core::ready; use futures_core::task::{Context, Poll}; -#[cfg(feature = "read-initializer")] -use futures_io::Initializer; -use futures_io::{AsyncRead, AsyncBufRead}; +use futures_io::{AsyncBufRead, AsyncRead}; use pin_project_lite::pin_project; -use std::{cmp, io}; use std::pin::Pin; +use std::{cmp, io}; pin_project! { /// Reader for the [`take`](super::AsyncReadExt::take) method. @@ -14,14 +12,13 @@ pin_project! { pub struct Take { #[pin] inner: R, - // Add '_' to avoid conflicts with `limit` method. - limit_: u64, + limit: u64, } } impl Take { pub(super) fn new(inner: R, limit: u64) -> Self { - Self { inner, limit_: limit } + Self { inner, limit } } /// Returns the remaining number of bytes that can be @@ -48,7 +45,7 @@ impl Take { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` pub fn limit(&self) -> u64 { - self.limit_ + self.limit } /// Sets the number of bytes that can be read before this instance will @@ -78,7 +75,7 @@ impl Take { /// # Ok::<(), Box>(()) }).unwrap(); /// ``` pub fn set_limit(&mut self, limit: u64) { - self.limit_ = limit + self.limit = limit } delegate_access_inner!(inner, R, ()); @@ -92,20 +89,15 @@ impl AsyncRead for Take { ) -> Poll> { let this = self.project(); - if *this.limit_ == 0 { + if *this.limit == 0 { return Poll::Ready(Ok(0)); } - let max = cmp::min(buf.len() as u64, *this.limit_) as usize; + let max = cmp::min(buf.len() as u64, *this.limit) as usize; let n = ready!(this.inner.poll_read(cx, &mut buf[..max]))?; - *this.limit_ -= n as u64; + *this.limit -= n as u64; Poll::Ready(Ok(n)) } - - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> Initializer { - self.inner.initializer() - } } impl AsyncBufRead for Take { @@ -113,12 +105,12 @@ impl AsyncBufRead for Take { let this = self.project(); // Don't call into inner reader at all at EOF because it may still block - if *this.limit_ == 0 { + if *this.limit == 0 { return Poll::Ready(Ok(&[])); } let buf = ready!(this.inner.poll_fill_buf(cx)?); - let cap = cmp::min(buf.len() as u64, *this.limit_) as usize; + let cap = cmp::min(buf.len() as u64, *this.limit) as usize; Poll::Ready(Ok(&buf[..cap])) } @@ -126,8 +118,8 @@ impl AsyncBufRead for Take { let this = self.project(); // Don't let callers reset the limit by passing an overlarge value - let amt = cmp::min(amt as u64, *this.limit_) as usize; - *this.limit_ -= amt as u64; + let amt = cmp::min(amt as u64, *this.limit) as usize; + *this.limit -= amt as u64; this.inner.consume(amt); } } diff --git a/futures-util/src/io/window.rs b/futures-util/src/io/window.rs index 3424197d75..d857282383 100644 --- a/futures-util/src/io/window.rs +++ b/futures-util/src/io/window.rs @@ -1,6 +1,6 @@ use std::ops::{Bound, Range, RangeBounds}; -/// A owned window around an underlying buffer. +/// An owned window around an underlying buffer. /// /// Normally slices work great for considering sub-portions of a buffer, but /// unfortunately a slice is a *borrowed* type in Rust which has an associated @@ -30,10 +30,7 @@ impl> Window { /// Further methods can be called on the returned `Window` to alter the /// window into the data provided. pub fn new(t: T) -> Self { - Self { - range: 0..t.as_ref().len(), - inner: t, - } + Self { range: 0..t.as_ref().len(), inner: t } } /// Gets a shared reference to the underlying buffer inside of this diff --git a/futures-util/src/io/write_all.rs b/futures-util/src/io/write_all.rs index b134bf1b22..08c025f94d 100644 --- a/futures-util/src/io/write_all.rs +++ b/futures-util/src/io/write_all.rs @@ -30,7 +30,7 @@ impl Future for WriteAll<'_, W> { while !this.buf.is_empty() { let n = ready!(Pin::new(&mut this.writer).poll_write(cx, this.buf))?; { - let (_, rest) = mem::replace(&mut this.buf, &[]).split_at(n); + let (_, rest) = mem::take(&mut this.buf).split_at(n); this.buf = rest; } if n == 0 { diff --git a/futures-util/src/io/write_all_vectored.rs b/futures-util/src/io/write_all_vectored.rs index 380604df98..26b3f25d92 100644 --- a/futures-util/src/io/write_all_vectored.rs +++ b/futures-util/src/io/write_all_vectored.rs @@ -1,10 +1,9 @@ -use futures_core::ready; use futures_core::future::Future; +use futures_core::ready; use futures_core::task::{Context, Poll}; use futures_io::AsyncWrite; use futures_io::IoSlice; use std::io; -use std::mem; use std::pin::Pin; /// Future for the @@ -19,8 +18,9 @@ pub struct WriteAllVectored<'a, W: ?Sized + Unpin> { impl Unpin for WriteAllVectored<'_, W> {} impl<'a, W: AsyncWrite + ?Sized + Unpin> WriteAllVectored<'a, W> { - pub(super) fn new(writer: &'a mut W, bufs: &'a mut [IoSlice<'a>]) -> Self { - Self { writer, bufs: IoSlice::advance(bufs, 0) } + pub(super) fn new(writer: &'a mut W, mut bufs: &'a mut [IoSlice<'a>]) -> Self { + IoSlice::advance_slices(&mut bufs, 0); + Self { writer, bufs } } } @@ -34,7 +34,7 @@ impl Future for WriteAllVectored<'_, W> { if n == 0 { return Poll::Ready(Err(io::ErrorKind::WriteZero.into())); } else { - this.bufs = IoSlice::advance(mem::take(&mut this.bufs), n); + IoSlice::advance_slices(&mut this.bufs, n); } } @@ -49,6 +49,8 @@ mod tests { use std::io; use std::pin::Pin; use std::task::{Context, Poll}; + use std::vec; + use std::vec::Vec; use crate::io::{AsyncWrite, AsyncWriteExt, IoSlice}; use crate::task::noop_waker; @@ -56,11 +58,7 @@ mod tests { /// Create a new writer that reads from at most `n_bufs` and reads /// `per_call` bytes (in total) per call to write. fn test_writer(n_bufs: usize, per_call: usize) -> TestWriter { - TestWriter { - n_bufs, - per_call, - written: Vec::new(), - } + TestWriter { n_bufs, per_call, written: Vec::new() } } // TODO: maybe move this the future-test crate? @@ -110,10 +108,9 @@ mod tests { let expected = $expected; match $e { Poll::Ready(Ok(ok)) if ok == expected => {} - got => panic!( - "unexpected result, got: {:?}, wanted: Ready(Ok({:?}))", - got, expected - ), + got => { + panic!("unexpected result, got: {:?}, wanted: Ready(Ok({:?}))", got, expected) + } } }; } @@ -154,11 +151,7 @@ mod tests { assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 3); // Read at most 3 bytes from three buffers. - let bufs = &[ - IoSlice::new(&[3]), - IoSlice::new(&[4]), - IoSlice::new(&[5, 5]), - ]; + let bufs = &[IoSlice::new(&[3]), IoSlice::new(&[4]), IoSlice::new(&[5, 5])]; assert_poll_ok!(dst.as_mut().poll_write_vectored(&mut cx, bufs), 3); assert_eq!(dst.written, &[1, 2, 2, 3, 4, 5]); diff --git a/futures-util/src/lib.rs b/futures-util/src/lib.rs index 44823cc7da..2201e4f2c9 100644 --- a/futures-util/src/lib.rs +++ b/futures-util/src/lib.rs @@ -1,33 +1,24 @@ //! Combinators and utilities for working with `Future`s, `Stream`s, `Sink`s, //! and the `AsyncRead` and `AsyncWrite` traits. -#![cfg_attr(feature = "cfg-target-has-atomic", feature(cfg_target_has_atomic))] -#![cfg_attr(feature = "read-initializer", feature(read_initializer))] -#![cfg_attr(feature = "write-all-vectored", feature(io_slice_advance))] -#![cfg_attr(not(feature = "std"), no_std)] -#![warn( - missing_docs, - missing_debug_implementations, - rust_2018_idioms, - unreachable_pub -)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, unsafe_op_in_unsafe_fn)] #![cfg_attr(docsrs, feature(doc_cfg))] -#[cfg(all(feature = "cfg-target-has-atomic", not(feature = "unstable")))] -compile_error!("The `cfg-target-has-atomic` feature requires the `unstable` feature as an explicit opt-in to unstable features"); - #[cfg(all(feature = "bilock", not(feature = "unstable")))] compile_error!("The `bilock` feature requires the `unstable` feature as an explicit opt-in to unstable features"); -#[cfg(all(feature = "read-initializer", not(feature = "unstable")))] -compile_error!("The `read-initializer` feature requires the `unstable` feature as an explicit opt-in to unstable features"); - #[cfg(feature = "alloc")] extern crate alloc; +#[cfg(feature = "std")] +extern crate std; // Macro re-exports pub use futures_core::ready; @@ -56,13 +47,6 @@ pub mod __private { } } -macro_rules! cfg_target_has_atomic { - ($($item:item)*) => {$( - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] - $item - )*}; -} - #[cfg(feature = "sink")] macro_rules! delegate_sink { ($field:ident, $item:ty) => { @@ -155,11 +139,6 @@ macro_rules! delegate_async_write { #[cfg(feature = "std")] macro_rules! delegate_async_read { ($field:ident) => { - #[cfg(feature = "read-initializer")] - unsafe fn initializer(&self) -> $crate::io::Initializer { - self.$field.initializer() - } - fn poll_read( self: core::pin::Pin<&mut Self>, cx: &mut core::task::Context<'_>, @@ -308,19 +287,19 @@ macro_rules! delegate_all { } pub mod future; -#[doc(hidden)] -pub use crate::future::{FutureExt, TryFutureExt}; +#[doc(no_inline)] +pub use crate::future::{Future, FutureExt, TryFuture, TryFutureExt}; pub mod stream; -#[doc(hidden)] -pub use crate::stream::{StreamExt, TryStreamExt}; +#[doc(no_inline)] +pub use crate::stream::{Stream, StreamExt, TryStream, TryStreamExt}; #[cfg(feature = "sink")] #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] pub mod sink; #[cfg(feature = "sink")] -#[doc(hidden)] -pub use crate::sink::SinkExt; +#[doc(no_inline)] +pub use crate::sink::{Sink, SinkExt}; pub mod task; @@ -336,11 +315,18 @@ pub mod compat; pub mod io; #[cfg(feature = "io")] #[cfg(feature = "std")] -#[doc(hidden)] -pub use crate::io::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; +#[doc(no_inline)] +pub use crate::io::{ + AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, AsyncWrite, + AsyncWriteExt, +}; #[cfg(feature = "alloc")] pub mod lock; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod abortable; + mod fns; mod unfold_state; diff --git a/futures-util/src/lock/bilock.rs b/futures-util/src/lock/bilock.rs index 600e16e421..a89678e05f 100644 --- a/futures-util/src/lock/bilock.rs +++ b/futures-util/src/lock/bilock.rs @@ -1,16 +1,16 @@ //! Futures-powered synchronization primitives. -#[cfg(feature = "bilock")] -use futures_core::future::Future; -use futures_core::task::{Context, Poll, Waker}; +use alloc::boxed::Box; +use alloc::sync::Arc; use core::cell::UnsafeCell; -use core::fmt; use core::ops::{Deref, DerefMut}; use core::pin::Pin; -use core::sync::atomic::AtomicUsize; +use core::sync::atomic::AtomicPtr; use core::sync::atomic::Ordering::SeqCst; -use alloc::boxed::Box; -use alloc::sync::Arc; +use core::{fmt, ptr}; +#[cfg(feature = "bilock")] +use futures_core::future::Future; +use futures_core::task::{Context, Poll, Waker}; /// A type of futures-powered synchronization primitive which is a mutex between /// two possible owners. @@ -41,7 +41,7 @@ pub struct BiLock { #[derive(Debug)] struct Inner { - state: AtomicUsize, + state: AtomicPtr, value: Option>, } @@ -62,7 +62,7 @@ impl BiLock { /// possible when `T` is `Unpin`. pub fn new(t: T) -> (Self, Self) { let arc = Arc::new(Inner { - state: AtomicUsize::new(0), + state: AtomicPtr::new(ptr::null_mut()), value: Some(UnsafeCell::new(t)), }); @@ -90,7 +90,8 @@ impl BiLock { pub fn poll_lock(&self, cx: &mut Context<'_>) -> Poll> { let mut waker = None; loop { - match self.arc.state.swap(1, SeqCst) { + let n = self.arc.state.swap(invalid_ptr(1), SeqCst); + match n as usize { // Woohoo, we grabbed the lock! 0 => return Poll::Ready(BiLockGuard { bilock: self }), @@ -99,18 +100,18 @@ impl BiLock { // A task was previously blocked on this lock, likely our task, // so we need to update that task. - n => unsafe { - let mut prev = Box::from_raw(n as *mut Waker); + _ => unsafe { + let mut prev = Box::from_raw(n); *prev = cx.waker().clone(); waker = Some(prev); - } + }, } // type ascription for safety's sake! - let me: Box = waker.take().unwrap_or_else(||Box::new(cx.waker().clone())); - let me = Box::into_raw(me) as usize; + let me: Box = waker.take().unwrap_or_else(|| Box::new(cx.waker().clone())); + let me = Box::into_raw(me); - match self.arc.state.compare_exchange(1, me, SeqCst, SeqCst) { + match self.arc.state.compare_exchange(invalid_ptr(1), me, SeqCst, SeqCst) { // The lock is still locked, but we've now parked ourselves, so // just report that we're scheduled to receive a notification. Ok(_) => return Poll::Pending, @@ -118,8 +119,8 @@ impl BiLock { // Oops, looks like the lock was unlocked after our swap above // and before the compare_exchange. Deallocate what we just // allocated and go through the loop again. - Err(0) => unsafe { - waker = Some(Box::from_raw(me as *mut Waker)); + Err(n) if n.is_null() => unsafe { + waker = Some(Box::from_raw(me)); }, // The top of this loop set the previous state to 1, so if we @@ -128,7 +129,7 @@ impl BiLock { // but we're trying to acquire the lock and there's only one // other reference of the lock, so it should be impossible for // that task to ever block itself. - Err(n) => panic!("invalid state: {}", n), + Err(n) => panic!("invalid state: {}", n as usize), } } } @@ -145,9 +146,12 @@ impl BiLock { #[cfg(feature = "bilock")] #[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] pub fn lock(&self) -> BiLockAcquire<'_, T> { - BiLockAcquire { - bilock: self, - } + BiLockAcquire { bilock: self } + } + + /// Returns `true` only if the other `BiLock` originated from the same call to `BiLock::new`. + pub fn is_pair_of(&self, other: &Self) -> bool { + Arc::ptr_eq(&self.arc, &other.arc) } /// Attempts to put the two "halves" of a `BiLock` back together and @@ -157,7 +161,7 @@ impl BiLock { where T: Unpin, { - if Arc::ptr_eq(&self.arc, &other.arc) { + if self.is_pair_of(&other) { drop(other); let inner = Arc::try_unwrap(self.arc) .ok() @@ -169,7 +173,8 @@ impl BiLock { } fn unlock(&self) { - match self.arc.state.swap(0, SeqCst) { + let n = self.arc.state.swap(ptr::null_mut(), SeqCst); + match n as usize { // we've locked the lock, shouldn't be possible for us to see an // unlocked lock. 0 => panic!("invalid unlocked state"), @@ -179,9 +184,9 @@ impl BiLock { // Another task has parked themselves on this lock, let's wake them // up as its now their turn. - n => unsafe { - Box::from_raw(n as *mut Waker).wake(); - } + _ => unsafe { + Box::from_raw(n).wake(); + }, } } } @@ -194,7 +199,7 @@ impl Inner { impl Drop for Inner { fn drop(&mut self) { - assert_eq!(self.state.load(SeqCst), 0); + assert!(self.state.load(SeqCst).is_null()); } } @@ -205,9 +210,7 @@ pub struct ReuniteError(pub BiLock, pub BiLock); impl fmt::Debug for ReuniteError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("ReuniteError") - .field(&"...") - .finish() + f.debug_tuple("ReuniteError").field(&"...").finish() } } @@ -231,6 +234,9 @@ pub struct BiLockGuard<'a, T> { bilock: &'a BiLock, } +// We allow parallel access to T via Deref, so Sync bound is also needed here. +unsafe impl Sync for BiLockGuard<'_, T> {} + impl Deref for BiLockGuard<'_, T> { type Target = T; fn deref(&self) -> &T { @@ -281,3 +287,12 @@ impl<'a, T> Future for BiLockAcquire<'a, T> { self.bilock.poll_lock(cx) } } + +// Based on core::ptr::invalid_mut. Equivalent to `addr as *mut T`, but is strict-provenance compatible. +#[allow(clippy::useless_transmute)] +#[inline] +fn invalid_ptr(addr: usize) -> *mut T { + // SAFETY: every valid integer is also a valid pointer (as long as you don't dereference that + // pointer). + unsafe { core::mem::transmute(addr) } +} diff --git a/futures-util/src/lock/mod.rs b/futures-util/src/lock/mod.rs index 071eef6f62..8ca0ff6255 100644 --- a/futures-util/src/lock/mod.rs +++ b/futures-util/src/lock/mod.rs @@ -3,20 +3,25 @@ //! This module is only available when the `std` or `alloc` feature of this //! library is activated, and it is activated by default. -cfg_target_has_atomic! { - #[cfg(feature = "std")] - mod mutex; - #[cfg(feature = "std")] - pub use self::mutex::{MappedMutexGuard, Mutex, MutexLockFuture, MutexGuard}; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(any(feature = "sink", feature = "io"))] +#[cfg(not(feature = "bilock"))] +pub(crate) use self::bilock::BiLock; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "bilock")] +#[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] +pub use self::bilock::{BiLock, BiLockAcquire, BiLockGuard, ReuniteError}; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "std")] +pub use self::mutex::{ + MappedMutexGuard, Mutex, MutexGuard, MutexLockFuture, OwnedMutexGuard, OwnedMutexLockFuture, +}; - #[cfg(any(feature = "bilock", feature = "sink", feature = "io"))] - #[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] - #[cfg_attr(not(feature = "bilock"), allow(unreachable_pub))] - mod bilock; - #[cfg(feature = "bilock")] - #[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] - pub use self::bilock::{BiLock, BiLockAcquire, BiLockGuard, ReuniteError}; - #[cfg(any(feature = "sink", feature = "io"))] - #[cfg(not(feature = "bilock"))] - pub(crate) use self::bilock::BiLock; -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(any(feature = "bilock", feature = "sink", feature = "io"))] +#[cfg_attr(docsrs, doc(cfg(feature = "bilock")))] +#[cfg_attr(not(feature = "bilock"), allow(unreachable_pub))] +mod bilock; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "std")] +mod mutex; diff --git a/futures-util/src/lock/mutex.rs b/futures-util/src/lock/mutex.rs index a78de6283c..c77048a786 100644 --- a/futures-util/src/lock/mutex.rs +++ b/futures-util/src/lock/mutex.rs @@ -1,13 +1,15 @@ -use futures_core::future::{FusedFuture, Future}; -use futures_core::task::{Context, Poll, Waker}; -use slab::Slab; -use std::{fmt, mem}; use std::cell::UnsafeCell; use std::marker::PhantomData; use std::ops::{Deref, DerefMut}; use std::pin::Pin; -use std::sync::Mutex as StdMutex; use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex as StdMutex}; +use std::{fmt, mem}; + +use slab::Slab; + +use futures_core::future::{FusedFuture, Future}; +use futures_core::task::{Context, Poll, Waker}; /// A futures-aware mutex. /// @@ -53,7 +55,7 @@ enum Waiter { impl Waiter { fn register(&mut self, waker: &Waker) { match self { - Self::Waiting(w) if waker.will_wake(w) => {}, + Self::Waiting(w) if waker.will_wake(w) => {} _ => *self = Self::Waiting(waker.clone()), } } @@ -61,12 +63,11 @@ impl Waiter { fn wake(&mut self) { match mem::replace(self, Self::Woken) { Self::Waiting(waker) => waker.wake(), - Self::Woken => {}, + Self::Woken => {} } } } -#[allow(clippy::identity_op)] // https://github.com/rust-lang/rust-clippy/issues/3445 const IS_LOCKED: usize = 1 << 0; const HAS_WAITERS: usize = 1 << 1; @@ -108,15 +109,32 @@ impl Mutex { } } + /// Attempt to acquire the lock immediately. + /// + /// If the lock is currently held, this will return `None`. + pub fn try_lock_owned(self: &Arc) -> Option> { + let old_state = self.state.fetch_or(IS_LOCKED, Ordering::Acquire); + if (old_state & IS_LOCKED) == 0 { + Some(OwnedMutexGuard { mutex: self.clone() }) + } else { + None + } + } + /// Acquire the lock asynchronously. /// /// This method returns a future that will resolve once the lock has been /// successfully acquired. pub fn lock(&self) -> MutexLockFuture<'_, T> { - MutexLockFuture { - mutex: Some(self), - wait_key: WAIT_KEY_NONE, - } + MutexLockFuture { mutex: Some(self), wait_key: WAIT_KEY_NONE } + } + + /// Acquire the lock asynchronously. + /// + /// This method returns a future that will resolve once the lock has been + /// successfully acquired. + pub fn lock_owned(self: Arc) -> OwnedMutexLockFuture { + OwnedMutexLockFuture { mutex: Some(self), wait_key: WAIT_KEY_NONE } } /// Returns a mutable reference to the underlying data. @@ -145,7 +163,7 @@ impl Mutex { if wait_key != WAIT_KEY_NONE { let mut waiters = self.waiters.lock().unwrap(); match waiters.remove(wait_key) { - Waiter::Waiting(_) => {}, + Waiter::Waiting(_) => {} Waiter::Woken => { // We were awoken, but then dropped before we could // wake up to acquire the lock. Wake up another @@ -177,7 +195,118 @@ impl Mutex { } // Sentinel for when no slot in the `Slab` has been dedicated to this object. -const WAIT_KEY_NONE: usize = usize::max_value(); +const WAIT_KEY_NONE: usize = usize::MAX; + +/// A future which resolves when the target mutex has been successfully acquired, owned version. +pub struct OwnedMutexLockFuture { + // `None` indicates that the mutex was successfully acquired. + mutex: Option>>, + wait_key: usize, +} + +impl fmt::Debug for OwnedMutexLockFuture { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OwnedMutexLockFuture") + .field("was_acquired", &self.mutex.is_none()) + .field("mutex", &self.mutex) + .field( + "wait_key", + &(if self.wait_key == WAIT_KEY_NONE { None } else { Some(self.wait_key) }), + ) + .finish() + } +} + +impl FusedFuture for OwnedMutexLockFuture { + fn is_terminated(&self) -> bool { + self.mutex.is_none() + } +} + +impl Future for OwnedMutexLockFuture { + type Output = OwnedMutexGuard; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + let mutex = this.mutex.as_ref().expect("polled OwnedMutexLockFuture after completion"); + + if let Some(lock) = mutex.try_lock_owned() { + mutex.remove_waker(this.wait_key, false); + this.mutex = None; + return Poll::Ready(lock); + } + + { + let mut waiters = mutex.waiters.lock().unwrap(); + if this.wait_key == WAIT_KEY_NONE { + this.wait_key = waiters.insert(Waiter::Waiting(cx.waker().clone())); + if waiters.len() == 1 { + mutex.state.fetch_or(HAS_WAITERS, Ordering::Relaxed); // released by mutex unlock + } + } else { + waiters[this.wait_key].register(cx.waker()); + } + } + + // Ensure that we haven't raced `MutexGuard::drop`'s unlock path by + // attempting to acquire the lock again. + if let Some(lock) = mutex.try_lock_owned() { + mutex.remove_waker(this.wait_key, false); + this.mutex = None; + return Poll::Ready(lock); + } + + Poll::Pending + } +} + +impl Drop for OwnedMutexLockFuture { + fn drop(&mut self) { + if let Some(mutex) = self.mutex.as_ref() { + // This future was dropped before it acquired the mutex. + // + // Remove ourselves from the map, waking up another waiter if we + // had been awoken to acquire the lock. + mutex.remove_waker(self.wait_key, true); + } + } +} + +/// An RAII guard returned by the `lock_owned` and `try_lock_owned` methods. +/// When this structure is dropped (falls out of scope), the lock will be +/// unlocked. +pub struct OwnedMutexGuard { + mutex: Arc>, +} + +impl fmt::Debug for OwnedMutexGuard { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("OwnedMutexGuard") + .field("value", &&**self) + .field("mutex", &self.mutex) + .finish() + } +} + +impl Drop for OwnedMutexGuard { + fn drop(&mut self) { + self.mutex.unlock() + } +} + +impl Deref for OwnedMutexGuard { + type Target = T; + fn deref(&self) -> &T { + unsafe { &*self.mutex.value.get() } + } +} + +impl DerefMut for OwnedMutexGuard { + fn deref_mut(&mut self) -> &mut T { + unsafe { &mut *self.mutex.value.get() } + } +} /// A future which resolves when the target mutex has been successfully acquired. pub struct MutexLockFuture<'a, T: ?Sized> { @@ -191,13 +320,10 @@ impl fmt::Debug for MutexLockFuture<'_, T> { f.debug_struct("MutexLockFuture") .field("was_acquired", &self.mutex.is_none()) .field("mutex", &self.mutex) - .field("wait_key", &( - if self.wait_key == WAIT_KEY_NONE { - None - } else { - Some(self.wait_key) - } - )) + .field( + "wait_key", + &(if self.wait_key == WAIT_KEY_NONE { None } else { Some(self.wait_key) }), + ) .finish() } } @@ -295,10 +421,7 @@ impl<'a, T: ?Sized> MutexGuard<'a, T> { impl fmt::Debug for MutexGuard<'_, T> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("MutexGuard") - .field("value", &&**self) - .field("mutex", &self.mutex) - .finish() + f.debug_struct("MutexGuard").field("value", &&**self).field("mutex", &self.mutex).finish() } } @@ -396,21 +519,39 @@ unsafe impl Sync for Mutex {} // It's safe to switch which thread the acquire is being attempted on so long as // `T` can be accessed on that thread. unsafe impl Send for MutexLockFuture<'_, T> {} + // doesn't have any interesting `&self` methods (only Debug) unsafe impl Sync for MutexLockFuture<'_, T> {} +// It's safe to switch which thread the acquire is being attempted on so long as +// `T` can be accessed on that thread. +unsafe impl Send for OwnedMutexLockFuture {} + +// doesn't have any interesting `&self` methods (only Debug) +unsafe impl Sync for OwnedMutexLockFuture {} + // Safe to send since we don't track any thread-specific details-- the inner // lock is essentially spinlock-equivalent (attempt to flip an atomic bool) unsafe impl Send for MutexGuard<'_, T> {} unsafe impl Sync for MutexGuard<'_, T> {} + +unsafe impl Send for OwnedMutexGuard {} +unsafe impl Sync for OwnedMutexGuard {} + unsafe impl Send for MappedMutexGuard<'_, T, U> {} unsafe impl Sync for MappedMutexGuard<'_, T, U> {} -#[test] -fn test_mutex_guard_debug_not_recurse() { - let mutex = Mutex::new(42); - let guard = mutex.try_lock().unwrap(); - let _ = format!("{:?}", guard); - let guard = MutexGuard::map(guard, |n| n); - let _ = format!("{:?}", guard); +#[cfg(test)] +mod tests { + use super::*; + use std::format; + + #[test] + fn test_mutex_guard_debug_not_recurse() { + let mutex = Mutex::new(42); + let guard = mutex.try_lock().unwrap(); + let _ = format!("{:?}", guard); + let guard = MutexGuard::map(guard, |n| n); + let _ = format!("{:?}", guard); + } } diff --git a/futures-util/src/sink/buffer.rs b/futures-util/src/sink/buffer.rs index 8c58f4f569..4aa6c36033 100644 --- a/futures-util/src/sink/buffer.rs +++ b/futures-util/src/sink/buffer.rs @@ -1,10 +1,10 @@ +use alloc::collections::VecDeque; +use core::pin::Pin; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; use futures_sink::Sink; use pin_project_lite::pin_project; -use core::pin::Pin; -use alloc::collections::VecDeque; pin_project! { /// Sink for the [`buffer`](super::SinkExt::buffer) method. @@ -22,19 +22,12 @@ pin_project! { impl, Item> Buffer { pub(super) fn new(sink: Si, capacity: usize) -> Self { - Self { - sink, - buf: VecDeque::with_capacity(capacity), - capacity, - } + Self { sink, buf: VecDeque::with_capacity(capacity), capacity } } delegate_access_inner!(sink, Si, ()); - fn try_empty_buffer( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn try_empty_buffer(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); ready!(this.sink.as_mut().poll_ready(cx))?; while let Some(item) = this.buf.pop_front() { @@ -48,7 +41,10 @@ impl, Item> Buffer { } // Forwarding impl of Stream from the underlying sink -impl Stream for Buffer where S: Sink + Stream { +impl Stream for Buffer +where + S: Sink + Stream, +{ type Item = S::Item; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { @@ -60,7 +56,10 @@ impl Stream for Buffer where S: Sink + Stream { } } -impl FusedStream for Buffer where S: Sink + FusedStream { +impl FusedStream for Buffer +where + S: Sink + FusedStream, +{ fn is_terminated(&self) -> bool { self.sink.is_terminated() } @@ -69,10 +68,7 @@ impl FusedStream for Buffer where S: Sink + FusedStream impl, Item> Sink for Buffer { type Error = Si::Error; - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.capacity == 0 { return self.project().sink.poll_ready(cx); } @@ -86,10 +82,7 @@ impl, Item> Sink for Buffer { } } - fn start_send( - self: Pin<&mut Self>, - item: Item, - ) -> Result<(), Self::Error> { + fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error> { if self.capacity == 0 { self.project().sink.start_send(item) } else { @@ -98,21 +91,13 @@ impl, Item> Sink for Buffer { } } - #[allow(clippy::debug_assert_with_mut_call)] - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().try_empty_buffer(cx))?; debug_assert!(self.buf.is_empty()); self.project().sink.poll_flush(cx) } - #[allow(clippy::debug_assert_with_mut_call)] - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().try_empty_buffer(cx))?; debug_assert!(self.buf.is_empty()); self.project().sink.poll_close(cx) diff --git a/futures-util/src/sink/close.rs b/futures-util/src/sink/close.rs index 4fc99f5d5b..43eea74b0f 100644 --- a/futures-util/src/sink/close.rs +++ b/futures-util/src/sink/close.rs @@ -19,20 +19,14 @@ impl Unpin for Close<'_, Si, Item> {} /// The sink itself is returned after closing is complete. impl<'a, Si: Sink + Unpin + ?Sized, Item> Close<'a, Si, Item> { pub(super) fn new(sink: &'a mut Si) -> Self { - Self { - sink, - _phantom: PhantomData, - } + Self { sink, _phantom: PhantomData } } } impl + Unpin + ?Sized, Item> Future for Close<'_, Si, Item> { type Output = Result<(), Si::Error>; - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.sink).poll_close(cx) } } diff --git a/futures-util/src/sink/drain.rs b/futures-util/src/sink/drain.rs index 33c5b3167c..1a5480c0d6 100644 --- a/futures-util/src/sink/drain.rs +++ b/futures-util/src/sink/drain.rs @@ -32,34 +32,28 @@ pub fn drain() -> Drain { impl Unpin for Drain {} +impl Clone for Drain { + fn clone(&self) -> Self { + drain() + } +} + impl Sink for Drain { type Error = Never; - fn poll_ready( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn start_send( - self: Pin<&mut Self>, - _item: T, - ) -> Result<(), Self::Error> { + fn start_send(self: Pin<&mut Self>, _item: T) -> Result<(), Self::Error> { Ok(()) } - fn poll_flush( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn poll_close( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } } diff --git a/futures-util/src/sink/err_into.rs b/futures-util/src/sink/err_into.rs index 3eb994041f..a64d1337ba 100644 --- a/futures-util/src/sink/err_into.rs +++ b/futures-util/src/sink/err_into.rs @@ -1,6 +1,6 @@ use crate::sink::{SinkExt, SinkMapErr}; -use futures_core::stream::{Stream, FusedStream}; -use futures_sink::{Sink}; +use futures_core::stream::{FusedStream, Stream}; +use futures_sink::Sink; use pin_project_lite::pin_project; pin_project! { @@ -14,21 +14,21 @@ pin_project! { } impl SinkErrInto - where Si: Sink, - Si::Error: Into, +where + Si: Sink, + Si::Error: Into, { pub(super) fn new(sink: Si) -> Self { - Self { - sink: SinkExt::sink_map_err(sink, Into::into), - } + Self { sink: SinkExt::sink_map_err(sink, Into::into) } } delegate_access_inner!(sink, Si, (.)); } impl Sink for SinkErrInto - where Si: Sink, - Si::Error: Into, +where + Si: Sink, + Si::Error: Into, { type Error = E; @@ -37,8 +37,9 @@ impl Sink for SinkErrInto // Forwarding impl of Stream from the underlying sink impl Stream for SinkErrInto - where S: Sink + Stream, - S::Error: Into +where + S: Sink + Stream, + S::Error: Into, { type Item = S::Item; @@ -46,8 +47,9 @@ impl Stream for SinkErrInto } impl FusedStream for SinkErrInto - where S: Sink + FusedStream, - S::Error: Into +where + S: Sink + FusedStream, + S::Error: Into, { fn is_terminated(&self) -> bool { self.sink.is_terminated() diff --git a/futures-util/src/sink/fanout.rs b/futures-util/src/sink/fanout.rs index f351e867d4..fe2038f27f 100644 --- a/futures-util/src/sink/fanout.rs +++ b/futures-util/src/sink/fanout.rs @@ -50,36 +50,32 @@ impl Fanout { impl Debug for Fanout { fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult { - f.debug_struct("Fanout") - .field("sink1", &self.sink1) - .field("sink2", &self.sink2) - .finish() + f.debug_struct("Fanout").field("sink1", &self.sink1).field("sink2", &self.sink2).finish() } } impl Sink for Fanout - where Si1: Sink, - Item: Clone, - Si2: Sink +where + Si1: Sink, + Item: Clone, + Si2: Sink, { type Error = Si1::Error; - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); let sink1_ready = this.sink1.poll_ready(cx)?.is_ready(); let sink2_ready = this.sink2.poll_ready(cx)?.is_ready(); let ready = sink1_ready && sink2_ready; - if ready { Poll::Ready(Ok(())) } else { Poll::Pending } + if ready { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } } - fn start_send( - self: Pin<&mut Self>, - item: Item, - ) -> Result<(), Self::Error> { + fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error> { let this = self.project(); this.sink1.start_send(item.clone())?; @@ -87,27 +83,29 @@ impl Sink for Fanout Ok(()) } - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); let sink1_ready = this.sink1.poll_flush(cx)?.is_ready(); let sink2_ready = this.sink2.poll_flush(cx)?.is_ready(); let ready = sink1_ready && sink2_ready; - if ready { Poll::Ready(Ok(())) } else { Poll::Pending } + if ready { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } } - fn poll_close( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); let sink1_ready = this.sink1.poll_close(cx)?.is_ready(); let sink2_ready = this.sink2.poll_close(cx)?.is_ready(); let ready = sink1_ready && sink2_ready; - if ready { Poll::Ready(Ok(())) } else { Poll::Pending } + if ready { + Poll::Ready(Ok(())) + } else { + Poll::Pending + } } } diff --git a/futures-util/src/sink/feed.rs b/futures-util/src/sink/feed.rs index 06df9a91a8..6701f7a1b4 100644 --- a/futures-util/src/sink/feed.rs +++ b/futures-util/src/sink/feed.rs @@ -17,10 +17,7 @@ impl Unpin for Feed<'_, Si, Item> {} impl<'a, Si: Sink + Unpin + ?Sized, Item> Feed<'a, Si, Item> { pub(super) fn new(sink: &'a mut Si, item: Item) -> Self { - Feed { - sink, - item: Some(item), - } + Feed { sink, item: Some(item) } } pub(super) fn sink_pin_mut(&mut self) -> Pin<&mut Si> { @@ -35,10 +32,7 @@ impl<'a, Si: Sink + Unpin + ?Sized, Item> Feed<'a, Si, Item> { impl + Unpin + ?Sized, Item> Future for Feed<'_, Si, Item> { type Output = Result<(), Si::Error>; - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); let mut sink = Pin::new(&mut this.sink); ready!(sink.as_mut().poll_ready(cx))?; diff --git a/futures-util/src/sink/flush.rs b/futures-util/src/sink/flush.rs index c06a22185e..35a8372de7 100644 --- a/futures-util/src/sink/flush.rs +++ b/futures-util/src/sink/flush.rs @@ -23,20 +23,14 @@ impl Unpin for Flush<'_, Si, Item> {} /// all current requests are processed. impl<'a, Si: Sink + Unpin + ?Sized, Item> Flush<'a, Si, Item> { pub(super) fn new(sink: &'a mut Si) -> Self { - Self { - sink, - _phantom: PhantomData, - } + Self { sink, _phantom: PhantomData } } } impl + Unpin + ?Sized, Item> Future for Flush<'_, Si, Item> { type Output = Result<(), Si::Error>; - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.sink).poll_flush(cx) } } diff --git a/futures-util/src/sink/map_err.rs b/futures-util/src/sink/map_err.rs index 282934465e..9d2ab7b24b 100644 --- a/futures-util/src/sink/map_err.rs +++ b/futures-util/src/sink/map_err.rs @@ -1,7 +1,7 @@ use core::pin::Pin; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; -use futures_sink::{Sink}; +use futures_sink::Sink; use pin_project_lite::pin_project; pin_project! { @@ -28,36 +28,25 @@ impl SinkMapErr { } impl Sink for SinkMapErr - where Si: Sink, - F: FnOnce(Si::Error) -> E, +where + Si: Sink, + F: FnOnce(Si::Error) -> E, { type Error = E; - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.as_mut().project().sink.poll_ready(cx).map_err(|e| self.as_mut().take_f()(e)) } - fn start_send( - mut self: Pin<&mut Self>, - item: Item, - ) -> Result<(), Self::Error> { + fn start_send(mut self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error> { self.as_mut().project().sink.start_send(item).map_err(|e| self.as_mut().take_f()(e)) } - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.as_mut().project().sink.poll_flush(cx).map_err(|e| self.as_mut().take_f()(e)) } - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.as_mut().project().sink.poll_close(cx).map_err(|e| self.as_mut().take_f()(e)) } } diff --git a/futures-util/src/sink/mod.rs b/futures-util/src/sink/mod.rs index e5b515b64a..147e9adc93 100644 --- a/futures-util/src/sink/mod.rs +++ b/futures-util/src/sink/mod.rs @@ -243,7 +243,8 @@ pub trait SinkExt: Sink { /// This future will drive the stream to keep producing items until it is /// exhausted, sending each item to the sink. It will complete once both the /// stream is exhausted, the sink has received all items, and the sink has - /// been flushed. Note that the sink is **not** closed. + /// been flushed. Note that the sink is **not** closed. If the stream produces + /// an error, that error will be returned by this future without flushing the sink. /// /// Doing `sink.send_all(stream)` is roughly equivalent to /// `stream.forward(sink)`. The returned future will exhaust all items from diff --git a/futures-util/src/sink/send.rs b/futures-util/src/sink/send.rs index 384c22c56c..6d21f33fe4 100644 --- a/futures-util/src/sink/send.rs +++ b/futures-util/src/sink/send.rs @@ -17,19 +17,14 @@ impl Unpin for Send<'_, Si, Item> {} impl<'a, Si: Sink + Unpin + ?Sized, Item> Send<'a, Si, Item> { pub(super) fn new(sink: &'a mut Si, item: Item) -> Self { - Self { - feed: Feed::new(sink, item), - } + Self { feed: Feed::new(sink, item) } } } impl + Unpin + ?Sized, Item> Future for Send<'_, Si, Item> { type Output = Result<(), Si::Error>; - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = &mut *self; if this.feed.is_item_pending() { diff --git a/futures-util/src/sink/send_all.rs b/futures-util/src/sink/send_all.rs index 6a33459be0..1302dd2148 100644 --- a/futures-util/src/sink/send_all.rs +++ b/futures-util/src/sink/send_all.rs @@ -1,9 +1,9 @@ -use crate::stream::{StreamExt, TryStreamExt, Fuse}; +use crate::stream::{Fuse, StreamExt, TryStreamExt}; use core::fmt; use core::pin::Pin; use futures_core::future::Future; use futures_core::ready; -use futures_core::stream::{TryStream, Stream}; +use futures_core::stream::{Stream, TryStream}; use futures_core::task::{Context, Poll}; use futures_sink::Sink; @@ -40,22 +40,16 @@ impl Unpin for SendAll<'_, Si, St> where Si: Unpin + ?Sized, St: TryStream + Unpin + ?Sized, -{} +{ +} impl<'a, Si, St, Ok, Error> SendAll<'a, Si, St> where Si: Sink + Unpin + ?Sized, St: TryStream + Stream + Unpin + ?Sized, { - pub(super) fn new( - sink: &'a mut Si, - stream: &'a mut St, - ) -> Self { - Self { - sink, - stream: stream.fuse(), - buffered: None, - } + pub(super) fn new(sink: &'a mut Si, stream: &'a mut St) -> Self { + Self { sink, stream: stream.fuse(), buffered: None } } fn try_start_send( @@ -65,9 +59,7 @@ where ) -> Poll> { debug_assert!(self.buffered.is_none()); match Pin::new(&mut self.sink).poll_ready(cx)? { - Poll::Ready(()) => { - Poll::Ready(Pin::new(&mut self.sink).start_send(item)) - } + Poll::Ready(()) => Poll::Ready(Pin::new(&mut self.sink).start_send(item)), Poll::Pending => { self.buffered = Some(item); Poll::Pending @@ -83,10 +75,7 @@ where { type Output = Result<(), Error>; - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = &mut *self; // If we've got an item buffered already, we need to write it to the // sink before we can do anything else @@ -96,16 +85,14 @@ where loop { match this.stream.try_poll_next_unpin(cx)? { - Poll::Ready(Some(item)) => { - ready!(this.try_start_send(cx, item))? - } + Poll::Ready(Some(item)) => ready!(this.try_start_send(cx, item))?, Poll::Ready(None) => { ready!(Pin::new(&mut this.sink).poll_flush(cx))?; - return Poll::Ready(Ok(())) + return Poll::Ready(Ok(())); } Poll::Pending => { ready!(Pin::new(&mut this.sink).poll_flush(cx))?; - return Poll::Pending + return Poll::Pending; } } } diff --git a/futures-util/src/sink/unfold.rs b/futures-util/src/sink/unfold.rs index 3903716837..dea1307b66 100644 --- a/futures-util/src/sink/unfold.rs +++ b/futures-util/src/sink/unfold.rs @@ -41,10 +41,7 @@ where F: FnMut(T, Item) -> R, R: Future>, { - assert_sink::(Unfold { - function, - state: UnfoldState::Value { value: init }, - }) + assert_sink::(Unfold { function, state: UnfoldState::Value { value: init } }) } impl Sink for Unfold @@ -76,7 +73,10 @@ where this.state.set(UnfoldState::Value { value: state }); Ok(()) } - Err(err) => Err(err), + Err(err) => { + this.state.set(UnfoldState::Empty); + Err(err) + } } } else { Ok(()) diff --git a/futures-util/src/sink/with.rs b/futures-util/src/sink/with.rs index 73b87b72fc..86d3dcc7b8 100644 --- a/futures-util/src/sink/with.rs +++ b/futures-util/src/sink/with.rs @@ -27,29 +27,22 @@ where Fut: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("With") - .field("sink", &self.sink) - .field("state", &self.state) - .finish() + f.debug_struct("With").field("sink", &self.sink).field("state", &self.state).finish() } } impl With -where Si: Sink, - F: FnMut(U) -> Fut, - Fut: Future, +where + Si: Sink, + F: FnMut(U) -> Fut, + Fut: Future, { pub(super) fn new(sink: Si, f: F) -> Self - where - Fut: Future>, - E: From, + where + Fut: Future>, + E: From, { - Self { - state: None, - sink, - f, - _phantom: PhantomData, - } + Self { state: None, sink, f, _phantom: PhantomData } } } @@ -71,9 +64,10 @@ where // Forwarding impl of Stream from the underlying sink impl Stream for With - where S: Stream + Sink, - F: FnMut(U) -> Fut, - Fut: Future +where + S: Stream + Sink, + F: FnMut(U) -> Fut, + Fut: Future, { type Item = S::Item; @@ -81,18 +75,16 @@ impl Stream for With } impl With - where Si: Sink, - F: FnMut(U) -> Fut, - Fut: Future>, - E: From, +where + Si: Sink, + F: FnMut(U) -> Fut, + Fut: Future>, + E: From, { delegate_access_inner!(sink, Si, ()); /// Completes the processing of previous item if any. - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); let item = match this.state.as_mut().as_pin_mut() { @@ -106,26 +98,21 @@ impl With } impl Sink for With - where Si: Sink, - F: FnMut(U) -> Fut, - Fut: Future>, - E: From, +where + Si: Sink, + F: FnMut(U) -> Fut, + Fut: Future>, + E: From, { type Error = E; - fn poll_ready( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().poll(cx))?; ready!(self.project().sink.poll_ready(cx)?); Poll::Ready(Ok(())) } - fn start_send( - self: Pin<&mut Self>, - item: U, - ) -> Result<(), Self::Error> { + fn start_send(self: Pin<&mut Self>, item: U) -> Result<(), Self::Error> { let mut this = self.project(); assert!(this.state.is_none()); @@ -133,19 +120,13 @@ impl Sink for With Ok(()) } - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().poll(cx))?; ready!(self.project().sink.poll_flush(cx)?); Poll::Ready(Ok(())) } - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().poll(cx))?; ready!(self.project().sink.poll_close(cx)?); Poll::Ready(Ok(())) diff --git a/futures-util/src/sink/with_flat_map.rs b/futures-util/src/sink/with_flat_map.rs index 4b8d3a275c..2ae877a24b 100644 --- a/futures-util/src/sink/with_flat_map.rs +++ b/futures-util/src/sink/with_flat_map.rs @@ -2,7 +2,7 @@ use core::fmt; use core::marker::PhantomData; use core::pin::Pin; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; use futures_sink::Sink; use pin_project_lite::pin_project; @@ -43,21 +43,12 @@ where St: Stream>, { pub(super) fn new(sink: Si, f: F) -> Self { - Self { - sink, - f, - stream: None, - buffer: None, - _marker: PhantomData, - } + Self { sink, f, stream: None, buffer: None, _marker: PhantomData } } delegate_access_inner!(sink, Si, ()); - fn try_empty_stream( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn try_empty_stream(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); if this.buffer.is_some() { @@ -112,17 +103,11 @@ where { type Error = Si::Error; - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.try_empty_stream(cx) } - fn start_send( - self: Pin<&mut Self>, - item: U, - ) -> Result<(), Self::Error> { + fn start_send(self: Pin<&mut Self>, item: U) -> Result<(), Self::Error> { let mut this = self.project(); assert!(this.stream.is_none()); @@ -130,18 +115,12 @@ where Ok(()) } - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().try_empty_stream(cx)?); self.project().sink.poll_flush(cx) } - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(self.as_mut().try_empty_stream(cx)?); self.project().sink.poll_close(cx) } diff --git a/futures-util/src/stream/abortable.rs b/futures-util/src/stream/abortable.rs new file mode 100644 index 0000000000..1fea895822 --- /dev/null +++ b/futures-util/src/stream/abortable.rs @@ -0,0 +1,19 @@ +use super::assert_stream; +use crate::stream::{AbortHandle, Abortable}; +use crate::Stream; + +/// Creates a new `Abortable` stream and an `AbortHandle` which can be used to stop it. +/// +/// This function is a convenient (but less flexible) alternative to calling +/// `AbortHandle::new` and `Abortable::new` manually. +/// +/// This function is only available when the `std` or `alloc` feature of this +/// library is activated, and it is activated by default. +pub fn abortable(stream: St) -> (Abortable, AbortHandle) +where + St: Stream, +{ + let (handle, reg) = AbortHandle::new_pair(); + let abortable = assert_stream::(Abortable::new(stream, reg)); + (abortable, handle) +} diff --git a/futures-util/src/stream/empty.rs b/futures-util/src/stream/empty.rs index c629a4b7fc..e4fd87326b 100644 --- a/futures-util/src/stream/empty.rs +++ b/futures-util/src/stream/empty.rs @@ -8,16 +8,14 @@ use futures_core::task::{Context, Poll}; #[derive(Debug)] #[must_use = "streams do nothing unless polled"] pub struct Empty { - _phantom: PhantomData + _phantom: PhantomData, } /// Creates a stream which contains no elements. /// /// The returned stream will always return `Ready(None)` when polled. pub fn empty() -> Empty { - assert_stream::(Empty { - _phantom: PhantomData - }) + assert_stream::(Empty { _phantom: PhantomData }) } impl Unpin for Empty {} diff --git a/futures-util/src/stream/futures_ordered.rs b/futures-util/src/stream/futures_ordered.rs index eda3b27038..2cc144e81d 100644 --- a/futures-util/src/stream/futures_ordered.rs +++ b/futures-util/src/stream/futures_ordered.rs @@ -19,7 +19,8 @@ pin_project! { struct OrderWrapper { #[pin] data: T, // A future or a future's output - index: usize, + // Use i64 for index since isize may overflow in 32-bit targets. + index: i64, } } @@ -52,45 +53,45 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let index = self.index; - self.project().data.poll(cx).map(|output| OrderWrapper { - data: output, - index, - }) + self.project().data.poll(cx).map(|output| OrderWrapper { data: output, index }) } } /// An unbounded queue of futures. /// -/// This "combinator" is similar to `FuturesUnordered`, but it imposes an order -/// on top of the set of futures. While futures in the set will race to +/// This "combinator" is similar to [`FuturesUnordered`], but it imposes a FIFO +/// order on top of the set of futures. While futures in the set will race to /// completion in parallel, results will only be returned in the order their /// originating futures were added to the queue. /// /// Futures are pushed into this queue and their realized values are yielded in /// order. This structure is optimized to manage a large number of futures. -/// Futures managed by `FuturesOrdered` will only be polled when they generate +/// Futures managed by [`FuturesOrdered`] will only be polled when they generate /// notifications. This reduces the required amount of work needed to coordinate /// large numbers of futures. /// -/// When a `FuturesOrdered` is first created, it does not contain any futures. -/// Calling `poll` in this state will result in `Poll::Ready(None))` to be -/// returned. Futures are submitted to the queue using `push`; however, the -/// future will **not** be polled at this point. `FuturesOrdered` will only -/// poll managed futures when `FuturesOrdered::poll` is called. As such, it -/// is important to call `poll` after pushing new futures. +/// When a [`FuturesOrdered`] is first created, it does not contain any futures. +/// Calling [`poll_next`](FuturesOrdered::poll_next) in this state will result +/// in [`Poll::Ready(None)`](Poll::Ready) to be returned. Futures are submitted +/// to the queue using [`push_back`](FuturesOrdered::push_back) (or +/// [`push_front`](FuturesOrdered::push_front)); however, the future will +/// **not** be polled at this point. [`FuturesOrdered`] will only poll managed +/// futures when [`FuturesOrdered::poll_next`] is called. As such, it +/// is important to call [`poll_next`](FuturesOrdered::poll_next) after pushing +/// new futures. /// -/// If `FuturesOrdered::poll` returns `Poll::Ready(None)` this means that -/// the queue is currently not managing any futures. A future may be submitted -/// to the queue at a later time. At that point, a call to -/// `FuturesOrdered::poll` will either return the future's resolved value -/// **or** `Poll::Pending` if the future has not yet completed. When -/// multiple futures are submitted to the queue, `FuturesOrdered::poll` will -/// return `Poll::Pending` until the first future completes, even if +/// If [`FuturesOrdered::poll_next`] returns [`Poll::Ready(None)`](Poll::Ready) +/// this means that the queue is currently not managing any futures. A future +/// may be submitted to the queue at a later time. At that point, a call to +/// [`FuturesOrdered::poll_next`] will either return the future's resolved value +/// **or** [`Poll::Pending`] if the future has not yet completed. When +/// multiple futures are submitted to the queue, [`FuturesOrdered::poll_next`] +/// will return [`Poll::Pending`] until the first future completes, even if /// some of the later futures have already completed. /// -/// Note that you can create a ready-made `FuturesOrdered` via the +/// Note that you can create a ready-made [`FuturesOrdered`] via the /// [`collect`](Iterator::collect) method, or you can start with an empty queue -/// with the `FuturesOrdered::new` constructor. +/// with the [`FuturesOrdered::new`] constructor. /// /// This type is only available when the `std` or `alloc` feature of this /// library is activated, and it is activated by default. @@ -98,8 +99,8 @@ where pub struct FuturesOrdered { in_progress_queue: FuturesUnordered>, queued_outputs: BinaryHeap>, - next_incoming_index: usize, - next_outgoing_index: usize, + next_incoming_index: i64, + next_outgoing_index: i64, } impl Unpin for FuturesOrdered {} @@ -107,8 +108,9 @@ impl Unpin for FuturesOrdered {} impl FuturesOrdered { /// Constructs a new, empty `FuturesOrdered` /// - /// The returned `FuturesOrdered` does not contain any futures and, in this - /// state, `FuturesOrdered::poll_next` will return `Poll::Ready(None)`. + /// The returned [`FuturesOrdered`] does not contain any futures and, in + /// this state, [`FuturesOrdered::poll_next`] will return + /// [`Poll::Ready(None)`](Poll::Ready). pub fn new() -> Self { Self { in_progress_queue: FuturesUnordered::new(), @@ -135,17 +137,38 @@ impl FuturesOrdered { /// Push a future into the queue. /// /// This function submits the given future to the internal set for managing. - /// This function will not call `poll` on the submitted future. The caller - /// must ensure that `FuturesOrdered::poll` is called in order to receive - /// task notifications. + /// This function will not call [`poll`](Future::poll) on the submitted + /// future. The caller must ensure that [`FuturesOrdered::poll_next`] is + /// called in order to receive task notifications. + #[deprecated(note = "use `push_back` instead")] pub fn push(&mut self, future: Fut) { - let wrapped = OrderWrapper { - data: future, - index: self.next_incoming_index, - }; + self.push_back(future); + } + + /// Pushes a future to the back of the queue. + /// + /// This function submits the given future to the internal set for managing. + /// This function will not call [`poll`](Future::poll) on the submitted + /// future. The caller must ensure that [`FuturesOrdered::poll_next`] is + /// called in order to receive task notifications. + pub fn push_back(&mut self, future: Fut) { + let wrapped = OrderWrapper { data: future, index: self.next_incoming_index }; self.next_incoming_index += 1; self.in_progress_queue.push(wrapped); } + + /// Pushes a future to the front of the queue. + /// + /// This function submits the given future to the internal set for managing. + /// This function will not call [`poll`](Future::poll) on the submitted + /// future. The caller must ensure that [`FuturesOrdered::poll_next`] is + /// called in order to receive task notifications. This future will be + /// the next future to be returned complete. + pub fn push_front(&mut self, future: Fut) { + let wrapped = OrderWrapper { data: future, index: self.next_outgoing_index - 1 }; + self.next_outgoing_index -= 1; + self.in_progress_queue.push(wrapped); + } } impl Default for FuturesOrdered { @@ -202,7 +225,7 @@ impl FromIterator for FuturesOrdered { { let acc = Self::new(); iter.into_iter().fold(acc, |mut acc, item| { - acc.push(item); + acc.push_back(item); acc }) } @@ -220,7 +243,7 @@ impl Extend for FuturesOrdered { I: IntoIterator, { for item in iter { - self.push(item); + self.push_back(item); } } } diff --git a/futures-util/src/stream/futures_unordered/iter.rs b/futures-util/src/stream/futures_unordered/iter.rs index ef7b15aed8..20248c70fe 100644 --- a/futures-util/src/stream/futures_unordered/iter.rs +++ b/futures-util/src/stream/futures_unordered/iter.rs @@ -1,41 +1,87 @@ -use super::FuturesUnordered; use super::task::Task; +use super::FuturesUnordered; use core::marker::PhantomData; use core::pin::Pin; +use core::ptr; use core::sync::atomic::Ordering::Relaxed; -#[derive(Debug)] /// Mutable iterator over all futures in the unordered set. +#[derive(Debug)] pub struct IterPinMut<'a, Fut> { pub(super) task: *const Task, pub(super) len: usize, - pub(super) _marker: PhantomData<&'a mut FuturesUnordered> + pub(super) _marker: PhantomData<&'a mut FuturesUnordered>, } -#[derive(Debug)] /// Mutable iterator over all futures in the unordered set. -pub struct IterMut<'a, Fut: Unpin> (pub(super) IterPinMut<'a, Fut>); - #[derive(Debug)] +pub struct IterMut<'a, Fut: Unpin>(pub(super) IterPinMut<'a, Fut>); + /// Immutable iterator over all futures in the unordered set. +#[derive(Debug)] pub struct IterPinRef<'a, Fut> { pub(super) task: *const Task, pub(super) len: usize, pub(super) pending_next_all: *mut Task, - pub(super) _marker: PhantomData<&'a FuturesUnordered> + pub(super) _marker: PhantomData<&'a FuturesUnordered>, } -#[derive(Debug)] /// Immutable iterator over all the futures in the unordered set. -pub struct Iter<'a, Fut: Unpin> (pub(super) IterPinRef<'a, Fut>); +#[derive(Debug)] +pub struct Iter<'a, Fut: Unpin>(pub(super) IterPinRef<'a, Fut>); + +/// Owned iterator over all futures in the unordered set. +#[derive(Debug)] +pub struct IntoIter { + pub(super) len: usize, + pub(super) inner: FuturesUnordered, +} + +impl Iterator for IntoIter { + type Item = Fut; + + fn next(&mut self) -> Option { + // `head_all` can be accessed directly and we don't need to spin on + // `Task::next_all` since we have exclusive access to the set. + let task = self.inner.head_all.get_mut(); + + if (*task).is_null() { + return None; + } + + unsafe { + // Moving out of the future is safe because it is `Unpin` + let future = (*(**task).future.get()).take().unwrap(); + + // Mutable access to a previously shared `FuturesUnordered` implies + // that the other threads already released the object before the + // current thread acquired it, so relaxed ordering can be used and + // valid `next_all` checks can be skipped. + let next = (**task).next_all.load(Relaxed); + *task = next; + if !task.is_null() { + *(**task).prev_all.get() = ptr::null_mut(); + } + self.len -= 1; + Some(future) + } + } + + fn size_hint(&self) -> (usize, Option) { + (self.len, Some(self.len)) + } +} + +impl ExactSizeIterator for IntoIter {} impl<'a, Fut> Iterator for IterPinMut<'a, Fut> { type Item = Pin<&'a mut Fut>; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option { if self.task.is_null() { return None; } + unsafe { let future = (*(*self.task).future.get()).as_mut().unwrap(); @@ -60,7 +106,7 @@ impl ExactSizeIterator for IterPinMut<'_, Fut> {} impl<'a, Fut: Unpin> Iterator for IterMut<'a, Fut> { type Item = &'a mut Fut; - fn next(&mut self) -> Option<&'a mut Fut> { + fn next(&mut self) -> Option { self.0.next().map(Pin::get_mut) } @@ -74,10 +120,11 @@ impl ExactSizeIterator for IterMut<'_, Fut> {} impl<'a, Fut> Iterator for IterPinRef<'a, Fut> { type Item = Pin<&'a Fut>; - fn next(&mut self) -> Option> { + fn next(&mut self) -> Option { if self.task.is_null() { return None; } + unsafe { let future = (*(*self.task).future.get()).as_ref().unwrap(); @@ -85,10 +132,7 @@ impl<'a, Fut> Iterator for IterPinRef<'a, Fut> { // `head_all` was initially read for this iterator implies acquire // ordering for all previously inserted nodes (and we don't need to // read `len_all` again for any other nodes). - let next = (*self.task).spin_next_all( - self.pending_next_all, - Relaxed, - ); + let next = (*self.task).spin_next_all(self.pending_next_all, Relaxed); self.task = next; self.len -= 1; Some(Pin::new_unchecked(future)) @@ -105,7 +149,7 @@ impl ExactSizeIterator for IterPinRef<'_, Fut> {} impl<'a, Fut: Unpin> Iterator for Iter<'a, Fut> { type Item = &'a Fut; - fn next(&mut self) -> Option<&'a Fut> { + fn next(&mut self) -> Option { self.0.next().map(Pin::get_ref) } @@ -115,3 +159,14 @@ impl<'a, Fut: Unpin> Iterator for Iter<'a, Fut> { } impl ExactSizeIterator for Iter<'_, Fut> {} + +// SAFETY: we do nothing thread-local and there is no interior mutability, +// so the usual structural `Send`/`Sync` apply. +unsafe impl Send for IterPinRef<'_, Fut> {} +unsafe impl Sync for IterPinRef<'_, Fut> {} + +unsafe impl Send for IterPinMut<'_, Fut> {} +unsafe impl Sync for IterPinMut<'_, Fut> {} + +unsafe impl Send for IntoIter {} +unsafe impl Sync for IntoIter {} diff --git a/futures-util/src/stream/futures_unordered/mod.rs b/futures-util/src/stream/futures_unordered/mod.rs index 8dcc551e9f..ca62b10b91 100644 --- a/futures-util/src/stream/futures_unordered/mod.rs +++ b/futures-util/src/stream/futures_unordered/mod.rs @@ -3,11 +3,8 @@ //! This module is only available when the `std` or `alloc` feature of this //! library is activated, and it is activated by default. -use futures_core::future::Future; -use futures_core::stream::{FusedStream, Stream}; -use futures_core::task::{Context, Poll}; -use futures_task::{FutureObj, LocalFutureObj, Spawn, LocalSpawn, SpawnError}; use crate::task::AtomicWaker; +use alloc::sync::{Arc, Weak}; use core::cell::UnsafeCell; use core::fmt::{self, Debug}; use core::iter::FromIterator; @@ -16,23 +13,29 @@ use core::mem; use core::pin::Pin; use core::ptr; use core::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release, SeqCst}; -use core::sync::atomic::{AtomicPtr, AtomicBool}; -use alloc::sync::{Arc, Weak}; +use core::sync::atomic::{AtomicBool, AtomicPtr}; +use futures_core::future::Future; +use futures_core::stream::{FusedStream, Stream}; +use futures_core::task::{Context, Poll}; +use futures_task::{FutureObj, LocalFutureObj, LocalSpawn, Spawn, SpawnError}; mod abort; mod iter; -pub use self::iter::{Iter, IterMut, IterPinMut, IterPinRef}; +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/102352 +pub use self::iter::{IntoIter, Iter, IterMut, IterPinMut, IterPinRef}; mod task; use self::task::Task; mod ready_to_run_queue; -use self::ready_to_run_queue::{ReadyToRunQueue, Dequeue}; - +use self::ready_to_run_queue::{Dequeue, ReadyToRunQueue}; /// A set of futures which may complete in any order. /// +/// See [`FuturesOrdered`](crate::stream::FuturesOrdered) for a version of this +/// type that preserves a FIFO order. +/// /// This structure is optimized to manage a large number of futures. /// Futures managed by [`FuturesUnordered`] will only be polled when they /// generate wake-up notifications. This reduces the required amount of work @@ -59,22 +62,18 @@ pub struct FuturesUnordered { } unsafe impl Send for FuturesUnordered {} -unsafe impl Sync for FuturesUnordered {} +unsafe impl Sync for FuturesUnordered {} impl Unpin for FuturesUnordered {} impl Spawn for FuturesUnordered> { - fn spawn_obj(&self, future_obj: FutureObj<'static, ()>) - -> Result<(), SpawnError> - { + fn spawn_obj(&self, future_obj: FutureObj<'static, ()>) -> Result<(), SpawnError> { self.push(future_obj); Ok(()) } } impl LocalSpawn for FuturesUnordered> { - fn spawn_local_obj(&self, future_obj: LocalFutureObj<'static, ()>) - -> Result<(), SpawnError> - { + fn spawn_local_obj(&self, future_obj: LocalFutureObj<'static, ()>) -> Result<(), SpawnError> { self.push(future_obj); Ok(()) } @@ -102,7 +101,7 @@ impl LocalSpawn for FuturesUnordered> { // Each task is wrapped in an `Arc` and thereby atomically reference counted. // Also, each task contains an `AtomicBool` which acts as a flag that indicates // whether the task is currently inserted in the atomic queue. When a wake-up -// notifiaction is received, the task will only be inserted into the ready to +// notification is received, the task will only be inserted into the ready to // run queue if it isn't inserted already. impl Default for FuturesUnordered { @@ -126,8 +125,9 @@ impl FuturesUnordered { next_ready_to_run: AtomicPtr::new(ptr::null_mut()), queued: AtomicBool::new(true), ready_to_run_queue: Weak::new(), + woken: AtomicBool::new(false), }); - let stub_ptr = &*stub as *const Task; + let stub_ptr = Arc::as_ptr(&stub); let ready_to_run_queue = Arc::new(ReadyToRunQueue { waker: AtomicWaker::new(), head: AtomicPtr::new(stub_ptr as *mut _), @@ -172,6 +172,7 @@ impl FuturesUnordered { next_ready_to_run: AtomicPtr::new(ptr::null_mut()), queued: AtomicBool::new(true), ready_to_run_queue: Arc::downgrade(&self.ready_to_run_queue), + woken: AtomicBool::new(false), }); // Reset the `is_terminated` flag if we've previously marked ourselves @@ -191,24 +192,26 @@ impl FuturesUnordered { } /// Returns an iterator that allows inspecting each future in the set. - pub fn iter(&self) -> Iter<'_, Fut> where Fut: Unpin { + pub fn iter(&self) -> Iter<'_, Fut> + where + Fut: Unpin, + { Iter(Pin::new(self).iter_pin_ref()) } /// Returns an iterator that allows inspecting each future in the set. - fn iter_pin_ref(self: Pin<&Self>) -> IterPinRef<'_, Fut> { + pub fn iter_pin_ref(self: Pin<&Self>) -> IterPinRef<'_, Fut> { let (task, len) = self.atomic_load_head_and_len_all(); + let pending_next_all = self.pending_next_all(); - IterPinRef { - task, - len, - pending_next_all: self.pending_next_all(), - _marker: PhantomData, - } + IterPinRef { task, len, pending_next_all, _marker: PhantomData } } /// Returns an iterator that allows modifying each future in the set. - pub fn iter_mut(&mut self) -> IterMut<'_, Fut> where Fut: Unpin { + pub fn iter_mut(&mut self) -> IterMut<'_, Fut> + where + Fut: Unpin, + { IterMut(Pin::new(self).iter_pin_mut()) } @@ -217,19 +220,9 @@ impl FuturesUnordered { // `head_all` can be accessed directly and we don't need to spin on // `Task::next_all` since we have exclusive access to the set. let task = *self.head_all.get_mut(); - let len = if task.is_null() { - 0 - } else { - unsafe { - *(*task).len_all.get() - } - }; + let len = if task.is_null() { 0 } else { unsafe { *(*task).len_all.get() } }; - IterPinMut { - task, - len, - _marker: PhantomData - } + IterPinMut { task, len, _marker: PhantomData } } /// Returns the current head node and number of futures in the list of all @@ -249,7 +242,7 @@ impl FuturesUnordered { (task, len) } - /// Releases the task. It destorys the future inside and either drops + /// Releases the task. It destroys the future inside and either drops /// the `Arc` or transfers ownership to the ready to run queue. /// The task this method is called on must have been unlinked before. fn release_task(&mut self, task: Arc>) { @@ -263,16 +256,6 @@ impl FuturesUnordered { // `wake` from doing any work in the future let prev = task.queued.swap(true, SeqCst); - // Drop the future, even if it hasn't finished yet. This is safe - // because we're dropping the future on the thread that owns - // `FuturesUnordered`, which correctly tracks `Fut`'s lifetimes and - // such. - unsafe { - // Set to `None` rather than `take()`ing to prevent moving the - // future. - *task.future.get() = None; - } - // If the queued flag was previously set, then it means that this task // is still in our internal ready to run queue. We then transfer // ownership of our reference count to the ready to run queue, and it'll @@ -284,8 +267,25 @@ impl FuturesUnordered { // enqueue the task, so our task will never see the ready to run queue // again. The task itself will be deallocated once all reference counts // have been dropped elsewhere by the various wakers that contain it. - if prev { - mem::forget(task); + // + // Use ManuallyDrop to transfer the reference count ownership before + // dropping the future so unwinding won't release the reference count. + let md_slot; + let task = if prev { + md_slot = mem::ManuallyDrop::new(task); + &*md_slot + } else { + &task + }; + + // Drop the future, even if it hasn't finished yet. This is safe + // because we're dropping the future on the thread that owns + // `FuturesUnordered`, which correctly tracks `Fut`'s lifetimes and + // such. + unsafe { + // Set to `None` rather than `take()`ing to prevent moving the + // future. + *task.future.get() = None; } } @@ -331,35 +331,37 @@ impl FuturesUnordered { /// This method is unsafe because it has be guaranteed that `task` is a /// valid pointer. unsafe fn unlink(&mut self, task: *const Task) -> Arc> { - // Compute the new list length now in case we're removing the head node - // and won't be able to retrieve the correct length later. - let head = *self.head_all.get_mut(); - debug_assert!(!head.is_null()); - let new_len = *(*head).len_all.get() - 1; - - let task = Arc::from_raw(task); - let next = task.next_all.load(Relaxed); - let prev = *task.prev_all.get(); - task.next_all.store(self.pending_next_all(), Relaxed); - *task.prev_all.get() = ptr::null_mut(); - - if !next.is_null() { - *(*next).prev_all.get() = prev; - } + unsafe { + // Compute the new list length now in case we're removing the head node + // and won't be able to retrieve the correct length later. + let head = *self.head_all.get_mut(); + debug_assert!(!head.is_null()); + let new_len = *(*head).len_all.get() - 1; + + let task = Arc::from_raw(task); + let next = task.next_all.load(Relaxed); + let prev = *task.prev_all.get(); + task.next_all.store(self.pending_next_all(), Relaxed); + *task.prev_all.get() = ptr::null_mut(); - if !prev.is_null() { - (*prev).next_all.store(next, Relaxed); - } else { - *self.head_all.get_mut() = next; - } + if !next.is_null() { + *(*next).prev_all.get() = prev; + } - // Store the new list length in the head node. - let head = *self.head_all.get_mut(); - if !head.is_null() { - *(*head).len_all.get() = new_len; - } + if !prev.is_null() { + (*prev).next_all.store(next, Relaxed); + } else { + *self.head_all.get_mut() = next; + } + + // Store the new list length in the head node. + let head = *self.head_all.get_mut(); + if !head.is_null() { + *(*head).len_all.get() = new_len; + } - task + task + } } /// Returns the reserved value for `Task::next_all` to indicate a pending @@ -388,35 +390,20 @@ impl FuturesUnordered { // The `ReadyToRunQueue` stub is never inserted into the `head_all` // list, and its pointer value will remain valid for the lifetime of // this `FuturesUnordered`, so we can make use of its value here. - &*self.ready_to_run_queue.stub as *const _ as *mut _ + Arc::as_ptr(&self.ready_to_run_queue.stub) as *mut _ } } impl Stream for FuturesUnordered { type Item = Fut::Output; - fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll> - { - // Variable to determine how many times it is allowed to poll underlying - // futures without yielding. - // - // A single call to `poll_next` may potentially do a lot of work before - // yielding. This happens in particular if the underlying futures are awoken - // frequently but continue to return `Pending`. This is problematic if other - // tasks are waiting on the executor, since they do not get to run. This value - // caps the number of calls to `poll` on underlying futures a single call to - // `poll_next` is allowed to make. - // - // The value is the length of FuturesUnordered. This ensures that each - // future is polled only once at most per iteration. - // - // See also https://github.com/rust-lang/futures-rs/issues/2047. - let yield_every = self.len(); + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let len = self.len(); // Keep track of how many child futures we have polled, // in case we want to forcibly yield. let mut polled = 0; + let mut yielded = 0; // Ensure `parent` is correctly set. self.ready_to_run_queue.waker.register(cx.waker()); @@ -469,14 +456,11 @@ impl Stream for FuturesUnordered { // Double check that the call to `release_task` really // happened. Calling it required the task to be unlinked. - debug_assert_eq!( - task.next_all.load(Relaxed), - self.pending_next_all() - ); + debug_assert_eq!(task.next_all.load(Relaxed), self.pending_next_all()); unsafe { debug_assert!((*task.prev_all.get()).is_null()); } - continue + continue; } }; @@ -516,10 +500,7 @@ impl Stream for FuturesUnordered { } } - let mut bomb = Bomb { - task: Some(task), - queue: &mut *self, - }; + let mut bomb = Bomb { task: Some(task), queue: &mut *self }; // Poll the underlying future with the appropriate waker // implementation. This is where a large bit of the unsafety @@ -533,7 +514,12 @@ impl Stream for FuturesUnordered { // the internal allocation, appropriately accessing fields and // deallocating the task if need be. let res = { - let waker = Task::waker_ref(bomb.task.as_ref().unwrap()); + let task = bomb.task.as_ref().unwrap(); + // We are only interested in whether the future is awoken before it + // finishes polling, so reset the flag here. + task.woken.store(false, Relaxed); + // SAFETY: see the comments of Bomb and this block. + let waker = unsafe { Task::waker_ref(task) }; let mut cx = Context::from_waker(&waker); // Safety: We won't move the future ever again @@ -546,20 +532,23 @@ impl Stream for FuturesUnordered { match res { Poll::Pending => { let task = bomb.task.take().unwrap(); + // If the future was awoken during polling, we assume + // the future wanted to explicitly yield. + yielded += task.woken.load(Relaxed) as usize; bomb.queue.link(task); - if polled == yield_every { - // We have polled a large number of futures in a row without yielding. - // To ensure we do not starve other tasks waiting on the executor, - // we yield here, but immediately wake ourselves up to continue. + // If a future yields, we respect it and yield here. + // If all futures have been polled, we also yield here to + // avoid starving other tasks waiting on the executor. + // (polling the same future twice per iteration may cause + // the problem: https://github.com/rust-lang/futures-rs/pull/2333) + if yielded >= 2 || polled == len { cx.waker().wake_by_ref(); return Poll::Pending; } - continue - } - Poll::Ready(output) => { - return Poll::Ready(Some(output)) + continue; } + Poll::Ready(output) => return Poll::Ready(Some(output)), } } } @@ -576,19 +565,36 @@ impl Debug for FuturesUnordered { } } +impl FuturesUnordered { + /// Clears the set, removing all futures. + pub fn clear(&mut self) { + *self = Self::new(); + } +} + impl Drop for FuturesUnordered { fn drop(&mut self) { + // Before the strong reference to the queue is dropped we need all + // futures to be dropped. See note at the bottom of this method. + // + // If there is a panic before this completes, we leak the queue. + struct LeakQueueOnDrop<'a, Fut>(&'a mut FuturesUnordered); + impl Drop for LeakQueueOnDrop<'_, Fut> { + fn drop(&mut self) { + mem::forget(Arc::clone(&self.0.ready_to_run_queue)); + } + } + let guard = LeakQueueOnDrop(self); // When a `FuturesUnordered` is dropped we want to drop all futures // associated with it. At the same time though there may be tons of // wakers flying around which contain `Task` references // inside them. We'll let those naturally get deallocated. - unsafe { - while !self.head_all.get_mut().is_null() { - let head = *self.head_all.get_mut(); - let task = self.unlink(head); - self.release_task(task); - } + while !guard.0.head_all.get_mut().is_null() { + let head = *guard.0.head_all.get_mut(); + let task = unsafe { guard.0.unlink(head) }; + guard.0.release_task(task); } + mem::forget(guard); // safe to release strong reference to queue // Note that at this point we could still have a bunch of tasks in the // ready to run queue. None of those tasks, however, have futures @@ -605,13 +611,48 @@ impl Drop for FuturesUnordered { } } +impl<'a, Fut: Unpin> IntoIterator for &'a FuturesUnordered { + type Item = &'a Fut; + type IntoIter = Iter<'a, Fut>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, Fut: Unpin> IntoIterator for &'a mut FuturesUnordered { + type Item = &'a mut Fut; + type IntoIter = IterMut<'a, Fut>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +impl IntoIterator for FuturesUnordered { + type Item = Fut; + type IntoIter = IntoIter; + + fn into_iter(mut self) -> Self::IntoIter { + // `head_all` can be accessed directly and we don't need to spin on + // `Task::next_all` since we have exclusive access to the set. + let task = *self.head_all.get_mut(); + let len = if task.is_null() { 0 } else { unsafe { *(*task).len_all.get() } }; + + IntoIter { len, inner: self } + } +} + impl FromIterator for FuturesUnordered { fn from_iter(iter: I) -> Self where I: IntoIterator, { let acc = Self::new(); - iter.into_iter().fold(acc, |acc, item| { acc.push(item); acc }) + iter.into_iter().fold(acc, |acc, item| { + acc.push(item); + acc + }) } } diff --git a/futures-util/src/stream/futures_unordered/ready_to_run_queue.rs b/futures-util/src/stream/futures_unordered/ready_to_run_queue.rs index 210519585e..6ffaf554dd 100644 --- a/futures-util/src/stream/futures_unordered/ready_to_run_queue.rs +++ b/futures-util/src/stream/futures_unordered/ready_to_run_queue.rs @@ -1,9 +1,9 @@ use crate::task::AtomicWaker; +use alloc::sync::Arc; use core::cell::UnsafeCell; use core::ptr; use core::sync::atomic::AtomicPtr; -use core::sync::atomic::Ordering::{Relaxed, Acquire, Release, AcqRel}; -use alloc::sync::Arc; +use core::sync::atomic::Ordering::{AcqRel, Acquire, Relaxed, Release}; use super::abort::abort; use super::task::Task; @@ -27,6 +27,8 @@ pub(super) struct ReadyToRunQueue { /// An MPSC queue into which the tasks containing the futures are inserted /// whenever the future inside is scheduled for polling. impl ReadyToRunQueue { + // FIXME: this takes raw pointer without safety conditions. + /// The enqueue function from the 1024cores intrusive MPSC queue algorithm. pub(super) fn enqueue(&self, task: *const Task) { unsafe { @@ -47,43 +49,45 @@ impl ReadyToRunQueue { /// Note that this is unsafe as it required mutual exclusion (only one /// thread can call this) to be guaranteed elsewhere. pub(super) unsafe fn dequeue(&self) -> Dequeue { - let mut tail = *self.tail.get(); - let mut next = (*tail).next_ready_to_run.load(Acquire); + unsafe { + let mut tail = *self.tail.get(); + let mut next = (*tail).next_ready_to_run.load(Acquire); - if tail == self.stub() { - if next.is_null() { - return Dequeue::Empty; + if tail == self.stub() { + if next.is_null() { + return Dequeue::Empty; + } + + *self.tail.get() = next; + tail = next; + next = (*next).next_ready_to_run.load(Acquire); } - *self.tail.get() = next; - tail = next; - next = (*next).next_ready_to_run.load(Acquire); - } + if !next.is_null() { + *self.tail.get() = next; + debug_assert!(tail != self.stub()); + return Dequeue::Data(tail); + } - if !next.is_null() { - *self.tail.get() = next; - debug_assert!(tail != self.stub()); - return Dequeue::Data(tail); - } + if self.head.load(Acquire) as *const _ != tail { + return Dequeue::Inconsistent; + } - if self.head.load(Acquire) as *const _ != tail { - return Dequeue::Inconsistent; - } + self.enqueue(self.stub()); - self.enqueue(self.stub()); + next = (*tail).next_ready_to_run.load(Acquire); - next = (*tail).next_ready_to_run.load(Acquire); + if !next.is_null() { + *self.tail.get() = next; + return Dequeue::Data(tail); + } - if !next.is_null() { - *self.tail.get() = next; - return Dequeue::Data(tail); + Dequeue::Inconsistent } - - Dequeue::Inconsistent } pub(super) fn stub(&self) -> *const Task { - &*self.stub + Arc::as_ptr(&self.stub) } } diff --git a/futures-util/src/stream/futures_unordered/task.rs b/futures-util/src/stream/futures_unordered/task.rs index 261408f01c..2ae4cb7d85 100644 --- a/futures-util/src/stream/futures_unordered/task.rs +++ b/futures-util/src/stream/futures_unordered/task.rs @@ -1,11 +1,11 @@ -use core::cell::UnsafeCell; -use core::sync::atomic::{AtomicPtr, AtomicBool}; -use core::sync::atomic::Ordering::{self, SeqCst}; use alloc::sync::{Arc, Weak}; +use core::cell::UnsafeCell; +use core::sync::atomic::Ordering::{self, Relaxed, SeqCst}; +use core::sync::atomic::{AtomicBool, AtomicPtr}; -use crate::task::{ArcWake, WakerRef, waker_ref}; -use super::ReadyToRunQueue; use super::abort::abort; +use super::ReadyToRunQueue; +use crate::task::ArcWake; pub(super) struct Task { // The future @@ -31,6 +31,11 @@ pub(super) struct Task { // Whether or not this task is currently in the ready to run queue pub(super) queued: AtomicBool, + + // Whether the future was awoken during polling + // It is possible for this flag to be set to true after the polling, + // but it will be ignored. + pub(super) woken: AtomicBool, } // `Task` can be sent across threads safely because it ensures that @@ -48,6 +53,8 @@ impl ArcWake for Task { None => return, }; + arc_self.woken.store(true, Relaxed); + // It's our job to enqueue this task it into the ready to run queue. To // do this we set the `queued` flag, and if successful we then do the // actual queueing operation, ensuring that we're only queued once. @@ -62,7 +69,7 @@ impl ArcWake for Task { // still. let prev = arc_self.queued.swap(true, SeqCst); if !prev { - inner.enqueue(&**arc_self); + inner.enqueue(Arc::as_ptr(arc_self)); inner.waker.wake(); } } @@ -70,8 +77,8 @@ impl ArcWake for Task { impl Task { /// Returns a waker reference for this task without cloning the Arc. - pub(super) fn waker_ref(this: &Arc) -> WakerRef<'_> { - waker_ref(this) + pub(super) unsafe fn waker_ref(this: &Arc) -> waker_ref::WakerRef<'_> { + unsafe { waker_ref::waker_ref(this) } } /// Spins until `next_all` is no longer set to `pending_next_all`. @@ -116,3 +123,92 @@ impl Drop for Task { } } } + +mod waker_ref { + use alloc::sync::Arc; + use core::marker::PhantomData; + use core::mem; + use core::mem::ManuallyDrop; + use core::ops::Deref; + use core::task::{RawWaker, RawWakerVTable, Waker}; + use futures_task::ArcWake; + + pub(crate) struct WakerRef<'a> { + waker: ManuallyDrop, + _marker: PhantomData<&'a ()>, + } + + impl WakerRef<'_> { + #[inline] + fn new_unowned(waker: ManuallyDrop) -> Self { + Self { waker, _marker: PhantomData } + } + } + + impl Deref for WakerRef<'_> { + type Target = Waker; + + #[inline] + fn deref(&self) -> &Waker { + &self.waker + } + } + + /// Copy of `future_task::waker_ref` without `W: 'static` bound. + /// + /// # Safety + /// + /// The caller must guarantee that use-after-free will not occur. + #[inline] + pub(crate) unsafe fn waker_ref(wake: &Arc) -> WakerRef<'_> + where + W: ArcWake, + { + // simply copy the pointer instead of using Arc::into_raw, + // as we don't actually keep a refcount by using ManuallyDrop.< + let ptr = Arc::as_ptr(wake).cast::<()>(); + + let waker = + ManuallyDrop::new(unsafe { Waker::from_raw(RawWaker::new(ptr, waker_vtable::())) }); + WakerRef::new_unowned(waker) + } + + fn waker_vtable() -> &'static RawWakerVTable { + &RawWakerVTable::new( + clone_arc_raw::, + wake_arc_raw::, + wake_by_ref_arc_raw::, + drop_arc_raw::, + ) + } + + // FIXME: panics on Arc::clone / refcount changes could wreak havoc on the + // code here. We should guard against this by aborting. + + unsafe fn increase_refcount(data: *const ()) { + // Retain Arc, but don't touch refcount by wrapping in ManuallyDrop + let arc = mem::ManuallyDrop::new(unsafe { Arc::::from_raw(data.cast::()) }); + // Now increase refcount, but don't drop new refcount either + let _arc_clone: mem::ManuallyDrop<_> = arc.clone(); + } + + unsafe fn clone_arc_raw(data: *const ()) -> RawWaker { + unsafe { increase_refcount::(data) } + RawWaker::new(data, waker_vtable::()) + } + + unsafe fn wake_arc_raw(data: *const ()) { + let arc: Arc = unsafe { Arc::from_raw(data.cast::()) }; + ArcWake::wake(arc); + } + + unsafe fn wake_by_ref_arc_raw(data: *const ()) { + // Retain Arc, but don't touch refcount by wrapping in ManuallyDrop + let arc = mem::ManuallyDrop::new(unsafe { Arc::::from_raw(data.cast::()) }); + ArcWake::wake_by_ref(&arc); + } + + unsafe fn drop_arc_raw(data: *const ()) { + drop(unsafe { Arc::::from_raw(data.cast::()) }) + } +} diff --git a/futures-util/src/stream/iter.rs b/futures-util/src/stream/iter.rs index 033dae1924..48b6519a39 100644 --- a/futures-util/src/stream/iter.rs +++ b/futures-util/src/stream/iter.rs @@ -10,6 +10,23 @@ pub struct Iter { iter: I, } +impl Iter { + /// Acquires a reference to the underlying iterator that this stream is pulling from. + pub fn get_ref(&self) -> &I { + &self.iter + } + + /// Acquires a mutable reference to the underlying iterator that this stream is pulling from. + pub fn get_mut(&mut self) -> &mut I { + &mut self.iter + } + + /// Consumes this stream, returning the underlying iterator. + pub fn into_inner(self) -> I { + self.iter + } +} + impl Unpin for Iter {} /// Converts an `Iterator` into a `Stream` which is always ready @@ -27,15 +44,15 @@ impl Unpin for Iter {} /// # }); /// ``` pub fn iter(i: I) -> Iter - where I: IntoIterator, +where + I: IntoIterator, { - assert_stream::(Iter { - iter: i.into_iter(), - }) + assert_stream::(Iter { iter: i.into_iter() }) } impl Stream for Iter - where I: Iterator, +where + I: Iterator, { type Item = I::Item; diff --git a/futures-util/src/stream/mod.rs b/futures-util/src/stream/mod.rs index f3b2baa408..2438e58b62 100644 --- a/futures-util/src/stream/mod.rs +++ b/futures-util/src/stream/mod.rs @@ -18,9 +18,10 @@ pub use futures_core::stream::{FusedStream, Stream, TryStream}; #[allow(clippy::module_inception)] mod stream; pub use self::stream::{ - Chain, Collect, Concat, Cycle, Enumerate, Filter, FilterMap, FlatMap, Flatten, Fold, ForEach, - Fuse, Inspect, Map, Next, Peek, Peekable, Scan, SelectNextSome, Skip, SkipWhile, StreamExt, - StreamFuture, Take, TakeUntil, TakeWhile, Then, Unzip, Zip, + All, Any, Chain, Collect, Concat, Count, Cycle, Enumerate, Filter, FilterMap, FlatMap, Flatten, + Fold, ForEach, Fuse, Inspect, Map, Next, NextIf, NextIfEq, Peek, PeekMut, Peekable, Scan, + SelectNextSome, Skip, SkipWhile, StreamExt, StreamFuture, Take, TakeUntil, TakeWhile, Then, + Unzip, Zip, }; #[cfg(feature = "std")] @@ -36,11 +37,13 @@ pub use self::stream::ReadyChunks; #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] pub use self::stream::Forward; -#[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] -pub use self::stream::{BufferUnordered, Buffered, ForEachConcurrent}; +pub use self::stream::{ + BufferUnordered, Buffered, FlatMapUnordered, FlattenUnordered, ForEachConcurrent, +}; -#[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "sink")] #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] #[cfg(feature = "alloc")] @@ -48,9 +51,9 @@ pub use self::stream::{ReuniteError, SplitSink, SplitStream}; mod try_stream; pub use self::try_stream::{ - try_unfold, AndThen, ErrInto, InspectErr, InspectOk, IntoStream, MapErr, MapOk, OrElse, - TryCollect, TryConcat, TryFilter, TryFilterMap, TryFlatten, TryFold, TryForEach, TryNext, - TrySkipWhile, TryStreamExt, TryTakeWhile, TryUnfold, + try_unfold, AndThen, ErrInto, InspectErr, InspectOk, IntoStream, MapErr, MapOk, OrElse, TryAll, + TryAny, TryCollect, TryConcat, TryFilter, TryFilterMap, TryFlatten, TryFold, TryForEach, + TryNext, TrySkipWhile, TryStreamExt, TryTakeWhile, TryUnfold, }; #[cfg(feature = "io")] @@ -58,9 +61,14 @@ pub use self::try_stream::{ #[cfg(feature = "std")] pub use self::try_stream::IntoAsyncRead; -#[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use self::try_stream::{ + TryBufferUnordered, TryBuffered, TryFlattenUnordered, TryForEachConcurrent, +}; + #[cfg(feature = "alloc")] -pub use self::try_stream::{TryBufferUnordered, TryBuffered, TryForEachConcurrent}; +pub use self::try_stream::{TryChunks, TryChunksError, TryReadyChunks, TryReadyChunksError}; // Primitive streams @@ -85,29 +93,50 @@ pub use self::pending::{pending, Pending}; mod poll_fn; pub use self::poll_fn::{poll_fn, PollFn}; +mod poll_immediate; +pub use self::poll_immediate::{poll_immediate, PollImmediate}; + mod select; pub use self::select::{select, Select}; +mod select_with_strategy; +pub use self::select_with_strategy::{select_with_strategy, PollNext, SelectWithStrategy}; + mod unfold; pub use self::unfold::{unfold, Unfold}; -cfg_target_has_atomic! { - #[cfg(feature = "alloc")] - mod futures_ordered; - #[cfg(feature = "alloc")] - pub use self::futures_ordered::FuturesOrdered; - - #[cfg(feature = "alloc")] - pub mod futures_unordered; - #[cfg(feature = "alloc")] - #[doc(inline)] - pub use self::futures_unordered::FuturesUnordered; - - #[cfg(feature = "alloc")] - mod select_all; - #[cfg(feature = "alloc")] - pub use self::select_all::{select_all, SelectAll}; -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod futures_ordered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use self::futures_ordered::FuturesOrdered; + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub mod futures_unordered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[doc(inline)] +pub use self::futures_unordered::FuturesUnordered; + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub mod select_all; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[doc(inline)] +pub use self::select_all::{select_all, SelectAll}; + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod abortable; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use crate::abortable::{AbortHandle, AbortRegistration, Abortable, Aborted}; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use abortable::abortable; // Just a helper function to ensure the streams we're returning all have the // right implementations. diff --git a/futures-util/src/stream/once.rs b/futures-util/src/stream/once.rs index e16fe00148..ee21c8b594 100644 --- a/futures-util/src/stream/once.rs +++ b/futures-util/src/stream/once.rs @@ -2,7 +2,7 @@ use super::assert_stream; use core::pin::Pin; use futures_core::future::Future; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; use pin_project_lite::pin_project; diff --git a/futures-util/src/stream/poll_immediate.rs b/futures-util/src/stream/poll_immediate.rs new file mode 100644 index 0000000000..c7e8a5b3c6 --- /dev/null +++ b/futures-util/src/stream/poll_immediate.rs @@ -0,0 +1,80 @@ +use core::pin::Pin; +use futures_core::task::{Context, Poll}; +use futures_core::Stream; +use pin_project_lite::pin_project; + +pin_project! { + /// Stream for the [poll_immediate](poll_immediate()) function. + /// + /// It will never return [Poll::Pending](core::task::Poll::Pending) + #[derive(Debug, Clone)] + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct PollImmediate { + #[pin] + stream: Option + } +} + +impl Stream for PollImmediate +where + S: Stream, +{ + type Item = Poll; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + let stream = match this.stream.as_mut().as_pin_mut() { + // inner is gone, so we can continue to signal that the stream is closed. + None => return Poll::Ready(None), + Some(inner) => inner, + }; + + match stream.poll_next(cx) { + Poll::Ready(Some(t)) => Poll::Ready(Some(Poll::Ready(t))), + Poll::Ready(None) => { + this.stream.set(None); + Poll::Ready(None) + } + Poll::Pending => Poll::Ready(Some(Poll::Pending)), + } + } + + fn size_hint(&self) -> (usize, Option) { + self.stream.as_ref().map_or((0, Some(0)), Stream::size_hint) + } +} + +impl super::FusedStream for PollImmediate { + fn is_terminated(&self) -> bool { + self.stream.is_none() + } +} + +/// Creates a new stream that always immediately returns [Poll::Ready](core::task::Poll::Ready) when awaiting it. +/// +/// This is useful when immediacy is more important than waiting for the next item to be ready. +/// +/// # Examples +/// +/// ``` +/// # futures::executor::block_on(async { +/// use futures::stream::{self, StreamExt}; +/// use futures::task::Poll; +/// +/// let mut r = stream::poll_immediate(Box::pin(stream::iter(1_u32..3))); +/// assert_eq!(r.next().await, Some(Poll::Ready(1))); +/// assert_eq!(r.next().await, Some(Poll::Ready(2))); +/// assert_eq!(r.next().await, None); +/// +/// let mut p = stream::poll_immediate(Box::pin(stream::once(async { +/// futures::pending!(); +/// 42_u8 +/// }))); +/// assert_eq!(p.next().await, Some(Poll::Pending)); +/// assert_eq!(p.next().await, Some(Poll::Ready(42))); +/// assert_eq!(p.next().await, None); +/// # }); +/// ``` +pub fn poll_immediate(s: S) -> PollImmediate { + super::assert_stream::, PollImmediate>(PollImmediate { stream: Some(s) }) +} diff --git a/futures-util/src/stream/repeat.rs b/futures-util/src/stream/repeat.rs index cf9f21bcf2..e09cfa2e41 100644 --- a/futures-util/src/stream/repeat.rs +++ b/futures-util/src/stream/repeat.rs @@ -1,6 +1,6 @@ use super::assert_stream; use core::pin::Pin; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; /// Stream for the [`repeat`] function. @@ -25,7 +25,8 @@ pub struct Repeat { /// # }); /// ``` pub fn repeat(item: T) -> Repeat - where T: Clone +where + T: Clone, { assert_stream::(Repeat { item }) } @@ -33,7 +34,8 @@ pub fn repeat(item: T) -> Repeat impl Unpin for Repeat {} impl Stream for Repeat - where T: Clone +where + T: Clone, { type Item = T; @@ -42,12 +44,13 @@ impl Stream for Repeat } fn size_hint(&self) -> (usize, Option) { - (usize::max_value(), None) + (usize::MAX, None) } } impl FusedStream for Repeat - where T: Clone, +where + T: Clone, { fn is_terminated(&self) -> bool { false diff --git a/futures-util/src/stream/repeat_with.rs b/futures-util/src/stream/repeat_with.rs index 0255643d57..a482510705 100644 --- a/futures-util/src/stream/repeat_with.rs +++ b/futures-util/src/stream/repeat_with.rs @@ -1,6 +1,6 @@ use super::assert_stream; use core::pin::Pin; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; /// An stream that repeats elements of type `A` endlessly by @@ -24,12 +24,11 @@ impl A> Stream for RepeatWith { } fn size_hint(&self) -> (usize, Option) { - (usize::max_value(), None) + (usize::MAX, None) } } -impl A> FusedStream for RepeatWith -{ +impl A> FusedStream for RepeatWith { fn is_terminated(&self) -> bool { false } diff --git a/futures-util/src/stream/select.rs b/futures-util/src/stream/select.rs index 2942494678..0c1e3af782 100644 --- a/futures-util/src/stream/select.rs +++ b/futures-util/src/stream/select.rs @@ -1,5 +1,5 @@ use super::assert_stream; -use crate::stream::{StreamExt, Fuse}; +use crate::stream::{select_with_strategy, PollNext, SelectWithStrategy}; use core::pin::Pin; use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; @@ -11,10 +11,7 @@ pin_project! { #[must_use = "streams do nothing unless polled"] pub struct Select { #[pin] - stream1: Fuse, - #[pin] - stream2: Fuse, - flag: bool, + inner: SelectWithStrategy PollNext, PollNext>, } } @@ -22,20 +19,42 @@ pin_project! { /// stream will be polled in a round-robin fashion, and whenever a stream is /// ready to yield an item that item is yielded. /// -/// After one of the two input stream completes, the remaining one will be +/// After one of the two input streams completes, the remaining one will be /// polled exclusively. The returned stream completes when both input /// streams have completed. /// /// Note that this function consumes both streams and returns a wrapped /// version of them. +/// +/// ## Examples +/// +/// ```rust +/// # futures::executor::block_on(async { +/// use futures::stream::{ repeat, select, StreamExt }; +/// +/// let left = repeat(1); +/// let right = repeat(2); +/// +/// let mut out = select(left, right); +/// +/// for _ in 0..100 { +/// // We should be alternating. +/// assert_eq!(1, out.select_next_some().await); +/// assert_eq!(2, out.select_next_some().await); +/// } +/// # }); +/// ``` pub fn select(stream1: St1, stream2: St2) -> Select - where St1: Stream, - St2: Stream +where + St1: Stream, + St2: Stream, { + fn round_robin(last: &mut PollNext) -> PollNext { + last.toggle() + } + assert_stream::(Select { - stream1: stream1.fuse(), - stream2: stream2.fuse(), - flag: false, + inner: select_with_strategy(stream1, stream2, round_robin), }) } @@ -43,7 +62,7 @@ impl Select { /// Acquires a reference to the underlying streams that this combinator is /// pulling from. pub fn get_ref(&self) -> (&St1, &St2) { - (self.stream1.get_ref(), self.stream2.get_ref()) + self.inner.get_ref() } /// Acquires a mutable reference to the underlying streams that this @@ -52,7 +71,7 @@ impl Select { /// Note that care must be taken to avoid tampering with the state of the /// stream which may otherwise confuse this combinator. pub fn get_mut(&mut self) -> (&mut St1, &mut St2) { - (self.stream1.get_mut(), self.stream2.get_mut()) + self.inner.get_mut() } /// Acquires a pinned mutable reference to the underlying streams that this @@ -62,7 +81,7 @@ impl Select { /// stream which may otherwise confuse this combinator. pub fn get_pin_mut(self: Pin<&mut Self>) -> (Pin<&mut St1>, Pin<&mut St2>) { let this = self.project(); - (this.stream1.get_pin_mut(), this.stream2.get_pin_mut()) + this.inner.get_pin_mut() } /// Consumes this combinator, returning the underlying streams. @@ -70,61 +89,29 @@ impl Select { /// Note that this may discard intermediate state of this combinator, so /// care should be taken to avoid losing resources when this is called. pub fn into_inner(self) -> (St1, St2) { - (self.stream1.into_inner(), self.stream2.into_inner()) + self.inner.into_inner() } } impl FusedStream for Select - where St1: Stream, - St2: Stream +where + St1: Stream, + St2: Stream, { fn is_terminated(&self) -> bool { - self.stream1.is_terminated() && self.stream2.is_terminated() + self.inner.is_terminated() } } impl Stream for Select - where St1: Stream, - St2: Stream +where + St1: Stream, + St2: Stream, { type Item = St1::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); - if !*this.flag { - poll_inner(this.flag, this.stream1, this.stream2, cx) - } else { - poll_inner(this.flag, this.stream2, this.stream1, cx) - } - } -} - -fn poll_inner( - flag: &mut bool, - a: Pin<&mut St1>, - b: Pin<&mut St2>, - cx: &mut Context<'_> -) -> Poll> - where St1: Stream, St2: Stream -{ - let a_done = match a.poll_next(cx) { - Poll::Ready(Some(item)) => { - // give the other stream a chance to go first next time - *flag = !*flag; - return Poll::Ready(Some(item)) - }, - Poll::Ready(None) => true, - Poll::Pending => false, - }; - - match b.poll_next(cx) { - Poll::Ready(Some(item)) => { - Poll::Ready(Some(item)) - } - Poll::Ready(None) if a_done => Poll::Ready(None), - Poll::Ready(None) | Poll::Pending => Poll::Pending, + this.inner.poll_next(cx) } } diff --git a/futures-util/src/stream/select_all.rs b/futures-util/src/stream/select_all.rs index c0b92faabe..121b6a0e59 100644 --- a/futures-util/src/stream/select_all.rs +++ b/futures-util/src/stream/select_all.rs @@ -5,11 +5,11 @@ use core::iter::FromIterator; use core::pin::Pin; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; use super::assert_stream; -use crate::stream::{StreamExt, StreamFuture, FuturesUnordered}; +use crate::stream::{futures_unordered, FuturesUnordered, StreamExt, StreamFuture}; /// An unbounded set of streams /// @@ -64,6 +64,21 @@ impl SelectAll { pub fn push(&mut self, stream: St) { self.inner.push(stream.into_future()); } + + /// Returns an iterator that allows inspecting each stream in the set. + pub fn iter(&self) -> Iter<'_, St> { + Iter(self.inner.iter()) + } + + /// Returns an iterator that allows modifying each stream in the set. + pub fn iter_mut(&mut self) -> IterMut<'_, St> { + IterMut(self.inner.iter_mut()) + } + + /// Clears the set, removing all streams. + pub fn clear(&mut self) { + self.inner.clear() + } } impl Default for SelectAll { @@ -75,10 +90,7 @@ impl Default for SelectAll { impl Stream for SelectAll { type Item = St::Item; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { match ready!(self.inner.poll_next_unpin(cx)) { Some((Some(item), remaining)) => { @@ -111,13 +123,14 @@ impl FusedStream for SelectAll { /// streams internally, in the order they become available. /// /// Note that the returned set can also be used to dynamically push more -/// futures into the set as they become available. +/// streams into the set as they become available. /// /// This function is only available when the `std` or `alloc` feature of this /// library is activated, and it is activated by default. pub fn select_all(streams: I) -> SelectAll - where I: IntoIterator, - I::Item: Stream + Unpin +where + I: IntoIterator, + I::Item: Stream + Unpin, { let mut set = SelectAll::new(); @@ -141,3 +154,96 @@ impl Extend for SelectAll { } } } + +impl IntoIterator for SelectAll { + type Item = St; + type IntoIter = IntoIter; + + fn into_iter(self) -> Self::IntoIter { + IntoIter(self.inner.into_iter()) + } +} + +impl<'a, St: Stream + Unpin> IntoIterator for &'a SelectAll { + type Item = &'a St; + type IntoIter = Iter<'a, St>; + + fn into_iter(self) -> Self::IntoIter { + self.iter() + } +} + +impl<'a, St: Stream + Unpin> IntoIterator for &'a mut SelectAll { + type Item = &'a mut St; + type IntoIter = IterMut<'a, St>; + + fn into_iter(self) -> Self::IntoIter { + self.iter_mut() + } +} + +/// Immutable iterator over all streams in the unordered set. +#[derive(Debug)] +pub struct Iter<'a, St: Unpin>(futures_unordered::Iter<'a, StreamFuture>); + +/// Mutable iterator over all streams in the unordered set. +#[derive(Debug)] +pub struct IterMut<'a, St: Unpin>(futures_unordered::IterMut<'a, StreamFuture>); + +/// Owned iterator over all streams in the unordered set. +#[derive(Debug)] +pub struct IntoIter(futures_unordered::IntoIter>); + +impl<'a, St: Stream + Unpin> Iterator for Iter<'a, St> { + type Item = &'a St; + + fn next(&mut self) -> Option { + let st = self.0.next()?; + let next = st.get_ref(); + // This should always be true because FuturesUnordered removes completed futures. + debug_assert!(next.is_some()); + next + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for Iter<'_, St> {} + +impl<'a, St: Stream + Unpin> Iterator for IterMut<'a, St> { + type Item = &'a mut St; + + fn next(&mut self) -> Option { + let st = self.0.next()?; + let next = st.get_mut(); + // This should always be true because FuturesUnordered removes completed futures. + debug_assert!(next.is_some()); + next + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for IterMut<'_, St> {} + +impl Iterator for IntoIter { + type Item = St; + + fn next(&mut self) -> Option { + let st = self.0.next()?; + let next = st.into_inner(); + // This should always be true because FuturesUnordered removes completed futures. + debug_assert!(next.is_some()); + next + } + + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +impl ExactSizeIterator for IntoIter {} diff --git a/futures-util/src/stream/select_with_strategy.rs b/futures-util/src/stream/select_with_strategy.rs new file mode 100644 index 0000000000..4e19873af7 --- /dev/null +++ b/futures-util/src/stream/select_with_strategy.rs @@ -0,0 +1,304 @@ +use super::assert_stream; +use core::{fmt, pin::Pin}; +use futures_core::stream::{FusedStream, Stream}; +use futures_core::task::{Context, Poll}; +use pin_project_lite::pin_project; + +/// Type to tell [`SelectWithStrategy`] which stream to poll next. +#[derive(Debug, PartialEq, Eq, Copy, Clone, Hash)] +pub enum PollNext { + /// Poll the first stream. + Left, + /// Poll the second stream. + Right, +} + +impl PollNext { + /// Toggle the value and return the old one. + pub fn toggle(&mut self) -> Self { + let old = *self; + *self = self.other(); + old + } + + fn other(&self) -> Self { + match self { + Self::Left => Self::Right, + Self::Right => Self::Left, + } + } +} + +impl Default for PollNext { + fn default() -> Self { + Self::Left + } +} + +enum InternalState { + Start, + LeftFinished, + RightFinished, + BothFinished, +} + +impl InternalState { + fn finish(&mut self, ps: PollNext) { + match (&self, ps) { + (Self::Start, PollNext::Left) => { + *self = Self::LeftFinished; + } + (Self::Start, PollNext::Right) => { + *self = Self::RightFinished; + } + (Self::LeftFinished, PollNext::Right) | (Self::RightFinished, PollNext::Left) => { + *self = Self::BothFinished; + } + _ => {} + } + } +} + +pin_project! { + /// Stream for the [`select_with_strategy()`] function. See function docs for details. + #[must_use = "streams do nothing unless polled"] + #[project = SelectWithStrategyProj] + pub struct SelectWithStrategy { + #[pin] + stream1: St1, + #[pin] + stream2: St2, + internal_state: InternalState, + state: State, + clos: Clos, + } +} + +#[allow(clippy::too_long_first_doc_paragraph)] +/// This function will attempt to pull items from both streams. You provide a +/// closure to tell [`SelectWithStrategy`] which stream to poll. The closure can +/// store state on `SelectWithStrategy` to which it will receive a `&mut` on every +/// invocation. This allows basing the strategy on prior choices. +/// +/// After one of the two input streams completes, the remaining one will be +/// polled exclusively. The returned stream completes when both input +/// streams have completed. +/// +/// Note that this function consumes both streams and returns a wrapped +/// version of them. +/// +/// ## Examples +/// +/// ### Priority +/// This example shows how to always prioritize the left stream. +/// +/// ```rust +/// # futures::executor::block_on(async { +/// use futures::stream::{ repeat, select_with_strategy, PollNext, StreamExt }; +/// +/// let left = repeat(1); +/// let right = repeat(2); +/// +/// // We don't need any state, so let's make it an empty tuple. +/// // We must provide some type here, as there is no way for the compiler +/// // to infer it. As we don't need to capture variables, we can just +/// // use a function pointer instead of a closure. +/// fn prio_left(_: &mut ()) -> PollNext { PollNext::Left } +/// +/// let mut out = select_with_strategy(left, right, prio_left); +/// +/// for _ in 0..100 { +/// // Whenever we poll out, we will alwas get `1`. +/// assert_eq!(1, out.select_next_some().await); +/// } +/// # }); +/// ``` +/// +/// ### Round Robin +/// This example shows how to select from both streams round robin. +/// Note: this special case is provided by [`futures-util::stream::select`]. +/// +/// ```rust +/// # futures::executor::block_on(async { +/// use futures::stream::{ repeat, select_with_strategy, PollNext, StreamExt }; +/// +/// let left = repeat(1); +/// let right = repeat(2); +/// +/// let rrobin = |last: &mut PollNext| last.toggle(); +/// +/// let mut out = select_with_strategy(left, right, rrobin); +/// +/// for _ in 0..100 { +/// // We should be alternating now. +/// assert_eq!(1, out.select_next_some().await); +/// assert_eq!(2, out.select_next_some().await); +/// } +/// # }); +/// ``` +pub fn select_with_strategy( + stream1: St1, + stream2: St2, + which: Clos, +) -> SelectWithStrategy +where + St1: Stream, + St2: Stream, + Clos: FnMut(&mut State) -> PollNext, + State: Default, +{ + assert_stream::(SelectWithStrategy { + stream1, + stream2, + state: Default::default(), + internal_state: InternalState::Start, + clos: which, + }) +} + +impl SelectWithStrategy { + /// Acquires a reference to the underlying streams that this combinator is + /// pulling from. + pub fn get_ref(&self) -> (&St1, &St2) { + (&self.stream1, &self.stream2) + } + + /// Acquires a mutable reference to the underlying streams that this + /// combinator is pulling from. + /// + /// Note that care must be taken to avoid tampering with the state of the + /// stream which may otherwise confuse this combinator. + pub fn get_mut(&mut self) -> (&mut St1, &mut St2) { + (&mut self.stream1, &mut self.stream2) + } + + /// Acquires a pinned mutable reference to the underlying streams that this + /// combinator is pulling from. + /// + /// Note that care must be taken to avoid tampering with the state of the + /// stream which may otherwise confuse this combinator. + pub fn get_pin_mut(self: Pin<&mut Self>) -> (Pin<&mut St1>, Pin<&mut St2>) { + let this = self.project(); + (this.stream1, this.stream2) + } + + /// Consumes this combinator, returning the underlying streams. + /// + /// Note that this may discard intermediate state of this combinator, so + /// care should be taken to avoid losing resources when this is called. + pub fn into_inner(self) -> (St1, St2) { + (self.stream1, self.stream2) + } +} + +impl FusedStream for SelectWithStrategy +where + St1: Stream, + St2: Stream, + Clos: FnMut(&mut State) -> PollNext, +{ + fn is_terminated(&self) -> bool { + match self.internal_state { + InternalState::BothFinished => true, + _ => false, + } + } +} + +#[inline] +fn poll_side( + select: &mut SelectWithStrategyProj<'_, St1, St2, Clos, State>, + side: PollNext, + cx: &mut Context<'_>, +) -> Poll> +where + St1: Stream, + St2: Stream, +{ + match side { + PollNext::Left => select.stream1.as_mut().poll_next(cx), + PollNext::Right => select.stream2.as_mut().poll_next(cx), + } +} + +#[inline] +fn poll_inner( + select: &mut SelectWithStrategyProj<'_, St1, St2, Clos, State>, + side: PollNext, + cx: &mut Context<'_>, +) -> Poll> +where + St1: Stream, + St2: Stream, +{ + let first_done = match poll_side(select, side, cx) { + Poll::Ready(Some(item)) => return Poll::Ready(Some(item)), + Poll::Ready(None) => { + select.internal_state.finish(side); + true + } + Poll::Pending => false, + }; + let other = side.other(); + match poll_side(select, other, cx) { + Poll::Ready(None) => { + select.internal_state.finish(other); + if first_done { + Poll::Ready(None) + } else { + Poll::Pending + } + } + a => a, + } +} + +impl Stream for SelectWithStrategy +where + St1: Stream, + St2: Stream, + Clos: FnMut(&mut State) -> PollNext, +{ + type Item = St1::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + + match this.internal_state { + InternalState::Start => { + let next_side = (this.clos)(this.state); + poll_inner(&mut this, next_side, cx) + } + InternalState::LeftFinished => match this.stream2.poll_next(cx) { + Poll::Ready(None) => { + *this.internal_state = InternalState::BothFinished; + Poll::Ready(None) + } + a => a, + }, + InternalState::RightFinished => match this.stream1.poll_next(cx) { + Poll::Ready(None) => { + *this.internal_state = InternalState::BothFinished; + Poll::Ready(None) + } + a => a, + }, + InternalState::BothFinished => Poll::Ready(None), + } + } +} + +impl fmt::Debug for SelectWithStrategy +where + St1: fmt::Debug, + St2: fmt::Debug, + State: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("SelectWithStrategy") + .field("stream1", &self.stream1) + .field("stream2", &self.stream2) + .field("state", &self.state) + .finish() + } +} diff --git a/futures-util/src/stream/stream/all.rs b/futures-util/src/stream/stream/all.rs new file mode 100644 index 0000000000..1435c798f2 --- /dev/null +++ b/futures-util/src/stream/stream/all.rs @@ -0,0 +1,93 @@ +use core::fmt; +use core::pin::Pin; +use futures_core::future::{FusedFuture, Future}; +use futures_core::ready; +use futures_core::stream::Stream; +use futures_core::task::{Context, Poll}; +use pin_project_lite::pin_project; + +pin_project! { + /// Future for the [`all`](super::StreamExt::all) method. + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct All { + #[pin] + stream: St, + f: F, + done: bool, + #[pin] + future: Option, + } +} + +impl fmt::Debug for All +where + St: fmt::Debug, + Fut: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("All") + .field("stream", &self.stream) + .field("done", &self.done) + .field("future", &self.future) + .finish() + } +} + +impl All +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, +{ + pub(super) fn new(stream: St, f: F) -> Self { + Self { stream, f, done: false, future: None } + } +} + +impl FusedFuture for All +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, +{ + fn is_terminated(&self) -> bool { + self.done && self.future.is_none() + } +} + +impl Future for All +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + Poll::Ready(loop { + if let Some(fut) = this.future.as_mut().as_pin_mut() { + // we're currently processing a future to produce a new value + let res = ready!(fut.poll(cx)); + this.future.set(None); + if !res { + *this.done = true; + break false; + } // early exit + } else if !*this.done { + // we're waiting on a new item from the stream + match ready!(this.stream.as_mut().poll_next(cx)) { + Some(item) => { + this.future.set(Some((this.f)(item))); + } + None => { + *this.done = true; + break true; + } + } + } else { + panic!("All polled after completion") + } + }) + } +} diff --git a/futures-util/src/stream/stream/any.rs b/futures-util/src/stream/stream/any.rs new file mode 100644 index 0000000000..cc3d695b9d --- /dev/null +++ b/futures-util/src/stream/stream/any.rs @@ -0,0 +1,93 @@ +use core::fmt; +use core::pin::Pin; +use futures_core::future::{FusedFuture, Future}; +use futures_core::ready; +use futures_core::stream::Stream; +use futures_core::task::{Context, Poll}; +use pin_project_lite::pin_project; + +pin_project! { + /// Future for the [`any`](super::StreamExt::any) method. + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct Any { + #[pin] + stream: St, + f: F, + done: bool, + #[pin] + future: Option, + } +} + +impl fmt::Debug for Any +where + St: fmt::Debug, + Fut: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Any") + .field("stream", &self.stream) + .field("done", &self.done) + .field("future", &self.future) + .finish() + } +} + +impl Any +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, +{ + pub(super) fn new(stream: St, f: F) -> Self { + Self { stream, f, done: false, future: None } + } +} + +impl FusedFuture for Any +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, +{ + fn is_terminated(&self) -> bool { + self.done && self.future.is_none() + } +} + +impl Future for Any +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, +{ + type Output = bool; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + Poll::Ready(loop { + if let Some(fut) = this.future.as_mut().as_pin_mut() { + // we're currently processing a future to produce a new value + let res = ready!(fut.poll(cx)); + this.future.set(None); + if res { + *this.done = true; + break true; + } // early exit + } else if !*this.done { + // we're waiting on a new item from the stream + match ready!(this.stream.as_mut().poll_next(cx)) { + Some(item) => { + this.future.set(Some((this.f)(item))); + } + None => { + *this.done = true; + break false; + } + } + } else { + panic!("Any polled after completion") + } + }) + } +} diff --git a/futures-util/src/stream/stream/buffer_unordered.rs b/futures-util/src/stream/stream/buffer_unordered.rs index de42cfd330..91b0f6bcce 100644 --- a/futures-util/src/stream/stream/buffer_unordered.rs +++ b/futures-util/src/stream/stream/buffer_unordered.rs @@ -1,12 +1,12 @@ use crate::stream::{Fuse, FuturesUnordered, StreamExt}; +use core::fmt; +use core::pin::Pin; use futures_core::future::Future; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use core::fmt; -use core::pin::Pin; pin_project! { /// Stream for the [`buffer_unordered`](super::StreamExt::buffer_unordered) @@ -41,11 +41,7 @@ where St: Stream, St::Item: Future, { - pub(super) fn new(stream: St, n: usize) -> Self - where - St: Stream, - St::Item: Future, - { + pub(super) fn new(stream: St, n: usize) -> Self { Self { stream: super::Fuse::new(stream), in_progress_queue: FuturesUnordered::new(), @@ -63,10 +59,7 @@ where { type Item = ::Output; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); // First up, try to spawn off as many futures as possible by filling up diff --git a/futures-util/src/stream/stream/buffered.rs b/futures-util/src/stream/stream/buffered.rs index 1af9f492b9..5854eb7ea5 100644 --- a/futures-util/src/stream/stream/buffered.rs +++ b/futures-util/src/stream/stream/buffered.rs @@ -1,4 +1,6 @@ -use crate::stream::{Fuse, FuturesOrdered, StreamExt}; +use crate::stream::{Fuse, FusedStream, FuturesOrdered, StreamExt}; +use core::fmt; +use core::pin::Pin; use futures_core::future::Future; use futures_core::ready; use futures_core::stream::Stream; @@ -6,8 +8,6 @@ use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use core::fmt; -use core::pin::Pin; pin_project! { /// Stream for the [`buffered`](super::StreamExt::buffered) method. @@ -44,11 +44,7 @@ where St::Item: Future, { pub(super) fn new(stream: St, n: usize) -> Self { - Self { - stream: super::Fuse::new(stream), - in_progress_queue: FuturesOrdered::new(), - max: n, - } + Self { stream: super::Fuse::new(stream), in_progress_queue: FuturesOrdered::new(), max: n } } delegate_access_inner!(stream, St, (.)); @@ -61,17 +57,14 @@ where { type Item = ::Output; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); // First up, try to spawn off as many futures as possible by filling up // our queue of futures. while this.in_progress_queue.len() < *this.max { match this.stream.as_mut().poll_next(cx) { - Poll::Ready(Some(fut)) => this.in_progress_queue.push(fut), + Poll::Ready(Some(fut)) => this.in_progress_queue.push_back(fut), Poll::Ready(None) | Poll::Pending => break, } } @@ -79,7 +72,7 @@ where // Attempt to pull the next value from the in_progress_queue let res = this.in_progress_queue.poll_next_unpin(cx); if let Some(val) = ready!(res) { - return Poll::Ready(Some(val)) + return Poll::Ready(Some(val)); } // If more values are still coming from the stream, we're not done yet @@ -102,6 +95,16 @@ where } } +impl FusedStream for Buffered +where + St: Stream, + St::Item: Future, +{ + fn is_terminated(&self) -> bool { + self.stream.is_done() && self.in_progress_queue.is_terminated() + } +} + // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for Buffered diff --git a/futures-util/src/stream/stream/catch_unwind.rs b/futures-util/src/stream/stream/catch_unwind.rs index d87a40a2e3..dfc388839d 100644 --- a/futures-util/src/stream/stream/catch_unwind.rs +++ b/futures-util/src/stream/stream/catch_unwind.rs @@ -1,9 +1,10 @@ -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; use pin_project_lite::pin_project; use std::any::Any; +use std::boxed::Box; +use std::panic::{catch_unwind, AssertUnwindSafe, UnwindSafe}; use std::pin::Pin; -use std::panic::{catch_unwind, UnwindSafe, AssertUnwindSafe}; pin_project! { /// Stream for the [`catch_unwind`](super::StreamExt::catch_unwind) method. @@ -27,25 +28,20 @@ impl CatchUnwind { impl Stream for CatchUnwind { type Item = Result>; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); if *this.caught_unwind { Poll::Ready(None) } else { - let res = catch_unwind(AssertUnwindSafe(|| { - this.stream.as_mut().poll_next(cx) - })); + let res = catch_unwind(AssertUnwindSafe(|| this.stream.as_mut().poll_next(cx))); match res { Ok(poll) => poll.map(|opt| opt.map(Ok)), Err(e) => { *this.caught_unwind = true; Poll::Ready(Some(Err(e))) - }, + } } } } diff --git a/futures-util/src/stream/stream/chain.rs b/futures-util/src/stream/stream/chain.rs index 2be710462a..36ff1e533d 100644 --- a/futures-util/src/stream/stream/chain.rs +++ b/futures-util/src/stream/stream/chain.rs @@ -18,20 +18,19 @@ pin_project! { // All interactions with `Pin<&mut Chain<..>>` happen through these methods impl Chain -where St1: Stream, - St2: Stream, +where + St1: Stream, + St2: Stream, { pub(super) fn new(stream1: St1, stream2: St2) -> Self { - Self { - first: Some(stream1), - second: stream2, - } + Self { first: Some(stream1), second: stream2 } } } impl FusedStream for Chain -where St1: Stream, - St2: FusedStream, +where + St1: Stream, + St2: FusedStream, { fn is_terminated(&self) -> bool { self.first.is_none() && self.second.is_terminated() @@ -39,22 +38,21 @@ where St1: Stream, } impl Stream for Chain -where St1: Stream, - St2: Stream, +where + St1: Stream, + St2: Stream, { type Item = St1::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); if let Some(first) = this.first.as_mut().as_pin_mut() { if let Some(item) = ready!(first.poll_next(cx)) { - return Poll::Ready(Some(item)) + return Poll::Ready(Some(item)); } + + this.first.set(None); } - this.first.set(None); this.second.poll_next(cx) } @@ -67,7 +65,7 @@ where St1: Stream, let upper = match (first_upper, second_upper) { (Some(x), Some(y)) => x.checked_add(y), - _ => None + _ => None, }; (lower, upper) diff --git a/futures-util/src/stream/stream/chunks.rs b/futures-util/src/stream/stream/chunks.rs index 45a3212582..2a71ebc6cc 100644 --- a/futures-util/src/stream/stream/chunks.rs +++ b/futures-util/src/stream/stream/chunks.rs @@ -1,13 +1,13 @@ use crate::stream::Fuse; +use alloc::vec::Vec; +use core::mem; +use core::pin::Pin; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use core::mem; -use core::pin::Pin; -use alloc::vec::Vec; pin_project! { /// Stream for the [`chunks`](super::StreamExt::chunks) method. @@ -21,7 +21,7 @@ pin_project! { } } -impl Chunks where St: Stream { +impl Chunks { pub(super) fn new(stream: St, capacity: usize) -> Self { assert!(capacity > 0); @@ -43,10 +43,7 @@ impl Chunks where St: Stream { impl Stream for Chunks { type Item = Vec; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.as_mut().project(); loop { match ready!(this.stream.as_mut().poll_next(cx)) { @@ -56,7 +53,7 @@ impl Stream for Chunks { Some(item) => { this.items.push(item); if this.items.len() >= *this.cap { - return Poll::Ready(Some(self.take())) + return Poll::Ready(Some(self.take())); } } @@ -66,7 +63,7 @@ impl Stream for Chunks { let last = if this.items.is_empty() { None } else { - let full_buf = mem::replace(this.items, Vec::new()); + let full_buf = mem::take(this.items); Some(full_buf) }; @@ -77,9 +74,9 @@ impl Stream for Chunks { } fn size_hint(&self) -> (usize, Option) { - let chunk_len = if self.items.is_empty() { 0 } else { 1 }; + let chunk_len = usize::from(!self.items.is_empty()); let (lower, upper) = self.stream.size_hint(); - let lower = lower.saturating_add(chunk_len); + let lower = (lower / self.cap).saturating_add(chunk_len); let upper = match upper { Some(x) => x.checked_add(chunk_len), None => None, diff --git a/futures-util/src/stream/stream/collect.rs b/futures-util/src/stream/stream/collect.rs index 774b34b39a..970ac26dbf 100644 --- a/futures-util/src/stream/stream/collect.rs +++ b/futures-util/src/stream/stream/collect.rs @@ -19,20 +19,18 @@ pin_project! { impl Collect { fn finish(self: Pin<&mut Self>) -> C { - mem::replace(self.project().collection, Default::default()) + mem::take(self.project().collection) } pub(super) fn new(stream: St) -> Self { - Self { - stream, - collection: Default::default(), - } + Self { stream, collection: Default::default() } } } impl FusedFuture for Collect -where St: FusedStream, - C: Default + Extend +where + St: FusedStream, + C: Default + Extend, { fn is_terminated(&self) -> bool { self.stream.is_terminated() @@ -40,8 +38,9 @@ where St: FusedStream, } impl Future for Collect -where St: Stream, - C: Default + Extend +where + St: Stream, + C: Default + Extend, { type Output = C; diff --git a/futures-util/src/stream/stream/concat.rs b/futures-util/src/stream/stream/concat.rs index ee1349f86d..7e058b2315 100644 --- a/futures-util/src/stream/stream/concat.rs +++ b/futures-util/src/stream/stream/concat.rs @@ -1,7 +1,7 @@ use core::pin::Pin; -use futures_core::future::{Future, FusedFuture}; +use futures_core::future::{FusedFuture, Future}; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; use pin_project_lite::pin_project; @@ -17,35 +17,28 @@ pin_project! { } impl Concat -where St: Stream, - St::Item: Extend<::Item> + - IntoIterator + Default, +where + St: Stream, + St::Item: Extend<::Item> + IntoIterator + Default, { pub(super) fn new(stream: St) -> Self { - Self { - stream, - accum: None, - } + Self { stream, accum: None } } } impl Future for Concat -where St: Stream, - St::Item: Extend<::Item> + - IntoIterator + Default, +where + St: Stream, + St::Item: Extend<::Item> + IntoIterator + Default, { type Output = St::Item; - fn poll( - self: Pin<&mut Self>, cx: &mut Context<'_> - ) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); loop { match ready!(this.stream.as_mut().poll_next(cx)) { - None => { - return Poll::Ready(this.accum.take().unwrap_or_default()) - } + None => return Poll::Ready(this.accum.take().unwrap_or_default()), Some(e) => { if let Some(a) = this.accum { a.extend(e) @@ -59,9 +52,9 @@ where St: Stream, } impl FusedFuture for Concat -where St: FusedStream, - St::Item: Extend<::Item> + - IntoIterator + Default, +where + St: FusedStream, + St::Item: Extend<::Item> + IntoIterator + Default, { fn is_terminated(&self) -> bool { self.accum.is_none() && self.stream.is_terminated() diff --git a/futures-util/src/stream/stream/count.rs b/futures-util/src/stream/stream/count.rs new file mode 100644 index 0000000000..513cab7b6a --- /dev/null +++ b/futures-util/src/stream/stream/count.rs @@ -0,0 +1,53 @@ +use core::fmt; +use core::pin::Pin; +use futures_core::future::{FusedFuture, Future}; +use futures_core::ready; +use futures_core::stream::{FusedStream, Stream}; +use futures_core::task::{Context, Poll}; +use pin_project_lite::pin_project; + +pin_project! { + /// Future for the [`count`](super::StreamExt::count) method. + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct Count { + #[pin] + stream: St, + count: usize + } +} + +impl fmt::Debug for Count +where + St: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("Count").field("stream", &self.stream).field("count", &self.count).finish() + } +} + +impl Count { + pub(super) fn new(stream: St) -> Self { + Self { stream, count: 0 } + } +} + +impl FusedFuture for Count { + fn is_terminated(&self) -> bool { + self.stream.is_terminated() + } +} + +impl Future for Count { + type Output = usize; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut this = self.project(); + + Poll::Ready(loop { + match ready!(this.stream.as_mut().poll_next(cx)) { + Some(_) => *this.count += 1, + None => break *this.count, + } + }) + } +} diff --git a/futures-util/src/stream/stream/cycle.rs b/futures-util/src/stream/stream/cycle.rs index a5b7dc08c5..60fe745399 100644 --- a/futures-util/src/stream/stream/cycle.rs +++ b/futures-util/src/stream/stream/cycle.rs @@ -1,5 +1,4 @@ use core::pin::Pin; -use core::usize; use futures_core::ready; use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; @@ -21,10 +20,7 @@ where St: Clone + Stream, { pub(super) fn new(stream: St) -> Self { - Self { - orig: stream.clone(), - stream, - } + Self { orig: stream.clone(), stream } } } @@ -51,7 +47,7 @@ where match self.orig.size_hint() { size @ (0, Some(0)) => size, (0, _) => (0, None), - _ => (usize::max_value(), None), + _ => (usize::MAX, None), } } } diff --git a/futures-util/src/stream/stream/enumerate.rs b/futures-util/src/stream/stream/enumerate.rs index 7d4c9cbe68..1cf9d49aaa 100644 --- a/futures-util/src/stream/stream/enumerate.rs +++ b/futures-util/src/stream/stream/enumerate.rs @@ -19,10 +19,7 @@ pin_project! { impl Enumerate { pub(super) fn new(stream: St) -> Self { - Self { - stream, - count: 0, - } + Self { stream, count: 0 } } delegate_access_inner!(stream, St, ()); @@ -37,10 +34,7 @@ impl FusedStream for Enumerate { impl Stream for Enumerate { type Item = (usize, St::Item); - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); match ready!(this.stream.poll_next(cx)) { diff --git a/futures-util/src/stream/stream/filter.rs b/futures-util/src/stream/stream/filter.rs index 57de0253a4..997fe9977e 100644 --- a/futures-util/src/stream/stream/filter.rs +++ b/futures-util/src/stream/stream/filter.rs @@ -1,3 +1,4 @@ +use crate::fns::FnMut1; use core::fmt; use core::pin::Pin; use futures_core::future::Future; @@ -7,7 +8,6 @@ use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use crate::fns::FnMut1; pin_project! { /// Stream for the [`filter`](super::StreamExt::filter) method. @@ -41,26 +41,23 @@ where #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 impl Filter -where St: Stream, - F: for<'a> FnMut1<&'a St::Item, Output=Fut>, - Fut: Future, +where + St: Stream, + F: for<'a> FnMut1<&'a St::Item, Output = Fut>, + Fut: Future, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - pending_fut: None, - pending_item: None, - } + Self { stream, f, pending_fut: None, pending_item: None } } delegate_access_inner!(stream, St, ()); } impl FusedStream for Filter - where St: Stream + FusedStream, - F: FnMut(&St::Item) -> Fut, - Fut: Future, +where + St: Stream + FusedStream, + F: FnMut(&St::Item) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.pending_fut.is_none() && self.stream.is_terminated() @@ -69,16 +66,14 @@ impl FusedStream for Filter #[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 impl Stream for Filter - where St: Stream, - F: for<'a> FnMut1<&'a St::Item, Output=Fut>, - Fut: Future, +where + St: Stream, + F: for<'a> FnMut1<&'a St::Item, Output = Fut>, + Fut: Future, { type Item = St::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); Poll::Ready(loop { if let Some(fut) = this.pending_fut.as_mut().as_pin_mut() { @@ -98,7 +93,7 @@ impl Stream for Filter } fn size_hint(&self) -> (usize, Option) { - let pending_len = if self.pending_item.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending_item.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), @@ -111,9 +106,10 @@ impl Stream for Filter // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for Filter - where S: Stream + Sink, - F: FnMut(&S::Item) -> Fut, - Fut: Future, +where + S: Stream + Sink, + F: FnMut(&S::Item) -> Fut, + Fut: Future, { type Error = S::Error; diff --git a/futures-util/src/stream/stream/filter_map.rs b/futures-util/src/stream/stream/filter_map.rs index b762face28..6b7d0070df 100644 --- a/futures-util/src/stream/stream/filter_map.rs +++ b/futures-util/src/stream/stream/filter_map.rs @@ -1,3 +1,4 @@ +use crate::fns::FnMut1; use core::fmt; use core::pin::Pin; use futures_core::future::Future; @@ -7,7 +8,6 @@ use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use crate::fns::FnMut1; pin_project! { /// Stream for the [`filter_map`](super::StreamExt::filter_map) method. @@ -35,9 +35,10 @@ where } impl FilterMap - where St: Stream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { pub(super) fn new(stream: St, f: F) -> Self { Self { stream, f, pending: None } @@ -47,9 +48,10 @@ impl FilterMap } impl FusedStream for FilterMap - where St: Stream + FusedStream, - F: FnMut1, - Fut: Future>, +where + St: Stream + FusedStream, + F: FnMut1, + Fut: Future>, { fn is_terminated(&self) -> bool { self.pending.is_none() && self.stream.is_terminated() @@ -57,16 +59,14 @@ impl FusedStream for FilterMap } impl Stream for FilterMap - where St: Stream, - F: FnMut1, - Fut: Future>, +where + St: Stream, + F: FnMut1, + Fut: Future>, { type Item = T; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); Poll::Ready(loop { if let Some(p) = this.pending.as_mut().as_pin_mut() { @@ -87,7 +87,7 @@ impl Stream for FilterMap } fn size_hint(&self) -> (usize, Option) { - let pending_len = if self.pending.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), @@ -100,9 +100,10 @@ impl Stream for FilterMap // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for FilterMap - where S: Stream + Sink, - F: FnMut1, - Fut: Future, +where + S: Stream + Sink, + F: FnMut1, + Fut: Future, { type Error = S::Error; diff --git a/futures-util/src/stream/stream/flatten_unordered.rs b/futures-util/src/stream/stream/flatten_unordered.rs new file mode 100644 index 0000000000..c79edbd213 --- /dev/null +++ b/futures-util/src/stream/stream/flatten_unordered.rs @@ -0,0 +1,531 @@ +use alloc::sync::Arc; +use core::{ + cell::UnsafeCell, + convert::identity, + fmt, + marker::PhantomData, + num::NonZeroUsize, + pin::Pin, + sync::atomic::{AtomicU8, Ordering}, +}; + +use pin_project_lite::pin_project; + +use futures_core::{ + future::Future, + ready, + stream::{FusedStream, Stream}, + task::{Context, Poll, Waker}, +}; +#[cfg(feature = "sink")] +use futures_sink::Sink; +use futures_task::{waker, ArcWake}; + +use crate::stream::FuturesUnordered; + +/// Stream for the [`flatten_unordered`](super::StreamExt::flatten_unordered) +/// method. +pub type FlattenUnordered = FlattenUnorderedWithFlowController; + +/// There is nothing to poll and stream isn't being polled/waking/woken at the moment. +const NONE: u8 = 0; + +/// Inner streams need to be polled. +const NEED_TO_POLL_INNER_STREAMS: u8 = 1; + +/// The base stream needs to be polled. +const NEED_TO_POLL_STREAM: u8 = 0b10; + +/// Both base stream and inner streams need to be polled. +const NEED_TO_POLL_ALL: u8 = NEED_TO_POLL_INNER_STREAMS | NEED_TO_POLL_STREAM; + +/// The current stream is being polled at the moment. +const POLLING: u8 = 0b100; + +/// Stream is being woken at the moment. +const WAKING: u8 = 0b1000; + +/// The stream was waked and will be polled. +const WOKEN: u8 = 0b10000; + +/// Internal polling state of the stream. +#[derive(Clone, Debug)] +struct SharedPollState { + state: Arc, +} + +impl SharedPollState { + /// Constructs new `SharedPollState` with the given state. + fn new(value: u8) -> Self { + Self { state: Arc::new(AtomicU8::new(value)) } + } + + /// Attempts to start polling, returning stored state in case of success. + /// Returns `None` if either waker is waking at the moment. + fn start_polling(&self) -> Option<(u8, PollStateBomb<'_, impl FnOnce(&Self) -> u8>)> { + let value = self + .state + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |value| { + if value & WAKING == NONE { + Some(POLLING) + } else { + None + } + }) + .ok()?; + let bomb = PollStateBomb::new(self, Self::reset); + + Some((value, bomb)) + } + + /// Attempts to start the waking process and performs bitwise or with the given value. + /// + /// If some waker is already in progress or stream is already woken/being polled, waking process won't start, however + /// state will be disjuncted with the given value. + fn start_waking( + &self, + to_poll: u8, + ) -> Option<(u8, PollStateBomb<'_, impl FnOnce(&Self) -> u8>)> { + let value = self + .state + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |value| { + let mut next_value = value | to_poll; + if value & (WOKEN | POLLING) == NONE { + next_value |= WAKING; + } + + if next_value != value { + Some(next_value) + } else { + None + } + }) + .ok()?; + + // Only start the waking process if we're not in the polling/waking phase and the stream isn't woken already + if value & (WOKEN | POLLING | WAKING) == NONE { + let bomb = PollStateBomb::new(self, Self::stop_waking); + + Some((value, bomb)) + } else { + None + } + } + + /// Sets current state to + /// - `!POLLING` allowing to use wakers + /// - `WOKEN` if the state was changed during `POLLING` phase as waker will be called, + /// or `will_be_woken` flag supplied + /// - `!WAKING` as + /// * Wakers called during the `POLLING` phase won't propagate their calls + /// * `POLLING` phase can't start if some of the wakers are active + /// So no wrapped waker can touch the inner waker's cell, it's safe to poll again. + fn stop_polling(&self, to_poll: u8, will_be_woken: bool) -> u8 { + self.state + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |mut value| { + let mut next_value = to_poll; + + value &= NEED_TO_POLL_ALL; + if value != NONE || will_be_woken { + next_value |= WOKEN; + } + next_value |= value; + + Some(next_value & !POLLING & !WAKING) + }) + .unwrap() + } + + /// Toggles state to non-waking, allowing to start polling. + fn stop_waking(&self) -> u8 { + let value = self + .state + .fetch_update(Ordering::SeqCst, Ordering::SeqCst, |value| { + let next_value = value & !WAKING | WOKEN; + + if next_value != value { + Some(next_value) + } else { + None + } + }) + .unwrap_or_else(identity); + + debug_assert!(value & (WOKEN | POLLING | WAKING) == WAKING); + value + } + + /// Resets current state allowing to poll the stream and wake up wakers. + fn reset(&self) -> u8 { + self.state.swap(NEED_TO_POLL_ALL, Ordering::SeqCst) + } +} + +/// Used to execute some function on the given state when dropped. +struct PollStateBomb<'a, F: FnOnce(&SharedPollState) -> u8> { + state: &'a SharedPollState, + drop: Option, +} + +impl<'a, F: FnOnce(&SharedPollState) -> u8> PollStateBomb<'a, F> { + /// Constructs new bomb with the given state. + fn new(state: &'a SharedPollState, drop: F) -> Self { + Self { state, drop: Some(drop) } + } + + /// Deactivates bomb, forces it to not call provided function when dropped. + fn deactivate(mut self) { + self.drop.take(); + } +} + +impl u8> Drop for PollStateBomb<'_, F> { + fn drop(&mut self) { + if let Some(drop) = self.drop.take() { + (drop)(self.state); + } + } +} + +/// Will update state with the provided value on `wake_by_ref` call +/// and then, if there is a need, call `inner_waker`. +struct WrappedWaker { + inner_waker: UnsafeCell>, + poll_state: SharedPollState, + need_to_poll: u8, +} + +unsafe impl Send for WrappedWaker {} +unsafe impl Sync for WrappedWaker {} + +impl WrappedWaker { + /// Replaces given waker's inner_waker for polling stream/futures which will + /// update poll state on `wake_by_ref` call. Use only if you need several + /// contexts. + /// + /// ## Safety + /// + /// This function will modify waker's `inner_waker` via `UnsafeCell`, so + /// it should be used only during `POLLING` phase by one thread at the time. + unsafe fn replace_waker(self_arc: &mut Arc, cx: &Context<'_>) { + unsafe { *self_arc.inner_waker.get() = cx.waker().clone().into() } + } + + /// Attempts to start the waking process for the waker with the given value. + /// If succeeded, then the stream isn't yet woken and not being polled at the moment. + fn start_waking(&self) -> Option<(u8, PollStateBomb<'_, impl FnOnce(&SharedPollState) -> u8>)> { + self.poll_state.start_waking(self.need_to_poll) + } +} + +impl ArcWake for WrappedWaker { + fn wake_by_ref(self_arc: &Arc) { + if let Some((_, state_bomb)) = self_arc.start_waking() { + // Safety: now state is not `POLLING` + let waker_opt = unsafe { self_arc.inner_waker.get().as_ref().unwrap() }; + + if let Some(inner_waker) = waker_opt.clone() { + // Stop waking to allow polling stream + drop(state_bomb); + + // Wake up inner waker + inner_waker.wake(); + } + } + } +} + +pin_project! { + /// Future which polls optional inner stream. + /// + /// If it's `Some`, it will attempt to call `poll_next` on it, + /// returning `Some((item, next_item_fut))` in case of `Poll::Ready(Some(...))` + /// or `None` in case of `Poll::Ready(None)`. + /// + /// If `poll_next` will return `Poll::Pending`, it will be forwarded to + /// the future and current task will be notified by waker. + #[must_use = "futures do nothing unless you `.await` or poll them"] + struct PollStreamFut { + #[pin] + stream: Option, + } +} + +impl PollStreamFut { + /// Constructs new `PollStreamFut` using given `stream`. + fn new(stream: impl Into>) -> Self { + Self { stream: stream.into() } + } +} + +impl Future for PollStreamFut { + type Output = Option<(St::Item, Self)>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let mut stream = self.project().stream; + + let item = if let Some(stream) = stream.as_mut().as_pin_mut() { + ready!(stream.poll_next(cx)) + } else { + None + }; + let next_item_fut = Self::new(stream.get_mut().take()); + let out = item.map(|item| (item, next_item_fut)); + + Poll::Ready(out) + } +} + +pin_project! { + /// Stream for the [`flatten_unordered`](super::StreamExt::flatten_unordered) + /// method with ability to specify flow controller. + #[project = FlattenUnorderedWithFlowControllerProj] + #[must_use = "streams do nothing unless polled"] + pub struct FlattenUnorderedWithFlowController where St: Stream { + #[pin] + inner_streams: FuturesUnordered>, + #[pin] + stream: St, + poll_state: SharedPollState, + limit: Option, + is_stream_done: bool, + inner_streams_waker: Arc, + stream_waker: Arc, + flow_controller: PhantomData + } +} + +impl fmt::Debug for FlattenUnorderedWithFlowController +where + St: Stream + fmt::Debug, + St::Item: Stream + fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("FlattenUnorderedWithFlowController") + .field("poll_state", &self.poll_state) + .field("inner_streams", &self.inner_streams) + .field("limit", &self.limit) + .field("stream", &self.stream) + .field("is_stream_done", &self.is_stream_done) + .field("flow_controller", &self.flow_controller) + .finish() + } +} + +impl FlattenUnorderedWithFlowController +where + St: Stream, + Fc: FlowController::Item>, + St::Item: Stream + Unpin, +{ + pub(crate) fn new(stream: St, limit: Option) -> Self { + let poll_state = SharedPollState::new(NEED_TO_POLL_STREAM); + + Self { + inner_streams: FuturesUnordered::new(), + stream, + is_stream_done: false, + limit: limit.and_then(NonZeroUsize::new), + inner_streams_waker: Arc::new(WrappedWaker { + inner_waker: UnsafeCell::new(None), + poll_state: poll_state.clone(), + need_to_poll: NEED_TO_POLL_INNER_STREAMS, + }), + stream_waker: Arc::new(WrappedWaker { + inner_waker: UnsafeCell::new(None), + poll_state: poll_state.clone(), + need_to_poll: NEED_TO_POLL_STREAM, + }), + poll_state, + flow_controller: PhantomData, + } + } + + delegate_access_inner!(stream, St, ()); +} + +/// Returns the next flow step based on the received item. +pub trait FlowController { + /// Handles an item producing `FlowStep` describing the next flow step. + fn next_step(item: I) -> FlowStep; +} + +impl FlowController for () { + fn next_step(item: I) -> FlowStep { + FlowStep::Continue(item) + } +} + +/// Describes the next flow step. +#[derive(Debug, Clone)] +pub enum FlowStep { + /// Just yields an item and continues standard flow. + Continue(C), + /// Immediately returns an underlying item from the function. + Return(R), +} + +impl FlattenUnorderedWithFlowControllerProj<'_, St, Fc> +where + St: Stream, +{ + /// Checks if current `inner_streams` bucket size is greater than optional limit. + fn is_exceeded_limit(&self) -> bool { + self.limit.map_or(false, |limit| self.inner_streams.len() >= limit.get()) + } +} + +impl FusedStream for FlattenUnorderedWithFlowController +where + St: FusedStream, + Fc: FlowController::Item>, + St::Item: Stream + Unpin, +{ + fn is_terminated(&self) -> bool { + self.stream.is_terminated() && self.inner_streams.is_empty() + } +} + +impl Stream for FlattenUnorderedWithFlowController +where + St: Stream, + Fc: FlowController::Item>, + St::Item: Stream + Unpin, +{ + type Item = ::Item; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut next_item = None; + let mut need_to_poll_next = NONE; + + let mut this = self.as_mut().project(); + + // Attempt to start polling, in case some waker is holding the lock, wait in loop + let (mut poll_state_value, state_bomb) = loop { + if let Some(value) = this.poll_state.start_polling() { + break value; + } + }; + + // Safety: now state is `POLLING`. + unsafe { + WrappedWaker::replace_waker(this.stream_waker, cx); + WrappedWaker::replace_waker(this.inner_streams_waker, cx) + }; + + if poll_state_value & NEED_TO_POLL_STREAM != NONE { + let mut stream_waker = None; + + // Here we need to poll the base stream. + // + // To improve performance, we will attempt to place as many items as we can + // to the `FuturesUnordered` bucket before polling inner streams + loop { + if this.is_exceeded_limit() || *this.is_stream_done { + // We either exceeded the limit or the stream is exhausted + if !*this.is_stream_done { + // The stream needs to be polled in the next iteration + need_to_poll_next |= NEED_TO_POLL_STREAM; + } + + break; + } else { + let mut cx = Context::from_waker( + stream_waker.get_or_insert_with(|| waker(this.stream_waker.clone())), + ); + + match this.stream.as_mut().poll_next(&mut cx) { + Poll::Ready(Some(item)) => { + let next_item_fut = match Fc::next_step(item) { + // Propagates an item immediately (the main use-case is for errors) + FlowStep::Return(item) => { + need_to_poll_next |= NEED_TO_POLL_STREAM + | (poll_state_value & NEED_TO_POLL_INNER_STREAMS); + poll_state_value &= !NEED_TO_POLL_INNER_STREAMS; + + next_item = Some(item); + + break; + } + // Yields an item and continues processing (normal case) + FlowStep::Continue(inner_stream) => { + PollStreamFut::new(inner_stream) + } + }; + // Add new stream to the inner streams bucket + this.inner_streams.as_mut().push(next_item_fut); + // Inner streams must be polled afterward + poll_state_value |= NEED_TO_POLL_INNER_STREAMS; + } + Poll::Ready(None) => { + // Mark the base stream as done + *this.is_stream_done = true; + } + Poll::Pending => { + break; + } + } + } + } + } + + if poll_state_value & NEED_TO_POLL_INNER_STREAMS != NONE { + let inner_streams_waker = waker(this.inner_streams_waker.clone()); + let mut cx = Context::from_waker(&inner_streams_waker); + + match this.inner_streams.as_mut().poll_next(&mut cx) { + Poll::Ready(Some(Some((item, next_item_fut)))) => { + // Push next inner stream item future to the list of inner streams futures + this.inner_streams.as_mut().push(next_item_fut); + // Take the received item + next_item = Some(item); + // On the next iteration, inner streams must be polled again + need_to_poll_next |= NEED_TO_POLL_INNER_STREAMS; + } + Poll::Ready(Some(None)) => { + // On the next iteration, inner streams must be polled again + need_to_poll_next |= NEED_TO_POLL_INNER_STREAMS; + } + _ => {} + } + } + + // We didn't have any `poll_next` panic, so it's time to deactivate the bomb + state_bomb.deactivate(); + + // Call the waker at the end of polling if + let mut force_wake = + // we need to poll the stream and didn't reach the limit yet + need_to_poll_next & NEED_TO_POLL_STREAM != NONE && !this.is_exceeded_limit() + // or we need to poll the inner streams again + || need_to_poll_next & NEED_TO_POLL_INNER_STREAMS != NONE; + + // Stop polling and swap the latest state + poll_state_value = this.poll_state.stop_polling(need_to_poll_next, force_wake); + // If state was changed during `POLLING` phase, we also need to manually call a waker + force_wake |= poll_state_value & NEED_TO_POLL_ALL != NONE; + + let is_done = *this.is_stream_done && this.inner_streams.is_empty(); + + if next_item.is_some() || is_done { + Poll::Ready(next_item) + } else { + if force_wake { + cx.waker().wake_by_ref(); + } + + Poll::Pending + } + } +} + +// Forwarding impl of Sink from the underlying stream +#[cfg(feature = "sink")] +impl Sink for FlattenUnorderedWithFlowController +where + St: Stream + Sink, +{ + type Error = St::Error; + + delegate_sink!(stream, Item); +} diff --git a/futures-util/src/stream/stream/fold.rs b/futures-util/src/stream/stream/fold.rs index e109c3bdcf..b8b55ecb67 100644 --- a/futures-util/src/stream/stream/fold.rs +++ b/futures-util/src/stream/stream/fold.rs @@ -35,24 +35,21 @@ where } impl Fold -where St: Stream, - F: FnMut(T, St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(T, St::Item) -> Fut, + Fut: Future, { pub(super) fn new(stream: St, f: F, t: T) -> Self { - Self { - stream, - f, - accum: Some(t), - future: None, - } + Self { stream, f, accum: Some(t), future: None } } } impl FusedFuture for Fold - where St: Stream, - F: FnMut(T, St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(T, St::Item) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.accum.is_none() && self.future.is_none() @@ -60,9 +57,10 @@ impl FusedFuture for Fold } impl Future for Fold - where St: Stream, - F: FnMut(T, St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(T, St::Item) -> Fut, + Fut: Future, { type Output = T; diff --git a/futures-util/src/stream/stream/for_each.rs b/futures-util/src/stream/stream/for_each.rs index ee90e66610..5302b0e034 100644 --- a/futures-util/src/stream/stream/for_each.rs +++ b/futures-util/src/stream/stream/for_each.rs @@ -32,23 +32,21 @@ where } impl ForEach -where St: Stream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - future: None, - } + Self { stream, f, future: None } } } impl FusedFuture for ForEach - where St: FusedStream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: FusedStream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.future.is_none() && self.stream.is_terminated() @@ -56,9 +54,10 @@ impl FusedFuture for ForEach } impl Future for ForEach - where St: Stream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { type Output = (); diff --git a/futures-util/src/stream/stream/for_each_concurrent.rs b/futures-util/src/stream/stream/for_each_concurrent.rs index cee0ba197e..6c18753eb9 100644 --- a/futures-util/src/stream/stream/for_each_concurrent.rs +++ b/futures-util/src/stream/stream/for_each_concurrent.rs @@ -1,7 +1,7 @@ use crate::stream::{FuturesUnordered, StreamExt}; use core::fmt; -use core::pin::Pin; use core::num::NonZeroUsize; +use core::pin::Pin; use futures_core::future::{FusedFuture, Future}; use futures_core::stream::Stream; use futures_core::task::{Context, Poll}; @@ -35,9 +35,10 @@ where } impl ForEachConcurrent -where St: Stream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { pub(super) fn new(stream: St, limit: Option, f: F) -> Self { Self { @@ -51,9 +52,10 @@ where St: Stream, } impl FusedFuture for ForEachConcurrent - where St: Stream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.stream.is_none() && self.futures.is_empty() @@ -61,9 +63,10 @@ impl FusedFuture for ForEachConcurrent } impl Future for ForEachConcurrent - where St: Stream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { type Output = (); @@ -80,7 +83,7 @@ impl Future for ForEachConcurrent Poll::Ready(Some(elem)) => { made_progress_this_iter = true; Some(elem) - }, + } Poll::Ready(None) => { stream_completed = true; None @@ -102,9 +105,9 @@ impl Future for ForEachConcurrent Poll::Ready(Some(())) => made_progress_this_iter = true, Poll::Ready(None) => { if this.stream.is_none() { - return Poll::Ready(()) + return Poll::Ready(()); } - }, + } Poll::Pending => {} } diff --git a/futures-util/src/stream/stream/forward.rs b/futures-util/src/stream/stream/forward.rs index 2247b21e97..1fe24273aa 100644 --- a/futures-util/src/stream/stream/forward.rs +++ b/futures-util/src/stream/stream/forward.rs @@ -23,11 +23,7 @@ pin_project! { impl Forward { pub(crate) fn new(stream: St, sink: Si) -> Self { - Self { - sink: Some(sink), - stream: Fuse::new(stream), - buffered_item: None, - } + Self { sink: Some(sink), stream: Fuse::new(stream), buffered_item: None } } } @@ -48,10 +44,7 @@ where { type Output = Result<(), E>; - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let ForwardProj { mut sink, mut stream, buffered_item } = self.project(); let mut si = sink.as_mut().as_pin_mut().expect("polled `Forward` after completion"); @@ -70,11 +63,11 @@ where Poll::Ready(None) => { ready!(si.poll_close(cx))?; sink.set(None); - return Poll::Ready(Ok(())) + return Poll::Ready(Ok(())); } Poll::Pending => { ready!(si.poll_flush(cx))?; - return Poll::Pending + return Poll::Pending; } } } diff --git a/futures-util/src/stream/stream/fuse.rs b/futures-util/src/stream/stream/fuse.rs index e1d8c122b3..fe67813e81 100644 --- a/futures-util/src/stream/stream/fuse.rs +++ b/futures-util/src/stream/stream/fuse.rs @@ -43,10 +43,7 @@ impl FusedStream for Fuse { impl Stream for Fuse { type Item = S::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.project(); if *this.done { diff --git a/futures-util/src/stream/stream/into_future.rs b/futures-util/src/stream/stream/into_future.rs index a9a1e2374a..8abfddcccd 100644 --- a/futures-util/src/stream/stream/into_future.rs +++ b/futures-util/src/stream/stream/into_future.rs @@ -79,10 +79,7 @@ impl FusedFuture for StreamFuture { impl Future for StreamFuture { type Output = (Option, St); - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let item = { let s = self.stream.as_mut().expect("polling StreamFuture twice"); ready!(s.poll_next_unpin(cx)) diff --git a/futures-util/src/stream/stream/map.rs b/futures-util/src/stream/stream/map.rs index 1a269f0c30..88bb6129d4 100644 --- a/futures-util/src/stream/stream/map.rs +++ b/futures-util/src/stream/stream/map.rs @@ -24,9 +24,7 @@ where St: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Map") - .field("stream", &self.stream) - .finish() + f.debug_struct("Map").field("stream", &self.stream).finish() } } @@ -39,8 +37,9 @@ impl Map { } impl FusedStream for Map - where St: FusedStream, - F: FnMut1, +where + St: FusedStream, + F: FnMut1, { fn is_terminated(&self) -> bool { self.stream.is_terminated() @@ -48,15 +47,13 @@ impl FusedStream for Map } impl Stream for Map - where St: Stream, - F: FnMut1, +where + St: Stream, + F: FnMut1, { type Item = F::Output; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); let res = ready!(this.stream.as_mut().poll_next(cx)); Poll::Ready(res.map(|x| this.f.call_mut(x))) @@ -70,8 +67,9 @@ impl Stream for Map // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for Map - where St: Stream + Sink, - F: FnMut1, +where + St: Stream + Sink, + F: FnMut1, { type Error = St::Error; diff --git a/futures-util/src/stream/stream/mod.rs b/futures-util/src/stream/stream/mod.rs index c3340ecc65..d365cfe43f 100644 --- a/futures-util/src/stream/stream/mod.rs +++ b/futures-util/src/stream/stream/mod.rs @@ -40,6 +40,10 @@ mod concat; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::concat::Concat; +mod count; +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::count::Count; + mod cycle; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::cycle::Cycle; @@ -70,6 +74,14 @@ mod fold; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::fold::Fold; +mod any; +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::any::Any; + +mod all; +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::all::All; + #[cfg(feature = "sink")] mod forward; @@ -123,7 +135,7 @@ pub use self::select_next_some::SelectNextSome; mod peek; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 -pub use self::peek::{Peek, Peekable}; +pub use self::peek::{NextIf, NextIfEq, Peek, PeekMut, Peekable}; mod skip; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 @@ -169,35 +181,60 @@ mod scan; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::scan::Scan; -cfg_target_has_atomic! { - #[cfg(feature = "alloc")] - mod buffer_unordered; - #[cfg(feature = "alloc")] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use self::buffer_unordered::BufferUnordered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod buffer_unordered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::buffer_unordered::BufferUnordered; - #[cfg(feature = "alloc")] - mod buffered; - #[cfg(feature = "alloc")] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use self::buffered::Buffered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod buffered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::buffered::Buffered; - #[cfg(feature = "alloc")] - mod for_each_concurrent; - #[cfg(feature = "alloc")] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use self::for_each_concurrent::ForEachConcurrent; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub(crate) mod flatten_unordered; - #[cfg(feature = "sink")] - #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] - #[cfg(feature = "alloc")] - mod split; - #[cfg(feature = "sink")] - #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] - #[cfg(feature = "alloc")] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use self::split::{SplitStream, SplitSink, ReuniteError}; -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] +pub use self::flatten_unordered::FlattenUnordered; + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +delegate_all!( + /// Stream for the [`flat_map_unordered`](StreamExt::flat_map_unordered) method. + FlatMapUnordered( + FlattenUnordered> + ): Debug + Sink + Stream + FusedStream + AccessInner[St, (. .)] + New[|x: St, limit: Option, f: F| FlattenUnordered::new(Map::new(x, f), limit)] + where St: Stream, U: Stream, U: Unpin, F: FnMut(St::Item) -> U +); + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod for_each_concurrent; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::for_each_concurrent::ForEachConcurrent; + +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "sink")] +#[cfg_attr(docsrs, doc(cfg(feature = "sink")))] +#[cfg(feature = "alloc")] +mod split; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "sink")] +#[cfg_attr(docsrs, doc(cfg(feature = "sink")))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::split::{ReuniteError, SplitSink, SplitStream}; #[cfg(feature = "std")] mod catch_unwind; @@ -286,6 +323,9 @@ pub trait StreamExt: Stream { /// wrapped version of it, similar to the existing `map` methods in the /// standard library. /// + /// See [`StreamExt::then`](Self::then) if you want to use a closure that + /// returns a future instead of a value. + /// /// # Examples /// /// ``` @@ -320,7 +360,7 @@ pub trait StreamExt: Stream { /// # Overflow Behavior /// /// The method does no guarding against overflows, so enumerating more than - /// [`prim@usize::max_value()`] elements either produces the wrong result or panics. If + /// [`usize::MAX`] elements either produces the wrong result or panics. If /// debug assertions are enabled, a panic is guaranteed. /// /// # Panics @@ -372,9 +412,9 @@ pub trait StreamExt: Stream { /// use futures::stream::{self, StreamExt}; /// /// let stream = stream::iter(1..=10); - /// let evens = stream.filter(|x| future::ready(x % 2 == 0)); + /// let events = stream.filter(|x| future::ready(x % 2 == 0)); /// - /// assert_eq!(vec![2, 4, 6, 8, 10], evens.collect::>().await); + /// assert_eq!(vec![2, 4, 6, 8, 10], events.collect::>().await); /// # }); /// ``` fn filter(self, f: F) -> Filter @@ -404,11 +444,11 @@ pub trait StreamExt: Stream { /// use futures::stream::{self, StreamExt}; /// /// let stream = stream::iter(1..=10); - /// let evens = stream.filter_map(|x| async move { + /// let events = stream.filter_map(|x| async move { /// if x % 2 == 0 { Some(x + 1) } else { None } /// }); /// - /// assert_eq!(vec![3, 5, 7, 9, 11], evens.collect::>().await); + /// assert_eq!(vec![3, 5, 7, 9, 11], events.collect::>().await); /// # }); /// ``` fn filter_map(self, f: F) -> FilterMap @@ -430,6 +470,9 @@ pub trait StreamExt: Stream { /// Note that this function consumes the stream passed into it and returns a /// wrapped version of it. /// + /// See [`StreamExt::map`](Self::map) if you want to use a closure that + /// returns a value instead of a future. + /// /// # Examples /// /// ``` @@ -562,6 +605,38 @@ pub trait StreamExt: Stream { assert_future::(Concat::new(self)) } + /// Drives the stream to completion, counting the number of items. + /// + /// # Overflow Behavior + /// + /// The method does no guarding against overflows, so counting elements of a + /// stream with more than [`usize::MAX`] elements either produces the wrong + /// result or panics. If debug assertions are enabled, a panic is guaranteed. + /// + /// # Panics + /// + /// This function might panic if the iterator has more than [`usize::MAX`] + /// elements. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// + /// let stream = stream::iter(1..=10); + /// let count = stream.count().await; + /// + /// assert_eq!(count, 10); + /// # }); + /// ``` + fn count(self) -> Count + where + Self: Sized, + { + assert_future::(Count::new(self)) + } + /// Repeats a stream endlessly. /// /// The stream never terminates. Note that you likely want to avoid @@ -621,6 +696,50 @@ pub trait StreamExt: Stream { assert_future::(Fold::new(self, f, init)) } + /// Execute predicate over asynchronous stream, and return `true` if any element in stream satisfied a predicate. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// + /// let number_stream = stream::iter(0..10); + /// let contain_three = number_stream.any(|i| async move { i == 3 }); + /// assert_eq!(contain_three.await, true); + /// # }); + /// ``` + fn any(self, f: F) -> Any + where + F: FnMut(Self::Item) -> Fut, + Fut: Future, + Self: Sized, + { + assert_future::(Any::new(self, f)) + } + + /// Execute predicate over asynchronous stream, and return `true` if all element in stream satisfied a predicate. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// + /// let number_stream = stream::iter(0..10); + /// let less_then_twenty = number_stream.all(|i| async move { i < 20 }); + /// assert_eq!(less_then_twenty.await, true); + /// # }); + /// ``` + fn all(self, f: F) -> All + where + F: FnMut(Self::Item) -> Fut, + Fut: Future, + Self: Sized, + { + assert_future::(All::new(self, f)) + } + /// Flattens a stream of streams into just one continuous stream. /// /// # Examples @@ -660,13 +779,64 @@ pub trait StreamExt: Stream { assert_stream::<::Item, _>(Flatten::new(self)) } + /// Flattens a stream of streams into just one continuous stream. Polls + /// inner streams produced by the base stream concurrently. + /// + /// The only argument is an optional limit on the number of concurrently + /// polled streams. If this limit is not `None`, no more than `limit` streams + /// will be polled at the same time. The `limit` argument is of type + /// `Into>`, and so can be provided as either `None`, + /// `Some(10)`, or just `10`. Note: a limit of zero is interpreted as + /// no limit at all, and will have the same result as passing in `None`. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::channel::mpsc; + /// use futures::stream::StreamExt; + /// use std::thread; + /// + /// let (tx1, rx1) = mpsc::unbounded(); + /// let (tx2, rx2) = mpsc::unbounded(); + /// let (tx3, rx3) = mpsc::unbounded(); + /// + /// thread::spawn(move || { + /// tx1.unbounded_send(1).unwrap(); + /// tx1.unbounded_send(2).unwrap(); + /// }); + /// thread::spawn(move || { + /// tx2.unbounded_send(3).unwrap(); + /// tx2.unbounded_send(4).unwrap(); + /// }); + /// thread::spawn(move || { + /// tx3.unbounded_send(rx1).unwrap(); + /// tx3.unbounded_send(rx2).unwrap(); + /// }); + /// + /// let mut output = rx3.flatten_unordered(None).collect::>().await; + /// output.sort(); + /// + /// assert_eq!(output, vec![1, 2, 3, 4]); + /// # }); + /// ``` + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + #[cfg(feature = "alloc")] + fn flatten_unordered(self, limit: impl Into>) -> FlattenUnordered + where + Self::Item: Stream + Unpin, + Self: Sized, + { + assert_stream::<::Item, _>(FlattenUnordered::new(self, limit.into())) + } + /// Maps a stream like [`StreamExt::map`] but flattens nested `Stream`s. /// /// [`StreamExt::map`] is very useful, but if it produces a `Stream` instead, /// you would have to chain combinators like `.map(f).flatten()` while this /// combinator provides ability to write `.flat_map(f)` instead of chaining. /// - /// The provided closure which produce inner streams is executed over all elements + /// The provided closure which produces inner streams is executed over all elements /// of stream as last inner stream is terminated and next stream item is available. /// /// Note that this function consumes the stream passed into it and returns a @@ -694,6 +864,59 @@ pub trait StreamExt: Stream { assert_stream::(FlatMap::new(self, f)) } + /// Maps a stream like [`StreamExt::map`] but flattens nested `Stream`s + /// and polls them concurrently, yielding items in any order, as they made + /// available. + /// + /// [`StreamExt::map`] is very useful, but if it produces `Stream`s + /// instead, and you need to poll all of them concurrently, you would + /// have to use something like `for_each_concurrent` and merge values + /// by hand. This combinator provides ability to collect all values + /// from concurrently polled streams into one stream. + /// + /// The first argument is an optional limit on the number of concurrently + /// polled streams. If this limit is not `None`, no more than `limit` streams + /// will be polled at the same time. The `limit` argument is of type + /// `Into>`, and so can be provided as either `None`, + /// `Some(10)`, or just `10`. Note: a limit of zero is interpreted as + /// no limit at all, and will have the same result as passing in `None`. + /// + /// The provided closure which produces inner streams is executed over + /// all elements of stream as next stream item is available and limit + /// of concurrently processed streams isn't exceeded. + /// + /// Note that this function consumes the stream passed into it and + /// returns a wrapped version of it. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// + /// let stream = stream::iter(1..5); + /// let stream = stream.flat_map_unordered(1, |x| stream::iter(vec![x; x])); + /// let mut values = stream.collect::>().await; + /// values.sort(); + /// + /// assert_eq!(vec![1usize, 2, 2, 3, 3, 3, 4, 4, 4, 4], values); + /// # }); + /// ``` + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + #[cfg(feature = "alloc")] + fn flat_map_unordered( + self, + limit: impl Into>, + f: F, + ) -> FlatMapUnordered + where + U: Stream + Unpin, + F: FnMut(Self::Item) -> U, + Self: Sized, + { + assert_stream::(FlatMapUnordered::new(self, limit.into(), f)) + } + /// Combinator similar to [`StreamExt::fold`] that holds internal state /// and produces a new stream. /// @@ -919,7 +1142,7 @@ pub trait StreamExt: Stream { /// fut.await; /// # }) /// ``` - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] fn for_each_concurrent( self, @@ -1142,7 +1365,7 @@ pub trait StreamExt: Stream { /// /// This method is only available when the `std` or `alloc` feature of this /// library is activated, and it is activated by default. - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] fn buffered(self, n: usize) -> Buffered where @@ -1187,7 +1410,7 @@ pub trait StreamExt: Stream { /// assert_eq!(buffered.next().await, None); /// # Ok::<(), i32>(()) }).unwrap(); /// ``` - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] fn buffer_unordered(self, n: usize) -> BufferUnordered where @@ -1303,8 +1526,7 @@ pub trait StreamExt: Stream { /// be immediately returned. /// /// If the underlying stream ended and only a partial vector was created, - /// it'll be returned. Additionally if an error happens from the underlying - /// stream then the currently buffered items will be yielded. + /// it will be returned. /// /// This method is only available when the `std` or `alloc` feature of this /// library is activated, and it is activated by default. @@ -1329,7 +1551,8 @@ pub trait StreamExt: Stream { /// the sink is closed. Note that neither the original stream nor provided /// sink will be output by this future. Pass the sink by `Pin<&mut S>` /// (for example, via `forward(&mut sink)` inside an `async` fn/block) in - /// order to preserve access to the `Sink`. + /// order to preserve access to the `Sink`. If the stream produces an error, + /// that error will be returned by this future without flushing/closing the sink. #[cfg(feature = "sink")] #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] fn forward(self, sink: S) -> Forward @@ -1354,7 +1577,7 @@ pub trait StreamExt: Stream { /// library is activated, and it is activated by default. #[cfg(feature = "sink")] #[cfg_attr(docsrs, doc(cfg(feature = "sink")))] - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] fn split(self) -> (SplitSink, SplitStream) where @@ -1463,6 +1686,8 @@ pub trait StreamExt: Stream { /// assert_eq!(total, 6); /// # }); /// ``` + /// + /// [`select!`]: crate::select fn select_next_some(&mut self) -> SelectNextSome<'_, Self> where Self: Unpin + FusedStream, diff --git a/futures-util/src/stream/stream/next.rs b/futures-util/src/stream/stream/next.rs index 6949878bef..8d8347aa03 100644 --- a/futures-util/src/stream/stream/next.rs +++ b/futures-util/src/stream/stream/next.rs @@ -28,10 +28,7 @@ impl FusedFuture for Next<'_, St> { impl Future for Next<'_, St> { type Output = Option; - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.stream.poll_next_unpin(cx) } } diff --git a/futures-util/src/stream/stream/peek.rs b/futures-util/src/stream/stream/peek.rs index a4031102ab..ea3d6243f3 100644 --- a/futures-util/src/stream/stream/peek.rs +++ b/futures-util/src/stream/stream/peek.rs @@ -1,5 +1,7 @@ +use crate::fns::FnOnce1; use crate::stream::{Fuse, StreamExt}; use core::fmt; +use core::marker::PhantomData; use core::pin::Pin; use futures_core::future::{FusedFuture, Future}; use futures_core::ready; @@ -26,15 +28,12 @@ pin_project! { impl Peekable { pub(super) fn new(stream: St) -> Self { - Self { - stream: stream.fuse(), - peeked: None, - } + Self { stream: stream.fuse(), peeked: None } } delegate_access_inner!(stream, St, (.)); - /// Produces a `Peek` future which retrieves a reference to the next item + /// Produces a future which retrieves a reference to the next item /// in the stream, or `None` if the underlying stream terminates. pub fn peek(self: Pin<&mut Self>) -> Peek<'_, St> { Peek { inner: Some(self) } @@ -44,15 +43,60 @@ impl Peekable { /// /// This method polls the underlying stream and return either a reference /// to the next item if the stream is ready or passes through any errors. - pub fn poll_peek( + pub fn poll_peek(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + + Poll::Ready(loop { + if this.peeked.is_some() { + break this.peeked.as_ref(); + } else if let Some(item) = ready!(this.stream.as_mut().poll_next(cx)) { + *this.peeked = Some(item); + } else { + break None; + } + }) + } + + /// Produces a future which retrieves a mutable reference to the next item + /// in the stream, or `None` if the underlying stream terminates. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// use futures::pin_mut; + /// + /// let stream = stream::iter(vec![1, 2, 3]).peekable(); + /// pin_mut!(stream); + /// + /// assert_eq!(stream.as_mut().peek_mut().await, Some(&mut 1)); + /// assert_eq!(stream.as_mut().next().await, Some(1)); + /// + /// // Peek into the stream and modify the value which will be returned next + /// if let Some(p) = stream.as_mut().peek_mut().await { + /// if *p == 2 { + /// *p = 5; + /// } + /// } + /// + /// assert_eq!(stream.collect::>().await, vec![5, 3]); + /// # }); + /// ``` + pub fn peek_mut(self: Pin<&mut Self>) -> PeekMut<'_, St> { + PeekMut { inner: Some(self) } + } + + /// Peek retrieves a mutable reference to the next item in the stream. + pub fn poll_peek_mut( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Poll> { + ) -> Poll> { let mut this = self.project(); Poll::Ready(loop { if this.peeked.is_some() { - break this.peeked.as_ref(); + break this.peeked.as_mut(); } else if let Some(item) = ready!(this.stream.as_mut().poll_next(cx)) { *this.peeked = Some(item); } else { @@ -60,6 +104,86 @@ impl Peekable { } }) } + + /// Creates a future which will consume and return the next value of this + /// stream if a condition is true. + /// + /// If `func` returns `true` for the next value of this stream, consume and + /// return it. Otherwise, return `None`. + /// + /// # Examples + /// + /// Consume a number if it's equal to 0. + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// use futures::pin_mut; + /// + /// let stream = stream::iter(0..5).peekable(); + /// pin_mut!(stream); + /// // The first item of the stream is 0; consume it. + /// assert_eq!(stream.as_mut().next_if(|&x| x == 0).await, Some(0)); + /// // The next item returned is now 1, so `consume` will return `false`. + /// assert_eq!(stream.as_mut().next_if(|&x| x == 0).await, None); + /// // `next_if` saves the value of the next item if it was not equal to `expected`. + /// assert_eq!(stream.next().await, Some(1)); + /// # }); + /// ``` + /// + /// Consume any number less than 10. + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// use futures::pin_mut; + /// + /// let stream = stream::iter(1..20).peekable(); + /// pin_mut!(stream); + /// // Consume all numbers less than 10 + /// while stream.as_mut().next_if(|&x| x < 10).await.is_some() {} + /// // The next value returned will be 10 + /// assert_eq!(stream.next().await, Some(10)); + /// # }); + /// ``` + pub fn next_if(self: Pin<&mut Self>, func: F) -> NextIf<'_, St, F> + where + F: FnOnce(&St::Item) -> bool, + { + NextIf { inner: Some((self, func)) } + } + + /// Creates a future which will consume and return the next item if it is + /// equal to `expected`. + /// + /// # Example + /// + /// Consume a number if it's equal to 0. + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt}; + /// use futures::pin_mut; + /// + /// let stream = stream::iter(0..5).peekable(); + /// pin_mut!(stream); + /// // The first item of the stream is 0; consume it. + /// assert_eq!(stream.as_mut().next_if_eq(&0).await, Some(0)); + /// // The next item returned is now 1, so `consume` will return `false`. + /// assert_eq!(stream.as_mut().next_if_eq(&0).await, None); + /// // `next_if_eq` saves the value of the next item if it was not equal to `expected`. + /// assert_eq!(stream.next().await, Some(1)); + /// # }); + /// ``` + pub fn next_if_eq<'a, T>(self: Pin<&'a mut Self>, expected: &'a T) -> NextIfEq<'a, St, T> + where + T: ?Sized, + St::Item: PartialEq, + { + NextIfEq { + inner: NextIf { inner: Some((self, NextIfEqFn { expected, _next: PhantomData })) }, + } + } } impl FusedStream for Peekable { @@ -80,7 +204,7 @@ impl Stream for Peekable { } fn size_hint(&self) -> (usize, Option) { - let peek_len = if self.peeked.is_some() { 1 } else { 0 }; + let peek_len = usize::from(self.peeked.is_some()); let (lower, upper) = self.stream.size_hint(); let lower = lower.saturating_add(peek_len); let upper = match upper { @@ -103,7 +227,7 @@ where } pin_project! { - /// Future for the [`Peekable::peek()`](self::Peekable::peek) function from [`Peekable`] + /// Future for the [`Peekable::peek`](self::Peekable::peek) method. #[must_use = "futures do nothing unless polled"] pub struct Peek<'a, St: Stream> { inner: Option>>, @@ -116,9 +240,7 @@ where St::Item: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Peek") - .field("inner", &self.inner) - .finish() + f.debug_struct("Peek").field("inner", &self.inner).finish() } } @@ -133,6 +255,7 @@ where St: Stream, { type Output = Option<&'a St::Item>; + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let inner = self.project().inner; if let Some(peekable) = inner { @@ -144,3 +267,167 @@ where } } } + +pin_project! { + /// Future for the [`Peekable::peek_mut`](self::Peekable::peek_mut) method. + #[must_use = "futures do nothing unless polled"] + pub struct PeekMut<'a, St: Stream> { + inner: Option>>, + } +} + +impl fmt::Debug for PeekMut<'_, St> +where + St: Stream + fmt::Debug, + St::Item: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("PeekMut").field("inner", &self.inner).finish() + } +} + +impl FusedFuture for PeekMut<'_, St> { + fn is_terminated(&self) -> bool { + self.inner.is_none() + } +} + +impl<'a, St> Future for PeekMut<'a, St> +where + St: Stream, +{ + type Output = Option<&'a mut St::Item>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let inner = self.project().inner; + if let Some(peekable) = inner { + ready!(peekable.as_mut().poll_peek_mut(cx)); + + inner.take().unwrap().poll_peek_mut(cx) + } else { + panic!("PeekMut polled after completion") + } + } +} + +pin_project! { + /// Future for the [`Peekable::next_if`](self::Peekable::next_if) method. + #[must_use = "futures do nothing unless polled"] + pub struct NextIf<'a, St: Stream, F> { + inner: Option<(Pin<&'a mut Peekable>, F)>, + } +} + +impl fmt::Debug for NextIf<'_, St, F> +where + St: Stream + fmt::Debug, + St::Item: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NextIf").field("inner", &self.inner.as_ref().map(|(s, _f)| s)).finish() + } +} + +#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 +impl FusedFuture for NextIf<'_, St, F> +where + St: Stream, + F: for<'a> FnOnce1<&'a St::Item, Output = bool>, +{ + fn is_terminated(&self) -> bool { + self.inner.is_none() + } +} + +#[allow(single_use_lifetimes)] // https://github.com/rust-lang/rust/issues/55058 +impl Future for NextIf<'_, St, F> +where + St: Stream, + F: for<'a> FnOnce1<&'a St::Item, Output = bool>, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let inner = self.project().inner; + if let Some((peekable, _)) = inner { + let res = ready!(peekable.as_mut().poll_next(cx)); + + let (peekable, func) = inner.take().unwrap(); + match res { + Some(ref matched) if func.call_once(matched) => Poll::Ready(res), + other => { + let peekable = peekable.project(); + // Since we called `self.next()`, we consumed `self.peeked`. + assert!(peekable.peeked.is_none()); + *peekable.peeked = other; + Poll::Ready(None) + } + } + } else { + panic!("NextIf polled after completion") + } + } +} + +pin_project! { + /// Future for the [`Peekable::next_if_eq`](self::Peekable::next_if_eq) method. + #[must_use = "futures do nothing unless polled"] + pub struct NextIfEq<'a, St: Stream, T: ?Sized> { + #[pin] + inner: NextIf<'a, St, NextIfEqFn<'a, T, St::Item>>, + } +} + +impl fmt::Debug for NextIfEq<'_, St, T> +where + St: Stream + fmt::Debug, + St::Item: fmt::Debug, + T: ?Sized, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("NextIfEq") + .field("inner", &self.inner.inner.as_ref().map(|(s, _f)| s)) + .finish() + } +} + +impl FusedFuture for NextIfEq<'_, St, T> +where + St: Stream, + T: ?Sized, + St::Item: PartialEq, +{ + fn is_terminated(&self) -> bool { + self.inner.is_terminated() + } +} + +impl Future for NextIfEq<'_, St, T> +where + St: Stream, + T: ?Sized, + St::Item: PartialEq, +{ + type Output = Option; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project().inner.poll(cx) + } +} + +struct NextIfEqFn<'a, T: ?Sized, Item> { + expected: &'a T, + _next: PhantomData, +} + +impl FnOnce1<&Item> for NextIfEqFn<'_, T, Item> +where + T: ?Sized, + Item: PartialEq, +{ + type Output = bool; + + fn call_once(self, next: &Item) -> Self::Output { + next == self.expected + } +} diff --git a/futures-util/src/stream/stream/ready_chunks.rs b/futures-util/src/stream/stream/ready_chunks.rs index b6e3e5c2de..192054c4ae 100644 --- a/futures-util/src/stream/stream/ready_chunks.rs +++ b/futures-util/src/stream/stream/ready_chunks.rs @@ -1,12 +1,11 @@ -use crate::stream::Fuse; -use futures_core::stream::{Stream, FusedStream}; +use crate::stream::{Fuse, StreamExt}; +use alloc::vec::Vec; +use core::pin::Pin; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use core::mem; -use core::pin::Pin; -use alloc::vec::Vec; pin_project! { /// Stream for the [`ready_chunks`](super::StreamExt::ready_chunks) method. @@ -15,20 +14,15 @@ pin_project! { pub struct ReadyChunks { #[pin] stream: Fuse, - items: Vec, cap: usize, // https://github.com/rust-lang/futures-rs/issues/1475 } } -impl ReadyChunks where St: Stream { +impl ReadyChunks { pub(super) fn new(stream: St, capacity: usize) -> Self { assert!(capacity > 0); - Self { - stream: super::Fuse::new(stream), - items: Vec::with_capacity(capacity), - cap: capacity, - } + Self { stream: stream.fuse(), cap: capacity } } delegate_access_inner!(stream, St, (.)); @@ -37,43 +31,36 @@ impl ReadyChunks where St: Stream { impl Stream for ReadyChunks { type Item = Vec; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); + let mut items: Vec = Vec::new(); + loop { match this.stream.as_mut().poll_next(cx) { // Flush all collected data if underlying stream doesn't contain // more ready values Poll::Pending => { - return if this.items.is_empty() { - Poll::Pending - } else { - Poll::Ready(Some(mem::replace(this.items, Vec::with_capacity(*this.cap)))) - } + return if items.is_empty() { Poll::Pending } else { Poll::Ready(Some(items)) } } // Push the ready item into the buffer and check whether it is full. // If so, replace our buffer with a new and empty one and return // the full one. Poll::Ready(Some(item)) => { - this.items.push(item); - if this.items.len() >= *this.cap { - return Poll::Ready(Some(mem::replace(this.items, Vec::with_capacity(*this.cap)))) + if items.is_empty() { + items.reserve(*this.cap); + } + items.push(item); + if items.len() >= *this.cap { + return Poll::Ready(Some(items)); } } // Since the underlying stream ran out of values, return what we // have buffered, if we have anything. Poll::Ready(None) => { - let last = if this.items.is_empty() { - None - } else { - let full_buf = mem::replace(this.items, Vec::new()); - Some(full_buf) - }; + let last = if items.is_empty() { None } else { Some(items) }; return Poll::Ready(last); } @@ -82,20 +69,15 @@ impl Stream for ReadyChunks { } fn size_hint(&self) -> (usize, Option) { - let chunk_len = if self.items.is_empty() { 0 } else { 1 }; let (lower, upper) = self.stream.size_hint(); - let lower = lower.saturating_add(chunk_len); - let upper = match upper { - Some(x) => x.checked_add(chunk_len), - None => None, - }; + let lower = lower / self.cap; (lower, upper) } } -impl FusedStream for ReadyChunks { +impl FusedStream for ReadyChunks { fn is_terminated(&self) -> bool { - self.stream.is_terminated() && self.items.is_empty() + self.stream.is_terminated() } } diff --git a/futures-util/src/stream/stream/scan.rs b/futures-util/src/stream/stream/scan.rs index 20972807ce..f5cfde9c36 100644 --- a/futures-util/src/stream/stream/scan.rs +++ b/futures-util/src/stream/stream/scan.rs @@ -56,14 +56,7 @@ where Fut: Future>, { pub(super) fn new(stream: St, initial_state: S, f: F) -> Self { - Self { - stream, - state_f: Some(StateFn { - state: initial_state, - f, - }), - future: None, - } + Self { stream, state_f: Some(StateFn { state: initial_state, f }), future: None } } delegate_access_inner!(stream, St, ()); @@ -125,11 +118,11 @@ where // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] -impl Sink for Scan +impl Sink for Scan where - S: Stream + Sink, + St: Stream + Sink, { - type Error = S::Error; + type Error = St::Error; delegate_sink!(stream, Item); } diff --git a/futures-util/src/stream/stream/select_next_some.rs b/futures-util/src/stream/stream/select_next_some.rs index fe7a089899..3115e14d9a 100644 --- a/futures-util/src/stream/stream/select_next_some.rs +++ b/futures-util/src/stream/stream/select_next_some.rs @@ -1,9 +1,9 @@ +use crate::stream::StreamExt; use core::pin::Pin; +use futures_core::future::{FusedFuture, Future}; use futures_core::ready; use futures_core::stream::FusedStream; -use futures_core::future::{Future, FusedFuture}; use futures_core::task::{Context, Poll}; -use crate::stream::StreamExt; /// Future for the [`select_next_some`](super::StreamExt::select_next_some) /// method. diff --git a/futures-util/src/stream/stream/skip.rs b/futures-util/src/stream/stream/skip.rs index 6ffcf57d77..f495779521 100644 --- a/futures-util/src/stream/stream/skip.rs +++ b/futures-util/src/stream/stream/skip.rs @@ -19,10 +19,7 @@ pin_project! { impl Skip { pub(super) fn new(stream: St, n: usize) -> Self { - Self { - stream, - remaining: n, - } + Self { stream, remaining: n } } delegate_access_inner!(stream, St, ()); @@ -37,10 +34,7 @@ impl FusedStream for Skip { impl Stream for Skip { type Item = St::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); while *this.remaining > 0 { @@ -57,11 +51,8 @@ impl Stream for Skip { fn size_hint(&self) -> (usize, Option) { let (lower, upper) = self.stream.size_hint(); - let lower = lower.saturating_sub(self.remaining as usize); - let upper = match upper { - Some(x) => Some(x.saturating_sub(self.remaining as usize)), - None => None, - }; + let lower = lower.saturating_sub(self.remaining); + let upper = upper.map(|x| x.saturating_sub(self.remaining)); (lower, upper) } diff --git a/futures-util/src/stream/stream/skip_while.rs b/futures-util/src/stream/stream/skip_while.rs index e1aa3f904b..dabd5eefae 100644 --- a/futures-util/src/stream/stream/skip_while.rs +++ b/futures-util/src/stream/stream/skip_while.rs @@ -39,27 +39,23 @@ where } impl SkipWhile - where St: Stream, - F: FnMut(&St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(&St::Item) -> Fut, + Fut: Future, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - pending_fut: None, - pending_item: None, - done_skipping: false, - } + Self { stream, f, pending_fut: None, pending_item: None, done_skipping: false } } delegate_access_inner!(stream, St, ()); } impl FusedStream for SkipWhile - where St: FusedStream, - F: FnMut(&St::Item) -> Fut, - Fut: Future, +where + St: FusedStream, + F: FnMut(&St::Item) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.pending_item.is_none() && self.stream.is_terminated() @@ -67,16 +63,14 @@ impl FusedStream for SkipWhile } impl Stream for SkipWhile - where St: Stream, - F: FnMut(&St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(&St::Item) -> Fut, + Fut: Future, { type Item = St::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); if *this.done_skipping { @@ -105,7 +99,7 @@ impl Stream for SkipWhile if self.done_skipping { self.stream.size_hint() } else { - let pending_len = if self.pending_item.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending_item.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), @@ -119,9 +113,10 @@ impl Stream for SkipWhile // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for SkipWhile - where S: Stream + Sink, - F: FnMut(&S::Item) -> Fut, - Fut: Future, +where + S: Stream + Sink, + F: FnMut(&S::Item) -> Fut, + Fut: Future, { type Error = S::Error; diff --git a/futures-util/src/stream/stream/split.rs b/futures-util/src/stream/stream/split.rs index 997b9747a2..e192f8a92d 100644 --- a/futures-util/src/stream/stream/split.rs +++ b/futures-util/src/stream/stream/split.rs @@ -1,9 +1,9 @@ +use core::fmt; +use core::pin::Pin; use futures_core::ready; use futures_core::stream::Stream; use futures_core::task::{Context, Poll}; use futures_sink::Sink; -use core::fmt; -use core::pin::Pin; use crate::lock::BiLock; @@ -15,12 +15,20 @@ pub struct SplitStream(BiLock); impl Unpin for SplitStream {} +impl SplitStream { + /// Returns `true` if the `SplitStream` and `SplitSink` originate from the same call to `StreamExt::split`. + pub fn is_pair_of(&self, other: &SplitSink) -> bool { + other.is_pair_of(&self) + } +} + impl SplitStream { /// Attempts to put the two "halves" of a split `Stream + Sink` back /// together. Succeeds only if the `SplitStream` and `SplitSink` are /// a matching pair originating from the same call to `StreamExt::split`. pub fn reunite(self, other: SplitSink) -> Result> - where S: Sink, + where + S: Sink, { other.reunite(self) } @@ -34,12 +42,9 @@ impl Stream for SplitStream { } } -#[allow(bad_style)] +#[allow(non_snake_case)] fn SplitSink, Item>(lock: BiLock) -> SplitSink { - SplitSink { - lock, - slot: None, - } + SplitSink { lock, slot: None } } /// A `Sink` part of the split pair @@ -58,14 +63,23 @@ impl + Unpin, Item> SplitSink { /// together. Succeeds only if the `SplitStream` and `SplitSink` are /// a matching pair originating from the same call to `StreamExt::split`. pub fn reunite(self, other: SplitStream) -> Result> { - self.lock.reunite(other.0).map_err(|err| { - ReuniteError(SplitSink(err.0), SplitStream(err.1)) - }) + self.lock.reunite(other.0).map_err(|err| ReuniteError(SplitSink(err.0), SplitStream(err.1))) + } +} + +impl SplitSink { + /// Returns `true` if the `SplitStream` and `SplitSink` originate from the same call to `StreamExt::split`. + pub fn is_pair_of(&self, other: &SplitStream) -> bool { + self.lock.is_pair_of(&other.0) } } impl, Item> SplitSink { - fn poll_flush_slot(mut inner: Pin<&mut S>, slot: &mut Option, cx: &mut Context<'_>) -> Poll> { + fn poll_flush_slot( + mut inner: Pin<&mut S>, + slot: &mut Option, + cx: &mut Context<'_>, + ) -> Poll> { if slot.is_some() { ready!(inner.as_mut().poll_ready(cx))?; Poll::Ready(inner.start_send(slot.take().unwrap())) @@ -74,7 +88,10 @@ impl, Item> SplitSink { } } - fn poll_lock_and_flush_slot(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + fn poll_lock_and_flush_slot( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { let this = &mut *self; let mut inner = ready!(this.lock.poll_lock(cx)); Self::poll_flush_slot(inner.as_pin_mut(), &mut this.slot, cx) @@ -127,9 +144,7 @@ pub struct ReuniteError(pub SplitSink, pub SplitStream); impl fmt::Debug for ReuniteError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_tuple("ReuniteError") - .field(&"...") - .finish() + f.debug_tuple("ReuniteError").field(&"...").finish() } } @@ -141,3 +156,69 @@ impl fmt::Display for ReuniteError { #[cfg(feature = "std")] impl std::error::Error for ReuniteError {} + +#[cfg(test)] +mod tests { + use super::*; + use crate::stream::StreamExt; + use core::marker::PhantomData; + + struct NopStream { + phantom: PhantomData, + } + + impl Stream for NopStream { + type Item = Item; + + fn poll_next(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + todo!() + } + } + + impl Sink for NopStream { + type Error = (); + + fn poll_ready( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + todo!() + } + + fn start_send(self: Pin<&mut Self>, _item: Item) -> Result<(), Self::Error> { + todo!() + } + + fn poll_flush( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + todo!() + } + + fn poll_close( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + ) -> Poll> { + todo!() + } + } + + #[test] + fn test_pairing() { + let s1 = NopStream::<()> { phantom: PhantomData }; + let (sink1, stream1) = s1.split(); + assert!(sink1.is_pair_of(&stream1)); + assert!(stream1.is_pair_of(&sink1)); + + let s2 = NopStream::<()> { phantom: PhantomData }; + let (sink2, stream2) = s2.split(); + assert!(sink2.is_pair_of(&stream2)); + assert!(stream2.is_pair_of(&sink2)); + + assert!(!sink1.is_pair_of(&stream2)); + assert!(!stream1.is_pair_of(&sink2)); + assert!(!sink2.is_pair_of(&stream1)); + assert!(!stream2.is_pair_of(&sink1)); + } +} diff --git a/futures-util/src/stream/stream/take.rs b/futures-util/src/stream/stream/take.rs index 124d397c46..29d6c39ee3 100644 --- a/futures-util/src/stream/stream/take.rs +++ b/futures-util/src/stream/stream/take.rs @@ -1,7 +1,7 @@ use core::cmp; use core::pin::Pin; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; @@ -20,24 +20,19 @@ pin_project! { impl Take { pub(super) fn new(stream: St, n: usize) -> Self { - Self { - stream, - remaining: n, - } + Self { stream, remaining: n } } delegate_access_inner!(stream, St, ()); } impl Stream for Take - where St: Stream, +where + St: Stream, { type Item = St::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.remaining == 0 { Poll::Ready(None) } else { @@ -59,11 +54,11 @@ impl Stream for Take let (lower, upper) = self.stream.size_hint(); - let lower = cmp::min(lower, self.remaining as usize); + let lower = cmp::min(lower, self.remaining); let upper = match upper { - Some(x) if x < self.remaining as usize => Some(x), - _ => Some(self.remaining as usize) + Some(x) if x < self.remaining => Some(x), + _ => Some(self.remaining), }; (lower, upper) @@ -71,7 +66,8 @@ impl Stream for Take } impl FusedStream for Take - where St: FusedStream, +where + St: FusedStream, { fn is_terminated(&self) -> bool { self.remaining == 0 || self.stream.is_terminated() @@ -81,7 +77,8 @@ impl FusedStream for Take // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for Take - where S: Stream + Sink, +where + S: Stream + Sink, { type Error = S::Error; diff --git a/futures-util/src/stream/stream/take_until.rs b/futures-util/src/stream/stream/take_until.rs index 4dea01aae9..d14f9ce100 100644 --- a/futures-util/src/stream/stream/take_until.rs +++ b/futures-util/src/stream/stream/take_until.rs @@ -34,10 +34,7 @@ where Fut: Future + fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TakeUntil") - .field("stream", &self.stream) - .field("fut", &self.fut) - .finish() + f.debug_struct("TakeUntil").field("stream", &self.stream).field("fut", &self.fut).finish() } } @@ -47,12 +44,7 @@ where Fut: Future, { pub(super) fn new(stream: St, fut: Fut) -> Self { - Self { - stream, - fut: Some(fut), - fut_result: None, - free: false, - } + Self { stream, fut: Some(fut), fut_result: None, free: false } } delegate_access_inner!(stream, St, ()); diff --git a/futures-util/src/stream/stream/take_while.rs b/futures-util/src/stream/stream/take_while.rs index 4cdba83564..9256943010 100644 --- a/futures-util/src/stream/stream/take_while.rs +++ b/futures-util/src/stream/stream/take_while.rs @@ -2,7 +2,7 @@ use core::fmt; use core::pin::Pin; use futures_core::future::Future; use futures_core::ready; -use futures_core::stream::{Stream, FusedStream}; +use futures_core::stream::{FusedStream, Stream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; @@ -39,34 +39,27 @@ where } impl TakeWhile - where St: Stream, - F: FnMut(&St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(&St::Item) -> Fut, + Fut: Future, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - pending_fut: None, - pending_item: None, - done_taking: false, - } + Self { stream, f, pending_fut: None, pending_item: None, done_taking: false } } delegate_access_inner!(stream, St, ()); } impl Stream for TakeWhile - where St: Stream, - F: FnMut(&St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(&St::Item) -> Fut, + Fut: Future, { type Item = St::Item; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.done_taking { return Poll::Ready(None); } @@ -98,7 +91,7 @@ impl Stream for TakeWhile return (0, Some(0)); } - let pending_len = if self.pending_item.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending_item.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), @@ -109,9 +102,10 @@ impl Stream for TakeWhile } impl FusedStream for TakeWhile - where St: FusedStream, - F: FnMut(&St::Item) -> Fut, - Fut: Future, +where + St: FusedStream, + F: FnMut(&St::Item) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.done_taking || self.pending_item.is_none() && self.stream.is_terminated() @@ -121,7 +115,8 @@ impl FusedStream for TakeWhile // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for TakeWhile - where S: Stream + Sink, +where + S: Stream + Sink, { type Error = S::Error; diff --git a/futures-util/src/stream/stream/then.rs b/futures-util/src/stream/stream/then.rs index 3d42bdd5c2..9192c0b0cf 100644 --- a/futures-util/src/stream/stream/then.rs +++ b/futures-util/src/stream/stream/then.rs @@ -26,32 +26,27 @@ where Fut: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Then") - .field("stream", &self.stream) - .field("future", &self.future) - .finish() + f.debug_struct("Then").field("stream", &self.stream).field("future", &self.future).finish() } } impl Then - where St: Stream, - F: FnMut(St::Item) -> Fut, +where + St: Stream, + F: FnMut(St::Item) -> Fut, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - future: None, - f, - } + Self { stream, future: None, f } } delegate_access_inner!(stream, St, ()); } impl FusedStream for Then - where St: FusedStream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: FusedStream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.future.is_none() && self.stream.is_terminated() @@ -59,16 +54,14 @@ impl FusedStream for Then } impl Stream for Then - where St: Stream, - F: FnMut(St::Item) -> Fut, - Fut: Future, +where + St: Stream, + F: FnMut(St::Item) -> Fut, + Fut: Future, { type Item = Fut::Output; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); Poll::Ready(loop { @@ -85,7 +78,7 @@ impl Stream for Then } fn size_hint(&self) -> (usize, Option) { - let future_len = if self.future.is_some() { 1 } else { 0 }; + let future_len = usize::from(self.future.is_some()); let (lower, upper) = self.stream.size_hint(); let lower = lower.saturating_add(future_len); let upper = match upper { @@ -99,7 +92,8 @@ impl Stream for Then // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for Then - where S: Sink, +where + S: Sink, { type Error = S::Error; diff --git a/futures-util/src/stream/stream/unzip.rs b/futures-util/src/stream/stream/unzip.rs index 5024770769..a88cf03266 100644 --- a/futures-util/src/stream/stream/unzip.rs +++ b/futures-util/src/stream/stream/unzip.rs @@ -21,25 +21,19 @@ pin_project! { impl Unzip { fn finish(self: Pin<&mut Self>) -> (FromA, FromB) { let this = self.project(); - ( - mem::replace(this.left, Default::default()), - mem::replace(this.right, Default::default()), - ) + (mem::take(this.left), mem::take(this.right)) } pub(super) fn new(stream: St) -> Self { - Self { - stream, - left: Default::default(), - right: Default::default(), - } + Self { stream, left: Default::default(), right: Default::default() } } } impl FusedFuture for Unzip -where St: FusedStream, - FromA: Default + Extend, - FromB: Default + Extend, +where + St: FusedStream, + FromA: Default + Extend, + FromB: Default + Extend, { fn is_terminated(&self) -> bool { self.stream.is_terminated() @@ -47,9 +41,10 @@ where St: FusedStream, } impl Future for Unzip -where St: Stream, - FromA: Default + Extend, - FromB: Default + Extend, +where + St: Stream, + FromA: Default + Extend, + FromB: Default + Extend, { type Output = (FromA, FromB); @@ -60,7 +55,7 @@ where St: Stream, Some(e) => { this.left.extend(Some(e.0)); this.right.extend(Some(e.1)); - }, + } None => return Poll::Ready(self.finish()), } } diff --git a/futures-util/src/stream/stream/zip.rs b/futures-util/src/stream/stream/zip.rs index 588531a468..25a47e96be 100644 --- a/futures-util/src/stream/stream/zip.rs +++ b/futures-util/src/stream/stream/zip.rs @@ -1,4 +1,4 @@ -use crate::stream::{StreamExt, Fuse}; +use crate::stream::{Fuse, StreamExt}; use core::cmp; use core::pin::Pin; use futures_core::stream::{FusedStream, Stream}; @@ -21,12 +21,7 @@ pin_project! { impl Zip { pub(super) fn new(stream1: St1, stream2: St2) -> Self { - Self { - stream1: stream1.fuse(), - stream2: stream2.fuse(), - queued1: None, - queued2: None, - } + Self { stream1: stream1.fuse(), stream2: stream2.fuse(), queued1: None, queued2: None } } /// Acquires a reference to the underlying streams that this combinator is @@ -64,7 +59,9 @@ impl Zip { } impl FusedStream for Zip - where St1: Stream, St2: Stream, +where + St1: Stream, + St2: Stream, { fn is_terminated(&self) -> bool { self.stream1.is_terminated() && self.stream2.is_terminated() @@ -72,14 +69,13 @@ impl FusedStream for Zip } impl Stream for Zip - where St1: Stream, St2: Stream +where + St1: Stream, + St2: Stream, { type Item = (St1::Item, St2::Item); - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); if this.queued1.is_none() { @@ -106,8 +102,8 @@ impl Stream for Zip } fn size_hint(&self) -> (usize, Option) { - let queued1_len = if self.queued1.is_some() { 1 } else { 0 }; - let queued2_len = if self.queued2.is_some() { 1 } else { 0 }; + let queued1_len = usize::from(self.queued1.is_some()); + let queued2_len = usize::from(self.queued2.is_some()); let (stream1_lower, stream1_upper) = self.stream1.size_hint(); let (stream2_lower, stream2_upper) = self.stream2.size_hint(); @@ -124,7 +120,7 @@ impl Stream for Zip } (Some(x), None) => x.checked_add(queued1_len), (None, Some(y)) => y.checked_add(queued2_len), - (None, None) => None + (None, None) => None, }; (lower, upper) diff --git a/futures-util/src/stream/try_stream/and_then.rs b/futures-util/src/stream/try_stream/and_then.rs index b18564649a..2f8b6f2589 100644 --- a/futures-util/src/stream/try_stream/and_then.rs +++ b/futures-util/src/stream/try_stream/and_then.rs @@ -2,7 +2,7 @@ use core::fmt; use core::pin::Pin; use futures_core::future::TryFuture; use futures_core::ready; -use futures_core::stream::{Stream, TryStream, FusedStream}; +use futures_core::stream::{FusedStream, Stream, TryStream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; @@ -34,9 +34,10 @@ where } impl AndThen - where St: TryStream, - F: FnMut(St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: TryFuture, { pub(super) fn new(stream: St, f: F) -> Self { Self { stream, future: None, f } @@ -46,16 +47,14 @@ impl AndThen } impl Stream for AndThen - where St: TryStream, - F: FnMut(St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: TryFuture, { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); Poll::Ready(loop { @@ -72,7 +71,7 @@ impl Stream for AndThen } fn size_hint(&self) -> (usize, Option) { - let future_len = if self.future.is_some() { 1 } else { 0 }; + let future_len = usize::from(self.future.is_some()); let (lower, upper) = self.stream.size_hint(); let lower = lower.saturating_add(future_len); let upper = match upper { @@ -84,9 +83,10 @@ impl Stream for AndThen } impl FusedStream for AndThen - where St: TryStream + FusedStream, - F: FnMut(St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream + FusedStream, + F: FnMut(St::Ok) -> Fut, + Fut: TryFuture, { fn is_terminated(&self) -> bool { self.future.is_none() && self.stream.is_terminated() @@ -96,7 +96,8 @@ impl FusedStream for AndThen // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for AndThen - where S: Sink, +where + S: Sink, { type Error = S::Error; diff --git a/futures-util/src/stream/try_stream/into_async_read.rs b/futures-util/src/stream/try_stream/into_async_read.rs index 197c10502d..ffbfc7eae9 100644 --- a/futures-util/src/stream/try_stream/into_async_read.rs +++ b/futures-util/src/stream/try_stream/into_async_read.rs @@ -1,30 +1,26 @@ -use crate::stream::TryStreamExt; use core::pin::Pin; use futures_core::ready; use futures_core::stream::TryStream; use futures_core::task::{Context, Poll}; -use futures_io::{AsyncRead, AsyncWrite, AsyncBufRead}; +use futures_io::{AsyncBufRead, AsyncRead, AsyncWrite}; +use pin_project_lite::pin_project; use std::cmp; use std::io::{Error, Result}; -/// Reader for the [`into_async_read`](super::TryStreamExt::into_async_read) method. -#[derive(Debug)] -#[must_use = "readers do nothing unless polled"] -#[cfg_attr(docsrs, doc(cfg(feature = "io")))] -pub struct IntoAsyncRead -where - St: TryStream + Unpin, - St::Ok: AsRef<[u8]>, -{ - stream: St, - state: ReadState, -} - -impl Unpin for IntoAsyncRead -where - St: TryStream + Unpin, - St::Ok: AsRef<[u8]>, -{ +pin_project! { + /// Reader for the [`into_async_read`](super::TryStreamExt::into_async_read) method. + #[derive(Debug)] + #[must_use = "readers do nothing unless polled"] + #[cfg_attr(docsrs, doc(cfg(feature = "io")))] + pub struct IntoAsyncRead + where + St: TryStream, + St::Ok: AsRef<[u8]>, + { + #[pin] + stream: St, + state: ReadState, + } } #[derive(Debug)] @@ -36,64 +32,56 @@ enum ReadState> { impl IntoAsyncRead where - St: TryStream + Unpin, + St: TryStream, St::Ok: AsRef<[u8]>, { pub(super) fn new(stream: St) -> Self { - Self { - stream, - state: ReadState::PendingChunk, - } + Self { stream, state: ReadState::PendingChunk } } } impl AsyncRead for IntoAsyncRead where - St: TryStream + Unpin, + St: TryStream, St::Ok: AsRef<[u8]>, { fn poll_read( - mut self: Pin<&mut Self>, + self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8], ) -> Poll> { + let mut this = self.project(); + loop { - match &mut self.state { + match this.state { ReadState::Ready { chunk, chunk_start } => { let chunk = chunk.as_ref(); let len = cmp::min(buf.len(), chunk.len() - *chunk_start); - buf[..len].copy_from_slice( - &chunk[*chunk_start..*chunk_start + len], - ); + buf[..len].copy_from_slice(&chunk[*chunk_start..*chunk_start + len]); *chunk_start += len; if chunk.len() == *chunk_start { - self.state = ReadState::PendingChunk; + *this.state = ReadState::PendingChunk; } return Poll::Ready(Ok(len)); } - ReadState::PendingChunk => { - match ready!(self.stream.try_poll_next_unpin(cx)) { - Some(Ok(chunk)) => { - if !chunk.as_ref().is_empty() { - self.state = ReadState::Ready { - chunk, - chunk_start: 0, - }; - } - } - Some(Err(err)) => { - self.state = ReadState::Eof; - return Poll::Ready(Err(err)); - } - None => { - self.state = ReadState::Eof; - return Poll::Ready(Ok(0)); + ReadState::PendingChunk => match ready!(this.stream.as_mut().try_poll_next(cx)) { + Some(Ok(chunk)) => { + if !chunk.as_ref().is_empty() { + *this.state = ReadState::Ready { chunk, chunk_start: 0 }; } } - } + Some(Err(err)) => { + *this.state = ReadState::Eof; + return Poll::Ready(Err(err)); + } + None => { + *this.state = ReadState::Eof; + return Poll::Ready(Ok(0)); + } + }, ReadState::Eof => { return Poll::Ready(Ok(0)); } @@ -104,63 +92,52 @@ where impl AsyncWrite for IntoAsyncRead where - St: TryStream + AsyncWrite + Unpin, + St: TryStream + AsyncWrite, St::Ok: AsRef<[u8]>, { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8] - ) -> Poll> { - Pin::new( &mut self.stream ).poll_write( cx, buf ) + fn poll_write(self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &[u8]) -> Poll> { + let this = self.project(); + this.stream.poll_write(cx, buf) } - fn poll_flush( - mut self: Pin<&mut Self>, - cx: &mut Context<'_> - ) -> Poll> { - Pin::new( &mut self.stream ).poll_flush( cx ) + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.stream.poll_flush(cx) } - fn poll_close( - mut self: Pin<&mut Self>, - cx: &mut Context<'_> - ) -> Poll> { - Pin::new( &mut self.stream ).poll_close( cx ) + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let this = self.project(); + this.stream.poll_close(cx) } } impl AsyncBufRead for IntoAsyncRead where - St: TryStream + Unpin, + St: TryStream, St::Ok: AsRef<[u8]>, { - fn poll_fill_buf( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - while let ReadState::PendingChunk = self.state { - match ready!(self.stream.try_poll_next_unpin(cx)) { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + + while let ReadState::PendingChunk = this.state { + match ready!(this.stream.as_mut().try_poll_next(cx)) { Some(Ok(chunk)) => { if !chunk.as_ref().is_empty() { - self.state = ReadState::Ready { - chunk, - chunk_start: 0, - }; + *this.state = ReadState::Ready { chunk, chunk_start: 0 }; } } Some(Err(err)) => { - self.state = ReadState::Eof; + *this.state = ReadState::Eof; return Poll::Ready(Err(err)); } None => { - self.state = ReadState::Eof; + *this.state = ReadState::Eof; return Poll::Ready(Ok(&[])); } } } - if let ReadState::Ready { ref chunk, chunk_start } = self.into_ref().get_ref().state { + if let &mut ReadState::Ready { ref chunk, chunk_start } = this.state { let chunk = chunk.as_ref(); return Poll::Ready(Ok(&chunk[chunk_start..])); } @@ -169,17 +146,18 @@ where Poll::Ready(Ok(&[])) } - fn consume( - mut self: Pin<&mut Self>, - amount: usize, - ) { - // https://github.com/rust-lang/futures-rs/pull/1556#discussion_r281644295 - if amount == 0 { return } - if let ReadState::Ready { chunk, chunk_start } = &mut self.state { + fn consume(self: Pin<&mut Self>, amount: usize) { + let this = self.project(); + + // https://github.com/rust-lang/futures-rs/pull/1556#discussion_r281644295 + if amount == 0 { + return; + } + if let ReadState::Ready { chunk, chunk_start } = this.state { *chunk_start += amount; debug_assert!(*chunk_start <= chunk.as_ref().len()); if *chunk_start >= chunk.as_ref().len() { - self.state = ReadState::PendingChunk; + *this.state = ReadState::PendingChunk; } } else { debug_assert!(false, "Attempted to consume from IntoAsyncRead without chunk"); diff --git a/futures-util/src/stream/try_stream/into_stream.rs b/futures-util/src/stream/try_stream/into_stream.rs index 89bc3ef177..2126258af7 100644 --- a/futures-util/src/stream/try_stream/into_stream.rs +++ b/futures-util/src/stream/try_stream/into_stream.rs @@ -34,10 +34,7 @@ impl Stream for IntoStream { type Item = Result; #[inline] - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().stream.try_poll_next(cx) } diff --git a/futures-util/src/stream/try_stream/mod.rs b/futures-util/src/stream/try_stream/mod.rs index b7353d908a..ee004b51e8 100644 --- a/futures-util/src/stream/try_stream/mod.rs +++ b/futures-util/src/stream/try_stream/mod.rs @@ -5,18 +5,22 @@ #[cfg(feature = "compat")] use crate::compat::Compat; +use crate::fns::{ + inspect_err_fn, inspect_ok_fn, into_fn, map_err_fn, map_ok_fn, InspectErrFn, InspectOkFn, + IntoFn, MapErrFn, MapOkFn, +}; +use crate::future::assert_future; +use crate::stream::assert_stream; +use crate::stream::{Inspect, Map}; +#[cfg(feature = "alloc")] +use alloc::vec::Vec; use core::pin::Pin; + use futures_core::{ future::{Future, TryFuture}, stream::TryStream, task::{Context, Poll}, }; -use crate::fns::{ - InspectOkFn, inspect_ok_fn, InspectErrFn, inspect_err_fn, MapErrFn, map_err_fn, IntoFn, into_fn, MapOkFn, map_ok_fn, -}; -use crate::future::assert_future; -use crate::stream::{Map, Inspect}; -use crate::stream::assert_stream; mod and_then; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 @@ -85,6 +89,14 @@ mod try_flatten; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::try_flatten::TryFlatten; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod try_flatten_unordered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_flatten_unordered::TryFlattenUnordered; + mod try_collect; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::try_collect::TryCollect; @@ -93,6 +105,18 @@ mod try_concat; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::try_concat::TryConcat; +#[cfg(feature = "alloc")] +mod try_chunks; +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_chunks::{TryChunks, TryChunksError}; + +#[cfg(feature = "alloc")] +mod try_ready_chunks; +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_ready_chunks::{TryReadyChunks, TryReadyChunksError}; + mod try_fold; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::try_fold::TryFold; @@ -109,25 +133,29 @@ mod try_take_while; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::try_take_while::TryTakeWhile; -cfg_target_has_atomic! { - #[cfg(feature = "alloc")] - mod try_buffer_unordered; - #[cfg(feature = "alloc")] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use self::try_buffer_unordered::TryBufferUnordered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod try_buffer_unordered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_buffer_unordered::TryBufferUnordered; - #[cfg(feature = "alloc")] - mod try_buffered; - #[cfg(feature = "alloc")] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use self::try_buffered::TryBuffered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod try_buffered; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_buffered::TryBuffered; - #[cfg(feature = "alloc")] - mod try_for_each_concurrent; - #[cfg(feature = "alloc")] - #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 - pub use self::try_for_each_concurrent::TryForEachConcurrent; -} +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +mod try_for_each_concurrent; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_for_each_concurrent::TryForEachConcurrent; #[cfg(feature = "io")] #[cfg(feature = "std")] @@ -138,6 +166,14 @@ mod into_async_read; #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 pub use self::into_async_read::IntoAsyncRead; +mod try_all; +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_all::TryAll; + +mod try_any; +#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 +pub use self::try_any::TryAny; + impl TryStreamExt for S {} /// Adapters specific to `Result`-returning streams @@ -515,7 +551,7 @@ pub trait TryStreamExt: TryStream { /// assert_eq!(Err(oneshot::Canceled), fut.await); /// # }) /// ``` - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] fn try_for_each_concurrent( self, @@ -571,6 +607,102 @@ pub trait TryStreamExt: TryStream { assert_future::, _>(TryCollect::new(self)) } + /// An adaptor for chunking up successful items of the stream inside a vector. + /// + /// This combinator will attempt to pull successful items from this stream and buffer + /// them into a local vector. At most `capacity` items will get buffered + /// before they're yielded from the returned stream. + /// + /// Note that the vectors returned from this iterator may not always have + /// `capacity` elements. If the underlying stream ended and only a partial + /// vector was created, it'll be returned. Additionally if an error happens + /// from the underlying stream then the currently buffered items will be + /// yielded. + /// + /// This method is only available when the `std` or `alloc` feature of this + /// library is activated, and it is activated by default. + /// + /// This function is similar to + /// [`StreamExt::chunks`](crate::stream::StreamExt::chunks) but exits + /// early if an error occurs. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, TryChunksError, TryStreamExt}; + /// + /// let stream = stream::iter(vec![Ok::(1), Ok(2), Ok(3), Err(4), Ok(5), Ok(6)]); + /// let mut stream = stream.try_chunks(2); + /// + /// assert_eq!(stream.try_next().await, Ok(Some(vec![1, 2]))); + /// assert_eq!(stream.try_next().await, Err(TryChunksError(vec![3], 4))); + /// assert_eq!(stream.try_next().await, Ok(Some(vec![5, 6]))); + /// # }) + /// ``` + /// + /// # Panics + /// + /// This method will panic if `capacity` is zero. + #[cfg(feature = "alloc")] + fn try_chunks(self, capacity: usize) -> TryChunks + where + Self: Sized, + { + assert_stream::, TryChunksError>, _>( + TryChunks::new(self, capacity), + ) + } + + /// An adaptor for chunking up successful, ready items of the stream inside a vector. + /// + /// This combinator will attempt to pull successful items from this stream and buffer + /// them into a local vector. At most `capacity` items will get buffered + /// before they're yielded from the returned stream. If the underlying stream + /// returns `Poll::Pending`, and the collected chunk is not empty, it will + /// be immediately returned. + /// + /// Note that the vectors returned from this iterator may not always have + /// `capacity` elements. If the underlying stream ended and only a partial + /// vector was created, it'll be returned. Additionally if an error happens + /// from the underlying stream then the currently buffered items will be + /// yielded. + /// + /// This method is only available when the `std` or `alloc` feature of this + /// library is activated, and it is activated by default. + /// + /// This function is similar to + /// [`StreamExt::ready_chunks`](crate::stream::StreamExt::ready_chunks) but exits + /// early if an error occurs. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, TryReadyChunksError, TryStreamExt}; + /// + /// let stream = stream::iter(vec![Ok::(1), Ok(2), Ok(3), Err(4), Ok(5), Ok(6)]); + /// let mut stream = stream.try_ready_chunks(2); + /// + /// assert_eq!(stream.try_next().await, Ok(Some(vec![1, 2]))); + /// assert_eq!(stream.try_next().await, Err(TryReadyChunksError(vec![3], 4))); + /// assert_eq!(stream.try_next().await, Ok(Some(vec![5, 6]))); + /// # }) + /// ``` + /// + /// # Panics + /// + /// This method will panic if `capacity` is zero. + #[cfg(feature = "alloc")] + fn try_ready_chunks(self, capacity: usize) -> TryReadyChunks + where + Self: Sized, + { + assert_stream::, TryReadyChunksError>, _>( + TryReadyChunks::new(self, capacity), + ) + } + /// Attempt to filter the values produced by this stream according to the /// provided asynchronous closure. /// @@ -651,6 +783,63 @@ pub trait TryStreamExt: TryStream { assert_stream::, _>(TryFilterMap::new(self, f)) } + /// Flattens a stream of streams into just one continuous stream. Produced streams + /// will be polled concurrently and any errors will be passed through without looking at them. + /// If the underlying base stream returns an error, it will be **immediately** propagated. + /// + /// The only argument is an optional limit on the number of concurrently + /// polled streams. If this limit is not `None`, no more than `limit` streams + /// will be polled at the same time. The `limit` argument is of type + /// `Into>`, and so can be provided as either `None`, + /// `Some(10)`, or just `10`. Note: a limit of zero is interpreted as + /// no limit at all, and will have the same result as passing in `None`. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::channel::mpsc; + /// use futures::stream::{StreamExt, TryStreamExt}; + /// use std::thread; + /// + /// let (tx1, rx1) = mpsc::unbounded(); + /// let (tx2, rx2) = mpsc::unbounded(); + /// let (tx3, rx3) = mpsc::unbounded(); + /// + /// thread::spawn(move || { + /// tx1.unbounded_send(Ok(1)).unwrap(); + /// }); + /// thread::spawn(move || { + /// tx2.unbounded_send(Ok(2)).unwrap(); + /// tx2.unbounded_send(Err(3)).unwrap(); + /// tx2.unbounded_send(Ok(4)).unwrap(); + /// }); + /// thread::spawn(move || { + /// tx3.unbounded_send(Ok(rx1)).unwrap(); + /// tx3.unbounded_send(Ok(rx2)).unwrap(); + /// tx3.unbounded_send(Err(5)).unwrap(); + /// }); + /// + /// let stream = rx3.try_flatten_unordered(None); + /// let mut values: Vec<_> = stream.collect().await; + /// values.sort(); + /// + /// assert_eq!(values, vec![Ok(1), Ok(2), Ok(4), Err(3), Err(5)]); + /// # }); + /// ``` + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] + #[cfg(feature = "alloc")] + fn try_flatten_unordered(self, limit: impl Into>) -> TryFlattenUnordered + where + Self::Ok: TryStream + Unpin, + ::Error: From, + Self: Sized, + { + assert_stream::::Ok, ::Error>, _>( + TryFlattenUnordered::new(self, limit), + ) + } + /// Flattens a stream of streams into just one continuous stream. /// /// If this stream's elements are themselves streams then this combinator @@ -676,17 +865,21 @@ pub trait TryStreamExt: TryStream { /// thread::spawn(move || { /// tx2.unbounded_send(Ok(2)).unwrap(); /// tx2.unbounded_send(Err(3)).unwrap(); + /// tx2.unbounded_send(Ok(4)).unwrap(); /// }); /// thread::spawn(move || { /// tx3.unbounded_send(Ok(rx1)).unwrap(); /// tx3.unbounded_send(Ok(rx2)).unwrap(); - /// tx3.unbounded_send(Err(4)).unwrap(); + /// tx3.unbounded_send(Err(5)).unwrap(); /// }); /// /// let mut stream = rx3.try_flatten(); /// assert_eq!(stream.next().await, Some(Ok(1))); /// assert_eq!(stream.next().await, Some(Ok(2))); /// assert_eq!(stream.next().await, Some(Err(3))); + /// assert_eq!(stream.next().await, Some(Ok(4))); + /// assert_eq!(stream.next().await, Some(Err(5))); + /// assert_eq!(stream.next().await, None); /// # }); /// ``` fn try_flatten(self) -> TryFlatten @@ -836,7 +1029,7 @@ pub trait TryStreamExt: TryStream { /// assert_eq!(buffered.next().await, Some(Err("error in the stream"))); /// # Ok::<(), Box>(()) }).unwrap(); /// ``` - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] fn try_buffer_unordered(self, n: usize) -> TryBufferUnordered where @@ -854,7 +1047,7 @@ pub trait TryStreamExt: TryStream { /// that matches the stream's `Error` type. /// /// This adaptor will buffer up to `n` futures and then return their - /// outputs in the order. If the underlying stream returns an error, it will + /// outputs in the same order as the underlying stream. If the underlying stream returns an error, it will /// be immediately propagated. /// /// The returned stream will be a stream of results, each containing either @@ -912,14 +1105,16 @@ pub trait TryStreamExt: TryStream { /// assert_eq!(buffered.next().await, Some(Err("error in the stream"))); /// # Ok::<(), Box>(()) }).unwrap(); /// ``` - #[cfg_attr(feature = "cfg-target-has-atomic", cfg(target_has_atomic = "ptr"))] + #[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] #[cfg(feature = "alloc")] fn try_buffered(self, n: usize) -> TryBuffered where Self::Ok: TryFuture, Self: Sized, { - assert_stream::::Ok, Self::Error>, _>(TryBuffered::new(self, n)) + assert_stream::::Ok, Self::Error>, _>(TryBuffered::new( + self, n, + )) } // TODO: false positive warning from rustdoc. Verify once #43466 settles @@ -939,6 +1134,7 @@ pub trait TryStreamExt: TryStream { /// Wraps a [`TryStream`] into a stream compatible with libraries using /// futures 0.1 `Stream`. Requires the `compat` feature to be enabled. /// ``` + /// # if cfg!(miri) { return; } // Miri does not support epoll /// use futures::future::{FutureExt, TryFutureExt}; /// # let (tx, rx) = futures::channel::oneshot::channel(); /// @@ -964,12 +1160,7 @@ pub trait TryStreamExt: TryStream { Compat::new(self) } - /// Adapter that converts this stream into an [`AsyncRead`](crate::io::AsyncRead). - /// - /// Note that because `into_async_read` moves the stream, the [`Stream`](futures_core::stream::Stream) type must be - /// [`Unpin`]. If you want to use `into_async_read` with a [`!Unpin`](Unpin) stream, you'll - /// first have to pin the stream. This can be done by boxing the stream using [`Box::pin`] - /// or pinning it to the stack using the `pin_mut!` macro from the `pin_utils` crate. + /// Adapter that converts this stream into an [`AsyncBufRead`](crate::io::AsyncBufRead). /// /// This method is only available when the `std` feature of this /// library is activated, and it is activated by default. @@ -981,12 +1172,12 @@ pub trait TryStreamExt: TryStream { /// use futures::stream::{self, TryStreamExt}; /// use futures::io::AsyncReadExt; /// - /// let stream = stream::iter(vec![Ok(vec![1, 2, 3, 4, 5])]); + /// let stream = stream::iter([Ok(vec![1, 2, 3]), Ok(vec![4, 5])]); /// let mut reader = stream.into_async_read(); - /// let mut buf = Vec::new(); /// - /// assert!(reader.read_to_end(&mut buf).await.is_ok()); - /// assert_eq!(buf, &[1, 2, 3, 4, 5]); + /// let mut buf = Vec::new(); + /// reader.read_to_end(&mut buf).await.unwrap(); + /// assert_eq!(buf, [1, 2, 3, 4, 5]); /// # }) /// ``` #[cfg(feature = "io")] @@ -994,9 +1185,67 @@ pub trait TryStreamExt: TryStream { #[cfg(feature = "std")] fn into_async_read(self) -> IntoAsyncRead where - Self: Sized + TryStreamExt + Unpin, + Self: Sized + TryStreamExt, Self::Ok: AsRef<[u8]>, { crate::io::assert_read(IntoAsyncRead::new(self)) } + + /// Attempt to execute a predicate over an asynchronous stream and evaluate if all items + /// satisfy the predicate. Exits early if an `Err` is encountered or if an `Ok` item is found + /// that does not satisfy the predicate. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt, TryStreamExt}; + /// use std::convert::Infallible; + /// + /// let number_stream = stream::iter(1..10).map(Ok::<_, Infallible>); + /// let positive = number_stream.try_all(|i| async move { i > 0 }); + /// assert_eq!(positive.await, Ok(true)); + /// + /// let stream_with_errors = stream::iter([Ok(1), Err("err"), Ok(3)]); + /// let positive = stream_with_errors.try_all(|i| async move { i > 0 }); + /// assert_eq!(positive.await, Err("err")); + /// # }); + /// ``` + fn try_all(self, f: F) -> TryAll + where + Self: Sized, + F: FnMut(Self::Ok) -> Fut, + Fut: Future, + { + assert_future::, _>(TryAll::new(self, f)) + } + + /// Attempt to execute a predicate over an asynchronous stream and evaluate if any items + /// satisfy the predicate. Exits early if an `Err` is encountered or if an `Ok` item is found + /// that satisfies the predicate. + /// + /// # Examples + /// + /// ``` + /// # futures::executor::block_on(async { + /// use futures::stream::{self, StreamExt, TryStreamExt}; + /// use std::convert::Infallible; + /// + /// let number_stream = stream::iter(0..10).map(Ok::<_, Infallible>); + /// let contain_three = number_stream.try_any(|i| async move { i == 3 }); + /// assert_eq!(contain_three.await, Ok(true)); + /// + /// let stream_with_errors = stream::iter([Ok(1), Err("err"), Ok(3)]); + /// let contain_three = stream_with_errors.try_any(|i| async move { i == 3 }); + /// assert_eq!(contain_three.await, Err("err")); + /// # }); + /// ``` + fn try_any(self, f: F) -> TryAny + where + Self: Sized, + F: FnMut(Self::Ok) -> Fut, + Fut: Future, + { + assert_future::, _>(TryAny::new(self, f)) + } } diff --git a/futures-util/src/stream/try_stream/or_else.rs b/futures-util/src/stream/try_stream/or_else.rs index 999123a437..53aceb8e64 100644 --- a/futures-util/src/stream/try_stream/or_else.rs +++ b/futures-util/src/stream/try_stream/or_else.rs @@ -2,7 +2,7 @@ use core::fmt; use core::pin::Pin; use futures_core::future::TryFuture; use futures_core::ready; -use futures_core::stream::{Stream, TryStream, FusedStream}; +use futures_core::stream::{FusedStream, Stream, TryStream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; @@ -34,9 +34,10 @@ where } impl OrElse - where St: TryStream, - F: FnMut(St::Error) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(St::Error) -> Fut, + Fut: TryFuture, { pub(super) fn new(stream: St, f: F) -> Self { Self { stream, future: None, f } @@ -46,16 +47,14 @@ impl OrElse } impl Stream for OrElse - where St: TryStream, - F: FnMut(St::Error) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(St::Error) -> Fut, + Fut: TryFuture, { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); Poll::Ready(loop { @@ -68,7 +67,7 @@ impl Stream for OrElse Some(Ok(item)) => break Some(Ok(item)), Some(Err(e)) => { this.future.set(Some((this.f)(e))); - }, + } None => break None, } } @@ -76,7 +75,7 @@ impl Stream for OrElse } fn size_hint(&self) -> (usize, Option) { - let future_len = if self.future.is_some() { 1 } else { 0 }; + let future_len = usize::from(self.future.is_some()); let (lower, upper) = self.stream.size_hint(); let lower = lower.saturating_add(future_len); let upper = match upper { @@ -88,9 +87,10 @@ impl Stream for OrElse } impl FusedStream for OrElse - where St: TryStream + FusedStream, - F: FnMut(St::Error) -> Fut, - Fut: TryFuture, +where + St: TryStream + FusedStream, + F: FnMut(St::Error) -> Fut, + Fut: TryFuture, { fn is_terminated(&self) -> bool { self.future.is_none() && self.stream.is_terminated() @@ -100,7 +100,8 @@ impl FusedStream for OrElse // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for OrElse - where S: Sink, +where + S: Sink, { type Error = S::Error; diff --git a/futures-util/src/stream/try_stream/try_all.rs b/futures-util/src/stream/try_stream/try_all.rs new file mode 100644 index 0000000000..8179f86afc --- /dev/null +++ b/futures-util/src/stream/try_stream/try_all.rs @@ -0,0 +1,98 @@ +use core::fmt; +use core::pin::Pin; +use futures_core::future::{FusedFuture, Future}; +use futures_core::ready; +use futures_core::stream::TryStream; +use futures_core::task::{Context, Poll}; +use pin_project_lite::pin_project; + +pin_project! { + /// Future for the [`try_all`](super::TryStreamExt::try_all) method. + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct TryAll { + #[pin] + stream: St, + f: F, + done: bool, + #[pin] + future: Option, + } +} + +impl fmt::Debug for TryAll +where + St: fmt::Debug, + Fut: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TryAll") + .field("stream", &self.stream) + .field("done", &self.done) + .field("future", &self.future) + .finish() + } +} + +impl TryAll +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future, +{ + pub(super) fn new(stream: St, f: F) -> Self { + Self { stream, f, done: false, future: None } + } +} + +impl FusedFuture for TryAll +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future, +{ + fn is_terminated(&self) -> bool { + self.done && self.future.is_none() + } +} + +impl Future for TryAll +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + + Poll::Ready(loop { + if let Some(fut) = this.future.as_mut().as_pin_mut() { + // we're currently processing a future to produce a new value + let acc = ready!(fut.poll(cx)); + this.future.set(None); + if !acc { + *this.done = true; + break Ok(false); + } // early exit + } else if !*this.done { + // we're waiting on a new item from the stream + match ready!(this.stream.as_mut().try_poll_next(cx)) { + Some(Ok(item)) => { + this.future.set(Some((this.f)(item))); + } + Some(Err(err)) => { + *this.done = true; + break Err(err); + } + None => { + *this.done = true; + break Ok(true); + } + } + } else { + panic!("TryAll polled after completion") + } + }) + } +} diff --git a/futures-util/src/stream/try_stream/try_any.rs b/futures-util/src/stream/try_stream/try_any.rs new file mode 100644 index 0000000000..55e876be05 --- /dev/null +++ b/futures-util/src/stream/try_stream/try_any.rs @@ -0,0 +1,98 @@ +use core::fmt; +use core::pin::Pin; +use futures_core::future::{FusedFuture, Future}; +use futures_core::ready; +use futures_core::stream::TryStream; +use futures_core::task::{Context, Poll}; +use pin_project_lite::pin_project; + +pin_project! { + /// Future for the [`try_any`](super::TryStreamExt::try_any) method. + #[must_use = "futures do nothing unless you `.await` or poll them"] + pub struct TryAny { + #[pin] + stream: St, + f: F, + done: bool, + #[pin] + future: Option, + } +} + +impl fmt::Debug for TryAny +where + St: fmt::Debug, + Fut: fmt::Debug, +{ + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.debug_struct("TryAny") + .field("stream", &self.stream) + .field("done", &self.done) + .field("future", &self.future) + .finish() + } +} + +impl TryAny +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future, +{ + pub(super) fn new(stream: St, f: F) -> Self { + Self { stream, f, done: false, future: None } + } +} + +impl FusedFuture for TryAny +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future, +{ + fn is_terminated(&self) -> bool { + self.done && self.future.is_none() + } +} + +impl Future for TryAny +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.project(); + + Poll::Ready(loop { + if let Some(fut) = this.future.as_mut().as_pin_mut() { + // we're currently processing a future to produce a new value + let acc = ready!(fut.poll(cx)); + this.future.set(None); + if acc { + *this.done = true; + break Ok(true); + } // early exit + } else if !*this.done { + // we're waiting on a new item from the stream + match ready!(this.stream.as_mut().try_poll_next(cx)) { + Some(Ok(item)) => { + this.future.set(Some((this.f)(item))); + } + Some(Err(err)) => { + *this.done = true; + break Err(err); + } + None => { + *this.done = true; + break Ok(false); + } + } + } else { + panic!("TryAny polled after completion") + } + }) + } +} diff --git a/futures-util/src/stream/try_stream/try_buffer_unordered.rs b/futures-util/src/stream/try_stream/try_buffer_unordered.rs index 71c6fc7e26..9a899d4ea6 100644 --- a/futures-util/src/stream/try_stream/try_buffer_unordered.rs +++ b/futures-util/src/stream/try_stream/try_buffer_unordered.rs @@ -1,12 +1,12 @@ -use crate::stream::{Fuse, FuturesUnordered, StreamExt, IntoStream}; use crate::future::{IntoFuture, TryFutureExt}; +use crate::stream::{Fuse, FuturesUnordered, IntoStream, StreamExt}; +use core::pin::Pin; use futures_core::future::TryFuture; use futures_core::stream::{Stream, TryStream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use core::pin::Pin; pin_project! { /// Stream for the @@ -24,8 +24,9 @@ pin_project! { } impl TryBufferUnordered - where St: TryStream, - St::Ok: TryFuture, +where + St: TryStream, + St::Ok: TryFuture, { pub(super) fn new(stream: St, n: usize) -> Self { Self { @@ -39,15 +40,13 @@ impl TryBufferUnordered } impl Stream for TryBufferUnordered - where St: TryStream, - St::Ok: TryFuture, +where + St: TryStream, + St::Ok: TryFuture, { type Item = Result<::Ok, St::Error>; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); // First up, try to spawn off as many futures as possible by filling up @@ -77,8 +76,9 @@ impl Stream for TryBufferUnordered // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for TryBufferUnordered - where S: TryStream + Sink, - S::Ok: TryFuture, +where + S: TryStream + Sink, + S::Ok: TryFuture, { type Error = E; diff --git a/futures-util/src/stream/try_stream/try_buffered.rs b/futures-util/src/stream/try_stream/try_buffered.rs index ff7e8447f9..9f48e5c0a7 100644 --- a/futures-util/src/stream/try_stream/try_buffered.rs +++ b/futures-util/src/stream/try_stream/try_buffered.rs @@ -1,12 +1,12 @@ -use crate::stream::{Fuse, FuturesOrdered, StreamExt, IntoStream}; use crate::future::{IntoFuture, TryFutureExt}; +use crate::stream::{Fuse, FuturesOrdered, IntoStream, StreamExt}; +use core::pin::Pin; use futures_core::future::TryFuture; use futures_core::stream::{Stream, TryStream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; use pin_project_lite::pin_project; -use core::pin::Pin; pin_project! { /// Stream for the [`try_buffered`](super::TryStreamExt::try_buffered) method. @@ -47,17 +47,14 @@ where { type Item = Result<::Ok, St::Error>; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); // First up, try to spawn off as many futures as possible by filling up // our queue of futures. Propagate errors from the stream immediately. while this.in_progress_queue.len() < *this.max { match this.stream.as_mut().poll_next(cx)? { - Poll::Ready(Some(fut)) => this.in_progress_queue.push(fut.into_future()), + Poll::Ready(Some(fut)) => this.in_progress_queue.push_back(fut.into_future()), Poll::Ready(None) | Poll::Pending => break, } } diff --git a/futures-util/src/stream/try_stream/try_chunks.rs b/futures-util/src/stream/try_stream/try_chunks.rs new file mode 100644 index 0000000000..ec53f4bd11 --- /dev/null +++ b/futures-util/src/stream/try_stream/try_chunks.rs @@ -0,0 +1,132 @@ +use crate::stream::{Fuse, IntoStream, StreamExt}; + +use alloc::vec::Vec; +use core::pin::Pin; +use core::{fmt, mem}; +use futures_core::ready; +use futures_core::stream::{FusedStream, Stream, TryStream}; +use futures_core::task::{Context, Poll}; +#[cfg(feature = "sink")] +use futures_sink::Sink; +use pin_project_lite::pin_project; + +pin_project! { + /// Stream for the [`try_chunks`](super::TryStreamExt::try_chunks) method. + #[derive(Debug)] + #[must_use = "streams do nothing unless polled"] + pub struct TryChunks { + #[pin] + stream: Fuse>, + items: Vec, + cap: usize, // https://github.com/rust-lang/futures-rs/issues/1475 + } +} + +impl TryChunks { + pub(super) fn new(stream: St, capacity: usize) -> Self { + assert!(capacity > 0); + + Self { + stream: IntoStream::new(stream).fuse(), + items: Vec::with_capacity(capacity), + cap: capacity, + } + } + + fn take(self: Pin<&mut Self>) -> Vec { + let cap = self.cap; + mem::replace(self.project().items, Vec::with_capacity(cap)) + } + + delegate_access_inner!(stream, St, (. .)); +} + +type TryChunksStreamError = TryChunksError<::Ok, ::Error>; + +impl Stream for TryChunks { + type Item = Result, TryChunksStreamError>; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.as_mut().project(); + loop { + match ready!(this.stream.as_mut().try_poll_next(cx)) { + // Push the item into the buffer and check whether it is full. + // If so, replace our buffer with a new and empty one and return + // the full one. + Some(item) => match item { + Ok(item) => { + this.items.push(item); + if this.items.len() >= *this.cap { + return Poll::Ready(Some(Ok(self.take()))); + } + } + Err(e) => { + return Poll::Ready(Some(Err(TryChunksError(self.take(), e)))); + } + }, + + // Since the underlying stream ran out of values, return what we + // have buffered, if we have anything. + None => { + let last = if this.items.is_empty() { + None + } else { + let full_buf = mem::take(this.items); + Some(full_buf) + }; + + return Poll::Ready(last.map(Ok)); + } + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let chunk_len = usize::from(!self.items.is_empty()); + let (lower, upper) = self.stream.size_hint(); + let lower = (lower / self.cap).saturating_add(chunk_len); + let upper = match upper { + Some(x) => x.checked_add(chunk_len), + None => None, + }; + (lower, upper) + } +} + +impl FusedStream for TryChunks { + fn is_terminated(&self) -> bool { + self.stream.is_terminated() && self.items.is_empty() + } +} + +// Forwarding impl of Sink from the underlying stream +#[cfg(feature = "sink")] +impl Sink for TryChunks +where + S: TryStream + Sink, +{ + type Error = >::Error; + + delegate_sink!(stream, Item); +} + +/// Error indicating, that while chunk was collected inner stream produced an error. +/// +/// Contains all items that were collected before an error occurred, and the stream error itself. +#[derive(PartialEq, Eq)] +pub struct TryChunksError(pub Vec, pub E); + +impl fmt::Debug for TryChunksError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.1.fmt(f) + } +} + +impl fmt::Display for TryChunksError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.1.fmt(f) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TryChunksError {} diff --git a/futures-util/src/stream/try_stream/try_collect.rs b/futures-util/src/stream/try_stream/try_collect.rs index 387de9703b..3e5963f033 100644 --- a/futures-util/src/stream/try_stream/try_collect.rs +++ b/futures-util/src/stream/try_stream/try_collect.rs @@ -19,10 +19,7 @@ pin_project! { impl TryCollect { pub(super) fn new(s: St) -> Self { - Self { - stream: s, - items: Default::default(), - } + Self { stream: s, items: Default::default() } } } @@ -43,15 +40,12 @@ where { type Output = Result; - fn poll( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); Poll::Ready(Ok(loop { match ready!(this.stream.as_mut().try_poll_next(cx)?) { Some(x) => this.items.extend(Some(x)), - None => break mem::replace(this.items, Default::default()), + None => break mem::take(this.items), } })) } diff --git a/futures-util/src/stream/try_stream/try_concat.rs b/futures-util/src/stream/try_stream/try_concat.rs index 2451332448..58fb6a5413 100644 --- a/futures-util/src/stream/try_stream/try_concat.rs +++ b/futures-util/src/stream/try_stream/try_concat.rs @@ -22,10 +22,7 @@ where St::Ok: Extend<::Item> + IntoIterator + Default, { pub(super) fn new(stream: St) -> Self { - Self { - stream, - accum: None, - } + Self { stream, accum: None } } } diff --git a/futures-util/src/stream/try_stream/try_filter.rs b/futures-util/src/stream/try_stream/try_filter.rs index eacefd20db..11d58243fd 100644 --- a/futures-util/src/stream/try_stream/try_filter.rs +++ b/futures-util/src/stream/try_stream/try_filter.rs @@ -2,7 +2,7 @@ use core::fmt; use core::pin::Pin; use futures_core::future::Future; use futures_core::ready; -use futures_core::stream::{Stream, TryStream, FusedStream}; +use futures_core::stream::{FusedStream, Stream, TryStream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; @@ -40,24 +40,21 @@ where } impl TryFilter - where St: TryStream +where + St: TryStream, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - pending_fut: None, - pending_item: None, - } + Self { stream, f, pending_fut: None, pending_item: None } } delegate_access_inner!(stream, St, ()); } impl FusedStream for TryFilter - where St: TryStream + FusedStream, - F: FnMut(&St::Ok) -> Fut, - Fut: Future, +where + St: TryStream + FusedStream, + F: FnMut(&St::Ok) -> Fut, + Fut: Future, { fn is_terminated(&self) -> bool { self.pending_fut.is_none() && self.stream.is_terminated() @@ -65,16 +62,14 @@ impl FusedStream for TryFilter } impl Stream for TryFilter - where St: TryStream, - Fut: Future, - F: FnMut(&St::Ok) -> Fut, +where + St: TryStream, + Fut: Future, + F: FnMut(&St::Ok) -> Fut, { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); Poll::Ready(loop { @@ -95,7 +90,7 @@ impl Stream for TryFilter } fn size_hint(&self) -> (usize, Option) { - let pending_len = if self.pending_fut.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending_fut.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), @@ -108,7 +103,8 @@ impl Stream for TryFilter // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for TryFilter - where S: TryStream + Sink, +where + S: TryStream + Sink, { type Error = E; diff --git a/futures-util/src/stream/try_stream/try_filter_map.rs b/futures-util/src/stream/try_stream/try_filter_map.rs index 335649bfc9..ed1201732b 100644 --- a/futures-util/src/stream/try_stream/try_filter_map.rs +++ b/futures-util/src/stream/try_stream/try_filter_map.rs @@ -1,8 +1,8 @@ use core::fmt; use core::pin::Pin; -use futures_core::future::{TryFuture}; +use futures_core::future::TryFuture; use futures_core::ready; -use futures_core::stream::{Stream, TryStream, FusedStream}; +use futures_core::stream::{FusedStream, Stream, TryStream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; @@ -43,9 +43,10 @@ impl TryFilterMap { } impl FusedStream for TryFilterMap - where St: TryStream + FusedStream, - Fut: TryFuture, Error = St::Error>, - F: FnMut(St::Ok) -> Fut, +where + St: TryStream + FusedStream, + Fut: TryFuture, Error = St::Error>, + F: FnMut(St::Ok) -> Fut, { fn is_terminated(&self) -> bool { self.pending.is_none() && self.stream.is_terminated() @@ -53,16 +54,14 @@ impl FusedStream for TryFilterMap } impl Stream for TryFilterMap - where St: TryStream, - Fut: TryFuture, Error = St::Error>, - F: FnMut(St::Ok) -> Fut, +where + St: TryStream, + Fut: TryFuture, Error = St::Error>, + F: FnMut(St::Ok) -> Fut, { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); Poll::Ready(loop { @@ -85,7 +84,7 @@ impl Stream for TryFilterMap } fn size_hint(&self) -> (usize, Option) { - let pending_len = if self.pending.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), @@ -98,7 +97,8 @@ impl Stream for TryFilterMap // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for TryFilterMap - where S: Sink, +where + S: Sink, { type Error = S::Error; diff --git a/futures-util/src/stream/try_stream/try_flatten_unordered.rs b/futures-util/src/stream/try_stream/try_flatten_unordered.rs new file mode 100644 index 0000000000..a74dfc451d --- /dev/null +++ b/futures-util/src/stream/try_stream/try_flatten_unordered.rs @@ -0,0 +1,176 @@ +use core::marker::PhantomData; +use core::pin::Pin; + +use futures_core::ready; +use futures_core::stream::{FusedStream, Stream, TryStream}; +use futures_core::task::{Context, Poll}; +#[cfg(feature = "sink")] +use futures_sink::Sink; + +use pin_project_lite::pin_project; + +use crate::future::Either; +use crate::stream::stream::flatten_unordered::{ + FlattenUnorderedWithFlowController, FlowController, FlowStep, +}; +use crate::stream::IntoStream; +use crate::TryStreamExt; + +delegate_all!( + /// Stream for the [`try_flatten_unordered`](super::TryStreamExt::try_flatten_unordered) method. + TryFlattenUnordered( + FlattenUnorderedWithFlowController, PropagateBaseStreamError> + ): Debug + Sink + Stream + FusedStream + AccessInner[St, (. .)] + + New[ + |stream: St, limit: impl Into>| + FlattenUnorderedWithFlowController::new( + NestedTryStreamIntoEitherTryStream::new(stream), + limit.into() + ) + ] + where + St: TryStream, + St::Ok: TryStream, + St::Ok: Unpin, + ::Error: From +); + +pin_project! { + /// Emits either successful streams or single-item streams containing the underlying errors. + /// This's a wrapper for `FlattenUnordered` to reuse its logic over `TryStream`. + #[derive(Debug)] + #[must_use = "streams do nothing unless polled"] + pub struct NestedTryStreamIntoEitherTryStream + where + St: TryStream, + St::Ok: TryStream, + St::Ok: Unpin, + ::Error: From + { + #[pin] + stream: St + } +} + +impl NestedTryStreamIntoEitherTryStream +where + St: TryStream, + St::Ok: TryStream + Unpin, + ::Error: From, +{ + fn new(stream: St) -> Self { + Self { stream } + } + + delegate_access_inner!(stream, St, ()); +} + +/// Emits a single item immediately, then stream will be terminated. +#[derive(Debug, Clone)] +pub struct Single(Option); + +impl Single { + /// Constructs new `Single` with the given value. + fn new(val: T) -> Self { + Self(Some(val)) + } + + /// Attempts to take inner item immediately. Will always succeed if the stream isn't terminated. + fn next_immediate(&mut self) -> Option { + self.0.take() + } +} + +impl Unpin for Single {} + +impl Stream for Single { + type Item = T; + + fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(self.0.take()) + } + + fn size_hint(&self) -> (usize, Option) { + self.0.as_ref().map_or((0, Some(0)), |_| (1, Some(1))) + } +} + +/// Immediately propagates errors occurred in the base stream. +#[derive(Debug, Clone, Copy)] +pub struct PropagateBaseStreamError(PhantomData); + +type BaseStreamItem = as Stream>::Item; +type InnerStreamItem = as Stream>::Item; + +impl FlowController, InnerStreamItem> for PropagateBaseStreamError +where + St: TryStream, + St::Ok: TryStream + Unpin, + ::Error: From, +{ + fn next_step(item: BaseStreamItem) -> FlowStep, InnerStreamItem> { + match item { + // A new successful inner stream received + st @ Either::Left(_) => FlowStep::Continue(st), + // An error encountered + Either::Right(mut err) => FlowStep::Return(err.next_immediate().unwrap()), + } + } +} + +type SingleStreamResult = Single::Ok, ::Error>>; + +impl Stream for NestedTryStreamIntoEitherTryStream +where + St: TryStream, + St::Ok: TryStream + Unpin, + ::Error: From, +{ + // Item is either an inner stream or a stream containing a single error. + // This will allow using `Either`'s `Stream` implementation as both branches are actually streams of `Result`'s. + type Item = Either, SingleStreamResult>; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let item = ready!(self.project().stream.try_poll_next(cx)); + + let out = match item { + Some(res) => match res { + // Emit successful inner stream as is + Ok(stream) => Either::Left(stream.into_stream()), + // Wrap an error into a stream containing a single item + err @ Err(_) => { + let res = err.map(|_: St::Ok| unreachable!()).map_err(Into::into); + + Either::Right(Single::new(res)) + } + }, + None => return Poll::Ready(None), + }; + + Poll::Ready(Some(out)) + } +} + +impl FusedStream for NestedTryStreamIntoEitherTryStream +where + St: TryStream + FusedStream, + St::Ok: TryStream + Unpin, + ::Error: From, +{ + fn is_terminated(&self) -> bool { + self.stream.is_terminated() + } +} + +// Forwarding impl of Sink from the underlying stream +#[cfg(feature = "sink")] +impl Sink for NestedTryStreamIntoEitherTryStream +where + St: TryStream + Sink, + St::Ok: TryStream + Unpin, + ::Error: From<::Error>, +{ + type Error = >::Error; + + delegate_sink!(stream, Item); +} diff --git a/futures-util/src/stream/try_stream/try_fold.rs b/futures-util/src/stream/try_stream/try_fold.rs index 1d41e4bc2b..d344d96e7d 100644 --- a/futures-util/src/stream/try_stream/try_fold.rs +++ b/futures-util/src/stream/try_stream/try_fold.rs @@ -35,24 +35,21 @@ where } impl TryFold -where St: TryStream, - F: FnMut(T, St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(T, St::Ok) -> Fut, + Fut: TryFuture, { pub(super) fn new(stream: St, f: F, t: T) -> Self { - Self { - stream, - f, - accum: Some(t), - future: None, - } + Self { stream, f, accum: Some(t), future: None } } } impl FusedFuture for TryFold - where St: TryStream, - F: FnMut(T, St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(T, St::Ok) -> Fut, + Fut: TryFuture, { fn is_terminated(&self) -> bool { self.accum.is_none() && self.future.is_none() @@ -60,9 +57,10 @@ impl FusedFuture for TryFold } impl Future for TryFold - where St: TryStream, - F: FnMut(T, St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(T, St::Ok) -> Fut, + Fut: TryFuture, { type Output = Result; diff --git a/futures-util/src/stream/try_stream/try_for_each.rs b/futures-util/src/stream/try_stream/try_for_each.rs index 0a814ae86c..6a081d84e7 100644 --- a/futures-util/src/stream/try_stream/try_for_each.rs +++ b/futures-util/src/stream/try_stream/try_for_each.rs @@ -32,23 +32,21 @@ where } impl TryForEach -where St: TryStream, - F: FnMut(St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: TryFuture, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - future: None, - } + Self { stream, f, future: None } } } impl Future for TryForEach - where St: TryStream, - F: FnMut(St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: TryFuture, { type Output = Result<(), St::Error>; diff --git a/futures-util/src/stream/try_stream/try_for_each_concurrent.rs b/futures-util/src/stream/try_stream/try_for_each_concurrent.rs index d2f4b0fed2..62734c746b 100644 --- a/futures-util/src/stream/try_stream/try_for_each_concurrent.rs +++ b/futures-util/src/stream/try_stream/try_for_each_concurrent.rs @@ -1,8 +1,8 @@ use crate::stream::{FuturesUnordered, StreamExt}; use core::fmt; use core::mem; -use core::pin::Pin; use core::num::NonZeroUsize; +use core::pin::Pin; use futures_core::future::{FusedFuture, Future}; use futures_core::stream::TryStream; use futures_core::task::{Context, Poll}; @@ -37,9 +37,10 @@ where } impl FusedFuture for TryForEachConcurrent - where St: TryStream, - F: FnMut(St::Ok) -> Fut, - Fut: Future>, +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future>, { fn is_terminated(&self) -> bool { self.stream.is_none() && self.futures.is_empty() @@ -47,9 +48,10 @@ impl FusedFuture for TryForEachConcurrent } impl TryForEachConcurrent -where St: TryStream, - F: FnMut(St::Ok) -> Fut, - Fut: Future>, +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future>, { pub(super) fn new(stream: St, limit: Option, f: F) -> Self { Self { @@ -63,9 +65,10 @@ where St: TryStream, } impl Future for TryForEachConcurrent - where St: TryStream, - F: FnMut(St::Ok) -> Fut, - Fut: Future>, +where + St: TryStream, + F: FnMut(St::Ok) -> Fut, + Fut: Future>, { type Output = Result<(), St::Error>; @@ -85,7 +88,7 @@ impl Future for TryForEachConcurrent Poll::Ready(Some(Ok(elem))) => { made_progress_this_iter = true; Some(elem) - }, + } Poll::Ready(None) => { this.stream.set(None); None @@ -109,9 +112,9 @@ impl Future for TryForEachConcurrent Poll::Ready(Some(Ok(()))) => made_progress_this_iter = true, Poll::Ready(None) => { if this.stream.is_none() { - return Poll::Ready(Ok(())) + return Poll::Ready(Ok(())); } - }, + } Poll::Pending => {} Poll::Ready(Some(Err(e))) => { // Empty the stream and futures so that we know diff --git a/futures-util/src/stream/try_stream/try_next.rs b/futures-util/src/stream/try_stream/try_next.rs index 1bc00fbc2d..13fcf80cae 100644 --- a/futures-util/src/stream/try_stream/try_next.rs +++ b/futures-util/src/stream/try_stream/try_next.rs @@ -28,10 +28,7 @@ impl FusedFuture for TryNext<'_, S impl Future for TryNext<'_, St> { type Output = Result, St::Error>; - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.stream.try_poll_next_unpin(cx)?.map(Ok) } } diff --git a/futures-util/src/stream/try_stream/try_ready_chunks.rs b/futures-util/src/stream/try_stream/try_ready_chunks.rs new file mode 100644 index 0000000000..8b1470ea26 --- /dev/null +++ b/futures-util/src/stream/try_stream/try_ready_chunks.rs @@ -0,0 +1,126 @@ +use crate::stream::{Fuse, IntoStream, StreamExt}; + +use alloc::vec::Vec; +use core::fmt; +use core::pin::Pin; +use futures_core::stream::{FusedStream, Stream, TryStream}; +use futures_core::task::{Context, Poll}; +#[cfg(feature = "sink")] +use futures_sink::Sink; +use pin_project_lite::pin_project; + +pin_project! { + /// Stream for the [`try_ready_chunks`](super::TryStreamExt::try_ready_chunks) method. + #[derive(Debug)] + #[must_use = "streams do nothing unless polled"] + pub struct TryReadyChunks { + #[pin] + stream: Fuse>, + cap: usize, // https://github.com/rust-lang/futures-rs/issues/1475 + } +} + +impl TryReadyChunks { + pub(super) fn new(stream: St, capacity: usize) -> Self { + assert!(capacity > 0); + + Self { stream: IntoStream::new(stream).fuse(), cap: capacity } + } + + delegate_access_inner!(stream, St, (. .)); +} + +type TryReadyChunksStreamError = + TryReadyChunksError<::Ok, ::Error>; + +impl Stream for TryReadyChunks { + type Item = Result, TryReadyChunksStreamError>; + + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + let mut this = self.as_mut().project(); + + let mut items: Vec = Vec::new(); + + loop { + match this.stream.as_mut().poll_next(cx) { + // Flush all the collected data if the underlying stream doesn't + // contain more ready values + Poll::Pending => { + return if items.is_empty() { + Poll::Pending + } else { + Poll::Ready(Some(Ok(items))) + } + } + + // Push the ready item into the buffer and check whether it is full. + // If so, return the buffer. + Poll::Ready(Some(Ok(item))) => { + if items.is_empty() { + items.reserve_exact(*this.cap); + } + items.push(item); + if items.len() >= *this.cap { + return Poll::Ready(Some(Ok(items))); + } + } + + // Return the already collected items and the error. + Poll::Ready(Some(Err(e))) => { + return Poll::Ready(Some(Err(TryReadyChunksError(items, e)))); + } + + // Since the underlying stream ran out of values, return what we + // have buffered, if we have anything. + Poll::Ready(None) => { + let last = if items.is_empty() { None } else { Some(Ok(items)) }; + return Poll::Ready(last); + } + } + } + } + + fn size_hint(&self) -> (usize, Option) { + let (lower, upper) = self.stream.size_hint(); + let lower = lower / self.cap; + (lower, upper) + } +} + +impl FusedStream for TryReadyChunks { + fn is_terminated(&self) -> bool { + self.stream.is_terminated() + } +} + +// Forwarding impl of Sink from the underlying stream +#[cfg(feature = "sink")] +impl Sink for TryReadyChunks +where + S: TryStream + Sink, +{ + type Error = >::Error; + + delegate_sink!(stream, Item); +} + +/// Error indicating, that while chunk was collected inner stream produced an error. +/// +/// Contains all items that were collected before an error occurred, and the stream error itself. +#[derive(PartialEq, Eq)] +pub struct TryReadyChunksError(pub Vec, pub E); + +impl fmt::Debug for TryReadyChunksError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.1.fmt(f) + } +} + +impl fmt::Display for TryReadyChunksError { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + self.1.fmt(f) + } +} + +#[cfg(feature = "std")] +impl std::error::Error for TryReadyChunksError {} diff --git a/futures-util/src/stream/try_stream/try_skip_while.rs b/futures-util/src/stream/try_stream/try_skip_while.rs index 0603b10f85..52aa2d478b 100644 --- a/futures-util/src/stream/try_stream/try_skip_while.rs +++ b/futures-util/src/stream/try_stream/try_skip_while.rs @@ -2,7 +2,7 @@ use core::fmt; use core::pin::Pin; use futures_core::future::TryFuture; use futures_core::ready; -use futures_core::stream::{Stream, TryStream, FusedStream}; +use futures_core::stream::{FusedStream, Stream, TryStream}; use futures_core::task::{Context, Poll}; #[cfg(feature = "sink")] use futures_sink::Sink; @@ -40,34 +40,27 @@ where } impl TrySkipWhile - where St: TryStream, - F: FnMut(&St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(&St::Ok) -> Fut, + Fut: TryFuture, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - pending_fut: None, - pending_item: None, - done_skipping: false, - } + Self { stream, f, pending_fut: None, pending_item: None, done_skipping: false } } delegate_access_inner!(stream, St, ()); } impl Stream for TrySkipWhile - where St: TryStream, - F: FnMut(&St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream, + F: FnMut(&St::Ok) -> Fut, + Fut: TryFuture, { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); if *this.done_skipping { @@ -94,7 +87,7 @@ impl Stream for TrySkipWhile } fn size_hint(&self) -> (usize, Option) { - let pending_len = if self.pending_item.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending_item.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), @@ -105,9 +98,10 @@ impl Stream for TrySkipWhile } impl FusedStream for TrySkipWhile - where St: TryStream + FusedStream, - F: FnMut(&St::Ok) -> Fut, - Fut: TryFuture, +where + St: TryStream + FusedStream, + F: FnMut(&St::Ok) -> Fut, + Fut: TryFuture, { fn is_terminated(&self) -> bool { self.pending_item.is_none() && self.stream.is_terminated() @@ -117,7 +111,8 @@ impl FusedStream for TrySkipWhile // Forwarding impl of Sink from the underlying stream #[cfg(feature = "sink")] impl Sink for TrySkipWhile - where S: TryStream + Sink, +where + S: TryStream + Sink, { type Error = E; diff --git a/futures-util/src/stream/try_stream/try_take_while.rs b/futures-util/src/stream/try_stream/try_take_while.rs index 624157290c..4b5ff1ad38 100644 --- a/futures-util/src/stream/try_stream/try_take_while.rs +++ b/futures-util/src/stream/try_stream/try_take_while.rs @@ -49,13 +49,7 @@ where Fut: TryFuture, { pub(super) fn new(stream: St, f: F) -> Self { - Self { - stream, - f, - pending_fut: None, - pending_item: None, - done_taking: false, - } + Self { stream, f, pending_fut: None, pending_item: None, done_taking: false } } delegate_access_inner!(stream, St, ()); @@ -102,7 +96,7 @@ where return (0, Some(0)); } - let pending_len = if self.pending_item.is_some() { 1 } else { 0 }; + let pending_len = usize::from(self.pending_item.is_some()); let (_, upper) = self.stream.size_hint(); let upper = match upper { Some(x) => x.checked_add(pending_len), diff --git a/futures-util/src/stream/try_stream/try_unfold.rs b/futures-util/src/stream/try_stream/try_unfold.rs index 258c18e461..fd9cdf1d8c 100644 --- a/futures-util/src/stream/try_stream/try_unfold.rs +++ b/futures-util/src/stream/try_stream/try_unfold.rs @@ -61,11 +61,7 @@ where F: FnMut(T) -> Fut, Fut: TryFuture>, { - assert_stream::, _>(TryUnfold { - f, - state: Some(init), - fut: None, - }) + assert_stream::, _>(TryUnfold { f, state: Some(init), fut: None }) } pin_project! { @@ -85,10 +81,7 @@ where Fut: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("TryUnfold") - .field("state", &self.state) - .field("fut", &self.fut) - .finish() + f.debug_struct("TryUnfold").field("state", &self.state).field("fut", &self.fut).finish() } } @@ -99,10 +92,7 @@ where { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.project(); if let Some(state) = this.state.take() { diff --git a/futures-util/src/stream/unfold.rs b/futures-util/src/stream/unfold.rs index e17d46515c..2f48cccb44 100644 --- a/futures-util/src/stream/unfold.rs +++ b/futures-util/src/stream/unfold.rs @@ -36,7 +36,7 @@ use pin_project_lite::pin_project; /// let stream = stream::unfold(0, |state| async move { /// if state <= 2 { /// let next_state = state + 1; -/// let yielded = state * 2; +/// let yielded = state * 2; /// Some((yielded, next_state)) /// } else { /// None @@ -52,10 +52,7 @@ where F: FnMut(T) -> Fut, Fut: Future>, { - assert_stream::(Unfold { - f, - state: UnfoldState::Value { value: init }, - }) + assert_stream::(Unfold { f, state: UnfoldState::Value { value: init } }) } pin_project! { @@ -74,9 +71,7 @@ where Fut: fmt::Debug, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.debug_struct("Unfold") - .field("state", &self.state) - .finish() + f.debug_struct("Unfold").field("state", &self.state).finish() } } @@ -105,9 +100,7 @@ where let mut this = self.project(); if let Some(state) = this.state.as_mut().take_value() { - this.state.set(UnfoldState::Future { - future: (this.f)(state), - }); + this.state.set(UnfoldState::Future { future: (this.f)(state) }); } let step = match this.state.as_mut().project_future() { diff --git a/futures-util/src/task/mod.rs b/futures-util/src/task/mod.rs index dd1515c7fa..7a9e993e5e 100644 --- a/futures-util/src/task/mod.rs +++ b/futures-util/src/task/mod.rs @@ -11,29 +11,30 @@ //! executors or dealing with synchronization issues around task wakeup. #[doc(no_inline)] -pub use core::task::{Context, Poll, Waker, RawWaker, RawWakerVTable}; +pub use core::task::{Context, Poll, RawWaker, RawWakerVTable, Waker}; -pub use futures_task::{ - Spawn, LocalSpawn, SpawnError, - FutureObj, LocalFutureObj, UnsafeFutureObj, -}; +pub use futures_task::{FutureObj, LocalFutureObj, LocalSpawn, Spawn, SpawnError, UnsafeFutureObj}; pub use futures_task::noop_waker; -#[cfg(feature = "std")] pub use futures_task::noop_waker_ref; -cfg_target_has_atomic! { - #[cfg(feature = "alloc")] - pub use futures_task::ArcWake; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use futures_task::ArcWake; - #[cfg(feature = "alloc")] - pub use futures_task::waker; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use futures_task::waker; - #[cfg(feature = "alloc")] - pub use futures_task::{waker_ref, WakerRef}; +#[cfg_attr(target_os = "none", cfg(target_has_atomic = "ptr"))] +#[cfg(feature = "alloc")] +pub use futures_task::{waker_ref, WakerRef}; - pub use futures_core::task::__internal::AtomicWaker; -} +#[cfg_attr( + target_os = "none", + cfg(any(target_has_atomic = "ptr", feature = "portable-atomic")) +)] +pub use futures_core::task::__internal::AtomicWaker; mod spawn; -pub use self::spawn::{SpawnExt, LocalSpawnExt}; +pub use self::spawn::{LocalSpawnExt, SpawnExt}; diff --git a/futures-util/src/task/spawn.rs b/futures-util/src/task/spawn.rs index f8779230ed..d9e9985309 100644 --- a/futures-util/src/task/spawn.rs +++ b/futures-util/src/task/spawn.rs @@ -34,6 +34,7 @@ pub trait SpawnExt: Spawn { /// today. Feel free to use this method in the meantime. /// /// ``` + /// # { /// use futures::executor::ThreadPool; /// use futures::task::SpawnExt; /// @@ -41,6 +42,8 @@ pub trait SpawnExt: Spawn { /// /// let future = async { /* ... */ }; /// executor.spawn(future).unwrap(); + /// # } + /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 /// ``` #[cfg(feature = "alloc")] fn spawn(&self, future: Fut) -> Result<(), SpawnError> @@ -58,6 +61,7 @@ pub trait SpawnExt: Spawn { /// resolves to the output of the spawned future. /// /// ``` + /// # { /// use futures::executor::{block_on, ThreadPool}; /// use futures::future; /// use futures::task::SpawnExt; @@ -67,6 +71,8 @@ pub trait SpawnExt: Spawn { /// let future = future::ready(1); /// let join_handle_fut = executor.spawn_with_handle(future).unwrap(); /// assert_eq!(block_on(join_handle_fut), 1); + /// # } + /// # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 /// ``` #[cfg(feature = "channel")] #[cfg_attr(docsrs, doc(cfg(feature = "channel")))] diff --git a/futures-util/src/unfold_state.rs b/futures-util/src/unfold_state.rs index 0edc15e437..b66956bbd8 100644 --- a/futures-util/src/unfold_state.rs +++ b/futures-util/src/unfold_state.rs @@ -29,7 +29,7 @@ impl UnfoldState { pub(crate) fn take_value(self: Pin<&mut Self>) -> Option { match &*self { - UnfoldState::Value { .. } => match self.project_replace(UnfoldState::Empty) { + Self::Value { .. } => match self.project_replace(Self::Empty) { UnfoldStateProjReplace::Value { value } => Some(value), _ => unreachable!(), }, diff --git a/futures/Cargo.toml b/futures/Cargo.toml index d046c54215..102f9215cc 100644 --- a/futures/Cargo.toml +++ b/futures/Cargo.toml @@ -1,14 +1,13 @@ [package] name = "futures" +version = "0.3.31" edition = "2018" -version = "0.3.13" -authors = ["Alex Crichton "] +rust-version = "1.56" license = "MIT OR Apache-2.0" readme = "../README.md" keywords = ["futures", "async", "future"] repository = "https://github.com/rust-lang/futures-rs" homepage = "https://rust-lang.github.io/futures-rs" -documentation = "https://docs.rs/futures/0.3" description = """ An implementation of futures and streams featuring zero allocations, composability, and iterator-like interfaces. @@ -16,20 +15,19 @@ composability, and iterator-like interfaces. categories = ["asynchronous"] [dependencies] -futures-core = { path = "../futures-core", version = "0.3.13", default-features = false } -futures-task = { path = "../futures-task", version = "0.3.13", default-features = false } -futures-channel = { path = "../futures-channel", version = "0.3.13", default-features = false, features = ["sink"] } -futures-executor = { path = "../futures-executor", version = "0.3.13", default-features = false, optional = true } -futures-io = { path = "../futures-io", version = "0.3.13", default-features = false } -futures-sink = { path = "../futures-sink", version = "0.3.13", default-features = false } -futures-util = { path = "../futures-util", version = "0.3.13", default-features = false, features = ["sink"] } +futures-core = { path = "../futures-core", version = "0.3.31", default-features = false } +futures-task = { path = "../futures-task", version = "0.3.31", default-features = false } +futures-channel = { path = "../futures-channel", version = "0.3.31", default-features = false, features = ["sink"] } +futures-executor = { path = "../futures-executor", version = "0.3.31", default-features = false, optional = true } +futures-io = { path = "../futures-io", version = "0.3.31", default-features = false } +futures-sink = { path = "../futures-sink", version = "0.3.31", default-features = false } +futures-util = { path = "../futures-util", version = "0.3.31", default-features = false, features = ["sink"] } [dev-dependencies] futures-executor = { path = "../futures-executor", features = ["thread-pool"] } futures-test = { path = "../futures-test" } assert_matches = "1.3.0" -pin-project = "1.0.1" -pin-utils = "0.1.0" +pin-project = "1.0.11" static_assertions = "1" tokio = "0.1.11" @@ -47,14 +45,19 @@ thread-pool = ["executor", "futures-executor/thread-pool"] # These features are outside of the normal semver guarantees and require the # `unstable` feature as an explicit opt-in to unstable API. unstable = ["futures-core/unstable", "futures-task/unstable", "futures-channel/unstable", "futures-io/unstable", "futures-util/unstable"] -cfg-target-has-atomic = ["futures-core/cfg-target-has-atomic", "futures-task/cfg-target-has-atomic", "futures-channel/cfg-target-has-atomic", "futures-util/cfg-target-has-atomic"] bilock = ["futures-util/bilock"] -read-initializer = ["futures-io/read-initializer", "futures-util/read-initializer"] write-all-vectored = ["futures-util/write-all-vectored"] +# These features are no longer used. +# TODO: remove in the next major version. +cfg-target-has-atomic = [] + [package.metadata.docs.rs] all-features = true rustdoc-args = ["--cfg", "docsrs"] [package.metadata.playground] features = ["std", "async-await", "compat", "io-compat", "executor", "thread-pool"] + +[lints] +workspace = true diff --git a/futures/src/lib.rs b/futures/src/lib.rs index de29ace218..654576fcda 100644 --- a/futures/src/lib.rs +++ b/futures/src/lib.rs @@ -3,12 +3,12 @@ //! This crate provides a number of core abstractions for writing asynchronous //! code: //! -//! - [Futures](crate::future::Future) are single eventual values produced by +//! - [Futures](crate::future) are single eventual values produced by //! asynchronous computations. Some programming languages (e.g. JavaScript) //! call this concept "promise". -//! - [Streams](crate::stream::Stream) represent a series of values +//! - [Streams](crate::stream) represent a series of values //! produced asynchronously. -//! - [Sinks](crate::sink::Sink) provide support for asynchronous writing of +//! - [Sinks](crate::sink) provide support for asynchronous writing of //! data. //! - [Executors](crate::executor) are responsible for running asynchronous //! tasks. @@ -29,8 +29,9 @@ //! # use futures::executor; ///standard executors to provide a context for futures and streams //! # use futures::executor::ThreadPool; //! # use futures::StreamExt; -//! +//! # //! fn main() { +//! # { //! let pool = ThreadPool::new().expect("Failed to build pool"); //! let (tx, rx) = mpsc::unbounded::(); //! @@ -67,59 +68,53 @@ //! }; //! //! // Actually execute the above future, which will invoke Future::poll and -//! // subsequenty chain appropriate Future::poll and methods needing executors +//! // subsequently chain appropriate Future::poll and methods needing executors //! // to drive all futures. Eventually fut_values will be driven to completion. //! let values: Vec = executor::block_on(fut_values); //! //! println!("Values={:?}", values); +//! # } +//! # std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 //! } //! ``` //! //! The majority of examples and code snippets in this crate assume that they are //! inside an async block as written above. -#![cfg_attr(feature = "cfg-target-has-atomic", feature(cfg_target_has_atomic))] -#![cfg_attr(feature = "read-initializer", feature(read_initializer))] - -#![cfg_attr(not(feature = "std"), no_std)] - -#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms, unreachable_pub)] -// It cannot be included in the published code because this lints have false positives in the minimum required version. -#![cfg_attr(test, warn(single_use_lifetimes))] -#![warn(clippy::all)] -#![doc(test(attr(deny(warnings), allow(dead_code, unused_assignments, unused_variables))))] - +#![no_std] +#![doc(test( + no_crate_inject, + attr( + deny(warnings, rust_2018_idioms, single_use_lifetimes), + allow(dead_code, unused_assignments, unused_variables) + ) +))] +#![warn(missing_docs, unsafe_op_in_unsafe_fn)] #![cfg_attr(docsrs, feature(doc_cfg))] -#[cfg(all(feature = "cfg-target-has-atomic", not(feature = "unstable")))] -compile_error!("The `cfg-target-has-atomic` feature requires the `unstable` feature as an explicit opt-in to unstable features"); - #[cfg(all(feature = "bilock", not(feature = "unstable")))] compile_error!("The `bilock` feature requires the `unstable` feature as an explicit opt-in to unstable features"); -#[cfg(all(feature = "read-initializer", not(feature = "unstable")))] -compile_error!("The `read-initializer` feature requires the `unstable` feature as an explicit opt-in to unstable features"); - -#[doc(hidden)] +#[doc(no_inline)] pub use futures_core::future::{Future, TryFuture}; -#[doc(hidden)] +#[doc(no_inline)] pub use futures_util::future::{FutureExt, TryFutureExt}; -#[doc(hidden)] +#[doc(no_inline)] pub use futures_core::stream::{Stream, TryStream}; -#[doc(hidden)] +#[doc(no_inline)] pub use futures_util::stream::{StreamExt, TryStreamExt}; -#[doc(hidden)] +#[doc(no_inline)] pub use futures_sink::Sink; -#[doc(hidden)] +#[doc(no_inline)] pub use futures_util::sink::SinkExt; #[cfg(feature = "std")] -#[doc(hidden)] +#[doc(no_inline)] pub use futures_io::{AsyncBufRead, AsyncRead, AsyncSeek, AsyncWrite}; #[cfg(feature = "std")] -#[doc(hidden)] +#[doc(no_inline)] pub use futures_util::{AsyncBufReadExt, AsyncReadExt, AsyncSeekExt, AsyncWriteExt}; // Macro reexports @@ -135,6 +130,10 @@ pub use futures_util::{join, pending, poll, select_biased, try_join}; // Async-a #[doc(inline)] pub use futures_util::{future, never, sink, stream, task}; +#[cfg(feature = "std")] +#[cfg(feature = "async-await")] +pub use futures_util::stream_select; + #[cfg(feature = "alloc")] #[doc(inline)] pub use futures_channel as channel; @@ -148,13 +147,73 @@ pub use futures_util::io; #[cfg(feature = "executor")] #[cfg_attr(docsrs, doc(cfg(feature = "executor")))] -#[doc(inline)] -pub use futures_executor as executor; +pub mod executor { + //! Built-in executors and related tools. + //! + //! All asynchronous computation occurs within an executor, which is + //! capable of spawning futures as tasks. This module provides several + //! built-in executors, as well as tools for building your own. + //! + //! + //! This module is only available when the `executor` feature of this + //! library is activated. + //! + //! # Using a thread pool (M:N task scheduling) + //! + //! Most of the time tasks should be executed on a [thread pool](ThreadPool). + //! A small set of worker threads can handle a very large set of spawned tasks + //! (which are much lighter weight than threads). Tasks spawned onto the pool + //! with the [`spawn_ok`](ThreadPool::spawn_ok) function will run ambiently on + //! the created threads. + //! + //! # Spawning additional tasks + //! + //! Tasks can be spawned onto a spawner by calling its [`spawn_obj`] method + //! directly. In the case of `!Send` futures, [`spawn_local_obj`] can be used + //! instead. + //! + //! # Single-threaded execution + //! + //! In addition to thread pools, it's possible to run a task (and the tasks + //! it spawns) entirely within a single thread via the [`LocalPool`] executor. + //! Aside from cutting down on synchronization costs, this executor also makes + //! it possible to spawn non-`Send` tasks, via [`spawn_local_obj`]. The + //! [`LocalPool`] is best suited for running I/O-bound tasks that do relatively + //! little work between I/O operations. + //! + //! There is also a convenience function [`block_on`] for simply running a + //! future to completion on the current thread. + //! + //! [`spawn_obj`]: https://docs.rs/futures/0.3/futures/task/trait.Spawn.html#tymethod.spawn_obj + //! [`spawn_local_obj`]: https://docs.rs/futures/0.3/futures/task/trait.LocalSpawn.html#tymethod.spawn_local_obj + + pub use futures_executor::{ + block_on, block_on_stream, enter, BlockingStream, Enter, EnterError, LocalPool, + LocalSpawner, + }; + + #[cfg(feature = "thread-pool")] + #[cfg_attr(docsrs, doc(cfg(feature = "thread-pool")))] + pub use futures_executor::{ThreadPool, ThreadPoolBuilder}; +} #[cfg(feature = "compat")] #[cfg_attr(docsrs, doc(cfg(feature = "compat")))] -#[doc(inline)] -pub use futures_util::compat; +pub mod compat { + //! Interop between `futures` 0.1 and 0.3. + //! + //! This module is only available when the `compat` feature of this + //! library is activated. + + pub use futures_util::compat::{ + Compat, Compat01As03, Compat01As03Sink, CompatSink, Executor01As03, Executor01CompatExt, + Executor01Future, Future01CompatExt, Sink01CompatExt, Stream01CompatExt, + }; + + #[cfg(feature = "io-compat")] + #[cfg_attr(docsrs, doc(cfg(feature = "io-compat")))] + pub use futures_util::compat::{AsyncRead01CompatExt, AsyncWrite01CompatExt}; +} pub mod prelude { //! A "prelude" for crates using the `futures` crate. @@ -175,10 +234,12 @@ pub mod prelude { pub use crate::stream::{self, Stream, TryStream}; #[doc(no_inline)] + #[allow(unreachable_pub)] pub use crate::future::{FutureExt as _, TryFutureExt as _}; #[doc(no_inline)] pub use crate::sink::SinkExt as _; #[doc(no_inline)] + #[allow(unreachable_pub)] pub use crate::stream::{StreamExt as _, TryStreamExt as _}; #[cfg(feature = "std")] @@ -186,6 +247,7 @@ pub mod prelude { #[cfg(feature = "std")] #[doc(no_inline)] + #[allow(unreachable_pub)] pub use crate::io::{ AsyncBufReadExt as _, AsyncReadExt as _, AsyncSeekExt as _, AsyncWriteExt as _, }; diff --git a/futures/tests/_require_features.rs b/futures/tests/_require_features.rs index da76dcd1e9..8046cc99a4 100644 --- a/futures/tests/_require_features.rs +++ b/futures/tests/_require_features.rs @@ -1,8 +1,13 @@ #[cfg(not(all( - feature = "std", feature = "alloc", feature = "async-await", - feature = "compat", feature = "io-compat", - feature = "executor", feature = "thread-pool", + feature = "std", + feature = "alloc", + feature = "async-await", + feature = "compat", + feature = "io-compat", + feature = "executor", + feature = "thread-pool", )))] -compile_error!("`futures` tests must have all stable features activated: \ +compile_error!( + "`futures` tests must have all stable features activated: \ use `--all-features` or `--features default,thread-pool,io-compat`" ); diff --git a/futures/tests/arc_wake.rs b/futures/tests/arc_wake.rs deleted file mode 100644 index d19a83dfac..0000000000 --- a/futures/tests/arc_wake.rs +++ /dev/null @@ -1,82 +0,0 @@ -mod countingwaker { - use futures::task::{self, ArcWake, Waker}; - use std::sync::{Arc, Mutex}; - - struct CountingWaker { - nr_wake: Mutex, - } - - impl CountingWaker { - fn new() -> Self { - Self { - nr_wake: Mutex::new(0), - } - } - - fn wakes(&self) -> i32 { - *self.nr_wake.lock().unwrap() - } - } - - impl ArcWake for CountingWaker { - fn wake_by_ref(arc_self: &Arc) { - let mut lock = arc_self.nr_wake.lock().unwrap(); - *lock += 1; - } - } - - #[test] - fn create_from_arc() { - let some_w = Arc::new(CountingWaker::new()); - - let w1: Waker = task::waker(some_w.clone()); - assert_eq!(2, Arc::strong_count(&some_w)); - w1.wake_by_ref(); - assert_eq!(1, some_w.wakes()); - - let w2 = w1.clone(); - assert_eq!(3, Arc::strong_count(&some_w)); - - w2.wake_by_ref(); - assert_eq!(2, some_w.wakes()); - - drop(w2); - assert_eq!(2, Arc::strong_count(&some_w)); - drop(w1); - assert_eq!(1, Arc::strong_count(&some_w)); - } - - #[test] - fn ref_wake_same() { - let some_w = Arc::new(CountingWaker::new()); - - let w1: Waker = task::waker(some_w.clone()); - let w2 = task::waker_ref(&some_w); - let w3 = w2.clone(); - - assert!(w1.will_wake(&w2)); - assert!(w2.will_wake(&w3)); - } -} - -#[test] -fn proper_refcount_on_wake_panic() { - use futures::task::{self, ArcWake, Waker}; - use std::sync::Arc; - - struct PanicWaker; - - impl ArcWake for PanicWaker { - fn wake_by_ref(_arc_self: &Arc) { - panic!("WAKE UP"); - } - } - - let some_w = Arc::new(PanicWaker); - - let w1: Waker = task::waker(some_w.clone()); - assert_eq!("WAKE UP", *std::panic::catch_unwind(|| w1.wake_by_ref()).unwrap_err().downcast::<&str>().unwrap()); - assert_eq!(2, Arc::strong_count(&some_w)); // some_w + w1 - drop(w1); - assert_eq!(1, Arc::strong_count(&some_w)); // some_w -} diff --git a/futures/tests/async_await_macros.rs b/futures/tests/async_await_macros.rs index bd586d6e52..0bc79e4ae8 100644 --- a/futures/tests/async_await_macros.rs +++ b/futures/tests/async_await_macros.rs @@ -1,9 +1,16 @@ +use futures::channel::{mpsc, oneshot}; +use futures::executor::block_on; +use futures::future::{self, poll_fn, FutureExt}; +use futures::sink::SinkExt; +use futures::stream::StreamExt; +use futures::task::{Context, Poll}; +use futures::{ + join, pending, pin_mut, poll, select, select_biased, stream, stream_select, try_join, +}; +use std::mem; + #[test] fn poll_and_pending() { - use futures::{pending, pin_mut, poll}; - use futures::executor::block_on; - use futures::task::Poll; - let pending_once = async { pending!() }; block_on(async { pin_mut!(pending_once); @@ -14,11 +21,6 @@ fn poll_and_pending() { #[test] fn join() { - use futures::{pin_mut, poll, join}; - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::task::Poll; - let (tx1, rx1) = oneshot::channel::(); let (tx2, rx2) = oneshot::channel::(); @@ -39,11 +41,6 @@ fn join() { #[test] fn select() { - use futures::select; - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::FutureExt; - let (tx1, rx1) = oneshot::channel::(); let (_tx2, rx2) = oneshot::channel::(); tx1.send(1).unwrap(); @@ -61,12 +58,19 @@ fn select() { } #[test] -fn select_biased() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::FutureExt; - use futures::select_biased; +fn select_grammar() { + // Parsing after `=>` using Expr::parse would parse `{}() = future::ready(())` + // as one expression. + block_on(async { + select! { + () = future::pending::<()>() => {} + () = future::ready(()) => {} + } + }); +} +#[test] +fn select_biased() { let (tx1, rx1) = oneshot::channel::(); let (_tx2, rx2) = oneshot::channel::(); tx1.send(1).unwrap(); @@ -85,12 +89,6 @@ fn select_biased() { #[test] fn select_streams() { - use futures::select; - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::sink::SinkExt; - use futures::stream::StreamExt; - let (mut tx1, rx1) = mpsc::channel::(1); let (mut tx2, rx2) = mpsc::channel::(1); let mut rx1 = rx1.fuse(); @@ -134,11 +132,6 @@ fn select_streams() { #[test] fn select_can_move_uncompleted_futures() { - use futures::select; - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::FutureExt; - let (tx1, rx1) = oneshot::channel::(); let (tx2, rx2) = oneshot::channel::(); tx1.send(1).unwrap(); @@ -165,10 +158,6 @@ fn select_can_move_uncompleted_futures() { #[test] fn select_nested() { - use futures::select; - use futures::executor::block_on; - use futures::future; - let mut outer_fut = future::ready(1); let mut inner_fut = future::ready(2); let res = block_on(async { @@ -183,18 +172,16 @@ fn select_nested() { assert_eq!(res, 3); } +#[cfg_attr(not(target_pointer_width = "64"), ignore)] #[test] fn select_size() { - use futures::select; - use futures::future; - let fut = async { let mut ready = future::ready(0i32); select! { _ = ready => {}, } }; - assert_eq!(::std::mem::size_of_val(&fut), 24); + assert_eq!(mem::size_of_val(&fut), 24); let fut = async { let mut ready1 = future::ready(0i32); @@ -204,19 +191,13 @@ fn select_size() { _ = ready2 => {}, } }; - assert_eq!(::std::mem::size_of_val(&fut), 40); + assert_eq!(mem::size_of_val(&fut), 40); } #[test] fn select_on_non_unpin_expressions() { - use futures::select; - use futures::executor::block_on; - use futures::future::FutureExt; - // The returned Future is !Unpin - let make_non_unpin_fut = || { async { - 5 - }}; + let make_non_unpin_fut = || async { 5 }; let res = block_on(async { let select_res; @@ -231,14 +212,8 @@ fn select_on_non_unpin_expressions() { #[test] fn select_on_non_unpin_expressions_with_default() { - use futures::select; - use futures::executor::block_on; - use futures::future::FutureExt; - // The returned Future is !Unpin - let make_non_unpin_fut = || { async { - 5 - }}; + let make_non_unpin_fut = || async { 5 }; let res = block_on(async { let select_res; @@ -252,15 +227,11 @@ fn select_on_non_unpin_expressions_with_default() { assert_eq!(res, 5); } +#[cfg_attr(not(target_pointer_width = "64"), ignore)] #[test] fn select_on_non_unpin_size() { - use futures::select; - use futures::future::FutureExt; - // The returned Future is !Unpin - let make_non_unpin_fut = || { async { - 5 - }}; + let make_non_unpin_fut = || async { 5 }; let fut = async { let select_res; @@ -271,15 +242,11 @@ fn select_on_non_unpin_size() { select_res }; - assert_eq!(32, std::mem::size_of_val(&fut)); + assert_eq!(32, mem::size_of_val(&fut)); } #[test] fn select_can_be_used_as_expression() { - use futures::select; - use futures::executor::block_on; - use futures::future; - block_on(async { let res = select! { x = future::ready(7) => x, @@ -291,11 +258,6 @@ fn select_can_be_used_as_expression() { #[test] fn select_with_default_can_be_used_as_expression() { - use futures::select; - use futures::executor::block_on; - use futures::future::{FutureExt, poll_fn}; - use futures::task::{Context, Poll}; - fn poll_always_pending(_cx: &mut Context<'_>) -> Poll { Poll::Pending } @@ -312,10 +274,6 @@ fn select_with_default_can_be_used_as_expression() { #[test] fn select_with_complete_can_be_used_as_expression() { - use futures::select; - use futures::executor::block_on; - use futures::future; - block_on(async { let res = select! { x = future::pending::() => x, @@ -330,10 +288,6 @@ fn select_with_complete_can_be_used_as_expression() { #[test] #[allow(unused_assignments)] fn select_on_mutable_borrowing_future_with_same_borrow_in_block() { - use futures::select; - use futures::executor::block_on; - use futures::future::FutureExt; - async fn require_mutable(_: &mut i32) {} async fn async_noop() {} @@ -351,10 +305,6 @@ fn select_on_mutable_borrowing_future_with_same_borrow_in_block() { #[test] #[allow(unused_assignments)] fn select_on_mutable_borrowing_future_with_same_borrow_in_block_and_default() { - use futures::select; - use futures::executor::block_on; - use futures::future::FutureExt; - async fn require_mutable(_: &mut i32) {} async fn async_noop() {} @@ -373,60 +323,83 @@ fn select_on_mutable_borrowing_future_with_same_borrow_in_block_and_default() { } #[test] -fn join_size() { - use futures::join; - use futures::future; +#[allow(unused_assignments)] +fn stream_select() { + // stream_select! macro + block_on(async { + let endless_ints = |i| stream::iter(vec![i].into_iter().cycle()); + + let mut endless_ones = stream_select!(endless_ints(1i32), stream::pending()); + assert_eq!(endless_ones.next().await, Some(1)); + assert_eq!(endless_ones.next().await, Some(1)); + + let mut finite_list = + stream_select!(stream::iter(vec![1].into_iter()), stream::iter(vec![1].into_iter())); + assert_eq!(finite_list.next().await, Some(1)); + assert_eq!(finite_list.next().await, Some(1)); + assert_eq!(finite_list.next().await, None); + + let endless_mixed = stream_select!(endless_ints(1i32), endless_ints(2), endless_ints(3)); + // Take 1000, and assert a somewhat even distribution of values. + // The fairness is randomized, but over 1000 samples we should be pretty close to even. + // This test may be a bit flaky. Feel free to adjust the margins as you see fit. + let mut count = 0; + let results = endless_mixed + .take_while(move |_| { + count += 1; + let ret = count < 1000; + async move { ret } + }) + .collect::>() + .await; + assert!(results.iter().filter(|x| **x == 1).count() >= 299); + assert!(results.iter().filter(|x| **x == 2).count() >= 299); + assert!(results.iter().filter(|x| **x == 3).count() >= 299); + }); +} +#[cfg_attr(not(target_pointer_width = "64"), ignore)] +#[test] +fn join_size() { let fut = async { let ready = future::ready(0i32); join!(ready) }; - assert_eq!(::std::mem::size_of_val(&fut), 16); + assert_eq!(mem::size_of_val(&fut), 24); let fut = async { let ready1 = future::ready(0i32); let ready2 = future::ready(0i32); join!(ready1, ready2) }; - assert_eq!(::std::mem::size_of_val(&fut), 28); + assert_eq!(mem::size_of_val(&fut), 40); } +#[cfg_attr(not(target_pointer_width = "64"), ignore)] #[test] fn try_join_size() { - use futures::try_join; - use futures::future; - let fut = async { let ready = future::ready(Ok::(0)); try_join!(ready) }; - assert_eq!(::std::mem::size_of_val(&fut), 16); + assert_eq!(mem::size_of_val(&fut), 24); let fut = async { let ready1 = future::ready(Ok::(0)); let ready2 = future::ready(Ok::(0)); try_join!(ready1, ready2) }; - assert_eq!(::std::mem::size_of_val(&fut), 28); + assert_eq!(mem::size_of_val(&fut), 48); } +#[allow(clippy::let_underscore_future)] #[test] fn join_doesnt_require_unpin() { - use futures::join; - - let _ = async { - join!(async {}, async {}) - }; + let _ = async { join!(async {}, async {}) }; } +#[allow(clippy::let_underscore_future)] #[test] fn try_join_doesnt_require_unpin() { - use futures::try_join; - - let _ = async { - try_join!( - async { Ok::<(), ()>(()) }, - async { Ok::<(), ()>(()) }, - ) - }; + let _ = async { try_join!(async { Ok::<(), ()>(()) }, async { Ok::<(), ()>(()) },) }; } diff --git a/futures/tests/auto_traits.rs b/futures/tests/auto_traits.rs index 111fdf6388..3385360591 100644 --- a/futures/tests/auto_traits.rs +++ b/futures/tests/auto_traits.rs @@ -1,4 +1,5 @@ #![cfg(feature = "compat")] +#![allow(dead_code)] //! Assert Send/Sync/Unpin for all public types. @@ -12,45 +13,49 @@ use static_assertions::{assert_impl_all as assert_impl, assert_not_impl_all as a use std::marker::PhantomPinned; use std::{marker::PhantomData, pin::Pin}; -pub type LocalFuture = Pin>>; -pub type LocalTryFuture = LocalFuture>; -pub type SendFuture = Pin + Send>>; -pub type SendTryFuture = SendFuture>; -pub type SyncFuture = Pin + Sync>>; -pub type SyncTryFuture = SyncFuture>; -pub type UnpinFuture = LocalFuture; -pub type UnpinTryFuture = UnpinFuture>; -pub struct PinnedFuture(PhantomPinned, PhantomData); +type LocalFuture = Pin>>; +type LocalTryFuture = LocalFuture>; +type SendFuture = Pin + Send>>; +type SendTryFuture = SendFuture>; +type SyncFuture = Pin + Sync>>; +type SyncTryFuture = SyncFuture>; +type SendSyncFuture = Pin + Send + Sync>>; +type SendSyncTryFuture = SendSyncFuture>; +type UnpinFuture = LocalFuture; +type UnpinTryFuture = UnpinFuture>; +struct PinnedFuture(PhantomPinned, PhantomData); impl Future for PinnedFuture { type Output = T; fn poll(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { unimplemented!() } } -pub type PinnedTryFuture = PinnedFuture>; - -pub type LocalStream = Pin>>; -pub type LocalTryStream = LocalStream>; -pub type SendStream = Pin + Send>>; -pub type SendTryStream = SendStream>; -pub type SyncStream = Pin + Sync>>; -pub type SyncTryStream = SyncStream>; -pub type UnpinStream = LocalStream; -pub type UnpinTryStream = UnpinStream>; -pub struct PinnedStream(PhantomPinned, PhantomData); +type PinnedTryFuture = PinnedFuture>; + +type LocalStream = Pin>>; +type LocalTryStream = LocalStream>; +type SendStream = Pin + Send>>; +type SendTryStream = SendStream>; +type SyncStream = Pin + Sync>>; +type SyncTryStream = SyncStream>; +type SendSyncStream = Pin + Send + Sync>>; +type SendSyncTryStream = SendSyncStream>; +type UnpinStream = LocalStream; +type UnpinTryStream = UnpinStream>; +struct PinnedStream(PhantomPinned, PhantomData); impl Stream for PinnedStream { type Item = T; fn poll_next(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { unimplemented!() } } -pub type PinnedTryStream = PinnedStream>; +type PinnedTryStream = PinnedStream>; -pub type LocalSink = Pin>>; -pub type SendSink = Pin + Send>>; -pub type SyncSink = Pin + Sync>>; -pub type UnpinSink = LocalSink; -pub struct PinnedSink(PhantomPinned, PhantomData<(T, E)>); +type LocalSink = Pin>>; +type SendSink = Pin + Send>>; +type SyncSink = Pin + Sync>>; +type UnpinSink = LocalSink; +struct PinnedSink(PhantomPinned, PhantomData<(T, E)>); impl Sink for PinnedSink { type Error = E; fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { @@ -68,7 +73,7 @@ impl Sink for PinnedSink { } /// Assert Send/Sync/Unpin for all public types in `futures::channel`. -pub mod channel { +mod channel { use super::*; use futures::channel::*; @@ -115,11 +120,11 @@ pub mod channel { assert_impl!(oneshot::Canceled: Sync); assert_impl!(oneshot::Canceled: Unpin); - assert_impl!(oneshot::Cancellation<()>: Send); - assert_not_impl!(oneshot::Cancellation<*const ()>: Send); - assert_impl!(oneshot::Cancellation<()>: Sync); - assert_not_impl!(oneshot::Cancellation<*const ()>: Sync); - assert_impl!(oneshot::Cancellation: Unpin); + assert_impl!(oneshot::Cancellation<'_, ()>: Send); + assert_not_impl!(oneshot::Cancellation<'_, *const ()>: Send); + assert_impl!(oneshot::Cancellation<'_, ()>: Sync); + assert_not_impl!(oneshot::Cancellation<'_, *const ()>: Sync); + assert_impl!(oneshot::Cancellation<'_, PhantomPinned>: Unpin); assert_impl!(oneshot::Receiver<()>: Send); assert_not_impl!(oneshot::Receiver<*const ()>: Send); @@ -135,7 +140,7 @@ pub mod channel { } /// Assert Send/Sync/Unpin for all public types in `futures::compat`. -pub mod compat { +mod compat { use super::*; use futures::compat::*; @@ -177,7 +182,7 @@ pub mod compat { } /// Assert Send/Sync/Unpin for all public types in `futures::executor`. -pub mod executor { +mod executor { use super::*; use futures::executor::*; @@ -215,7 +220,7 @@ pub mod executor { } /// Assert Send/Sync/Unpin for all public types in `futures::future`. -pub mod future { +mod future { use super::*; use futures::future::*; @@ -301,9 +306,9 @@ pub mod future { assert_impl!(Fuse: Unpin); assert_not_impl!(Fuse: Unpin); - assert_impl!(FutureObj<*const ()>: Send); - assert_not_impl!(FutureObj<()>: Sync); - assert_impl!(FutureObj: Unpin); + assert_impl!(FutureObj<'_, *const ()>: Send); + assert_not_impl!(FutureObj<'_, ()>: Sync); + assert_impl!(FutureObj<'_, PhantomPinned>: Unpin); assert_impl!(Inspect: Send); assert_not_impl!(Inspect: Send); @@ -365,9 +370,10 @@ pub mod future { assert_impl!(JoinAll>: Send); assert_not_impl!(JoinAll: Send); assert_not_impl!(JoinAll: Send); - assert_impl!(JoinAll>: Sync); - assert_not_impl!(JoinAll: Sync); - assert_not_impl!(JoinAll: Sync); + assert_impl!(JoinAll>: Sync); + assert_not_impl!(JoinAll>: Sync); + assert_not_impl!(JoinAll>: Sync); + assert_not_impl!(JoinAll: Sync); assert_impl!(JoinAll: Unpin); assert_impl!(Lazy<()>: Send); @@ -376,9 +382,9 @@ pub mod future { assert_not_impl!(Lazy<*const ()>: Sync); assert_impl!(Lazy: Unpin); - assert_not_impl!(LocalFutureObj<()>: Send); - assert_not_impl!(LocalFutureObj<()>: Sync); - assert_impl!(LocalFutureObj: Unpin); + assert_not_impl!(LocalFutureObj<'_, ()>: Send); + assert_not_impl!(LocalFutureObj<'_, ()>: Sync); + assert_impl!(LocalFutureObj<'_, PhantomPinned>: Unpin); assert_impl!(Map: Send); assert_not_impl!(Map: Send); @@ -470,6 +476,13 @@ pub mod future { assert_not_impl!(PollFn<*const ()>: Sync); assert_impl!(PollFn: Unpin); + assert_impl!(PollImmediate: Send); + assert_not_impl!(PollImmediate>: Send); + assert_impl!(PollImmediate: Sync); + assert_not_impl!(PollImmediate>: Sync); + assert_impl!(PollImmediate: Unpin); + assert_not_impl!(PollImmediate: Unpin); + assert_impl!(Ready<()>: Send); assert_not_impl!(Ready<*const ()>: Send); assert_impl!(Ready<()>: Sync); @@ -569,12 +582,13 @@ pub mod future { // TryJoin3, TryJoin4, TryJoin5 are the same as TryJoin - assert_impl!(TryJoinAll>: Send); + assert_impl!(TryJoinAll>: Send); assert_not_impl!(TryJoinAll: Send); assert_not_impl!(TryJoinAll: Send); - assert_impl!(TryJoinAll>: Sync); - assert_not_impl!(TryJoinAll: Sync); - assert_not_impl!(TryJoinAll: Sync); + assert_impl!(TryJoinAll>: Sync); + assert_not_impl!(TryJoinAll>: Sync); + assert_not_impl!(TryJoinAll>: Sync); + assert_not_impl!(TryJoinAll: Sync); assert_impl!(TryJoinAll: Unpin); assert_impl!(TrySelect: Send); @@ -639,7 +653,7 @@ pub mod future { } /// Assert Send/Sync/Unpin for all public types in `futures::io`. -pub mod io { +mod io { use super::*; use futures::io::{Sink, *}; @@ -680,23 +694,23 @@ pub mod io { assert_impl!(Close<'_, ()>: Unpin); assert_not_impl!(Close<'_, PhantomPinned>: Unpin); - assert_impl!(Copy<(), ()>: Send); - assert_not_impl!(Copy<(), *const ()>: Send); - assert_not_impl!(Copy<*const (), ()>: Send); - assert_impl!(Copy<(), ()>: Sync); - assert_not_impl!(Copy<(), *const ()>: Sync); - assert_not_impl!(Copy<*const (), ()>: Sync); - assert_impl!(Copy<(), PhantomPinned>: Unpin); - assert_not_impl!(Copy: Unpin); - - assert_impl!(CopyBuf<(), ()>: Send); - assert_not_impl!(CopyBuf<(), *const ()>: Send); - assert_not_impl!(CopyBuf<*const (), ()>: Send); - assert_impl!(CopyBuf<(), ()>: Sync); - assert_not_impl!(CopyBuf<(), *const ()>: Sync); - assert_not_impl!(CopyBuf<*const (), ()>: Sync); - assert_impl!(CopyBuf<(), PhantomPinned>: Unpin); - assert_not_impl!(CopyBuf: Unpin); + assert_impl!(Copy<'_, (), ()>: Send); + assert_not_impl!(Copy<'_, (), *const ()>: Send); + assert_not_impl!(Copy<'_, *const (), ()>: Send); + assert_impl!(Copy<'_, (), ()>: Sync); + assert_not_impl!(Copy<'_, (), *const ()>: Sync); + assert_not_impl!(Copy<'_, *const (), ()>: Sync); + assert_impl!(Copy<'_, (), PhantomPinned>: Unpin); + assert_not_impl!(Copy<'_, PhantomPinned, ()>: Unpin); + + assert_impl!(CopyBuf<'_, (), ()>: Send); + assert_not_impl!(CopyBuf<'_, (), *const ()>: Send); + assert_not_impl!(CopyBuf<'_, *const (), ()>: Send); + assert_impl!(CopyBuf<'_, (), ()>: Sync); + assert_not_impl!(CopyBuf<'_, (), *const ()>: Sync); + assert_not_impl!(CopyBuf<'_, *const (), ()>: Sync); + assert_impl!(CopyBuf<'_, (), PhantomPinned>: Unpin); + assert_not_impl!(CopyBuf<'_, PhantomPinned, ()>: Unpin); assert_impl!(Cursor<()>: Send); assert_not_impl!(Cursor<*const ()>: Send); @@ -810,6 +824,12 @@ pub mod io { assert_impl!(Seek<'_, ()>: Unpin); assert_not_impl!(Seek<'_, PhantomPinned>: Unpin); + assert_impl!(SeeKRelative<'_, ()>: Send); + assert_not_impl!(SeeKRelative<'_, *const ()>: Send); + assert_impl!(SeeKRelative<'_, ()>: Sync); + assert_not_impl!(SeeKRelative<'_, *const ()>: Sync); + assert_impl!(SeeKRelative<'_, PhantomPinned>: Unpin); + assert_impl!(Sink: Send); assert_impl!(Sink: Sync); assert_impl!(Sink: Unpin); @@ -871,7 +891,7 @@ pub mod io { } /// Assert Send/Sync/Unpin for all public types in `futures::lock`. -pub mod lock { +mod lock { use super::*; use futures::lock::*; @@ -947,7 +967,7 @@ pub mod lock { } /// Assert Send/Sync/Unpin for all public types in `futures::sink`. -pub mod sink { +mod sink { use super::*; use futures::sink::{self, *}; use std::marker::Send; @@ -1076,7 +1096,7 @@ pub mod sink { } /// Assert Send/Sync/Unpin for all public types in `futures::stream`. -pub mod stream { +mod stream { use super::*; use futures::{io, stream::*}; @@ -1105,10 +1125,9 @@ pub mod stream { assert_not_impl!(Buffered>: Send); assert_not_impl!(Buffered>: Send); assert_not_impl!(Buffered>>: Send); - assert_impl!(Buffered>>: Sync); - assert_not_impl!(Buffered>: Sync); - assert_not_impl!(Buffered>: Sync); - assert_not_impl!(Buffered>>: Sync); + assert_impl!(Buffered>>: Sync); + assert_not_impl!(Buffered>>: Sync); + assert_not_impl!(Buffered>>: Sync); assert_impl!(Buffered>: Unpin); assert_not_impl!(Buffered>: Unpin); @@ -1290,9 +1309,10 @@ pub mod stream { assert_impl!(FuturesOrdered>: Send); assert_not_impl!(FuturesOrdered: Send); assert_not_impl!(FuturesOrdered: Send); - assert_impl!(FuturesOrdered>: Sync); - assert_not_impl!(FuturesOrdered>: Sync); - assert_not_impl!(FuturesOrdered>: Sync); + assert_impl!(FuturesOrdered>: Sync); + assert_not_impl!(FuturesOrdered>: Sync); + assert_not_impl!(FuturesOrdered>: Sync); + assert_not_impl!(FuturesOrdered: Sync); assert_impl!(FuturesOrdered: Unpin); assert_impl!(FuturesUnordered<()>: Send); @@ -1383,6 +1403,26 @@ pub mod stream { assert_impl!(Next<'_, ()>: Unpin); assert_not_impl!(Next<'_, PhantomPinned>: Unpin); + assert_impl!(NextIf<'_, SendStream<()>, ()>: Send); + assert_not_impl!(NextIf<'_, SendStream<()>, *const ()>: Send); + assert_not_impl!(NextIf<'_, SendStream, ()>: Send); + assert_not_impl!(NextIf<'_, LocalStream<()>, ()>: Send); + assert_impl!(NextIf<'_, SyncStream<()>, ()>: Sync); + assert_not_impl!(NextIf<'_, SyncStream<()>, *const ()>: Sync); + assert_not_impl!(NextIf<'_, SyncStream, ()>: Sync); + assert_not_impl!(NextIf<'_, LocalStream<()>, ()>: Send); + assert_impl!(NextIf<'_, PinnedStream, PhantomPinned>: Unpin); + + assert_impl!(NextIfEq<'_, SendStream<()>, ()>: Send); + assert_not_impl!(NextIfEq<'_, SendStream<()>, *const ()>: Send); + assert_not_impl!(NextIfEq<'_, SendStream, ()>: Send); + assert_not_impl!(NextIfEq<'_, LocalStream<()>, ()>: Send); + assert_impl!(NextIfEq<'_, SyncStream<()>, ()>: Sync); + assert_not_impl!(NextIfEq<'_, SyncStream<()>, *const ()>: Sync); + assert_not_impl!(NextIfEq<'_, SyncStream, ()>: Sync); + assert_not_impl!(NextIfEq<'_, LocalStream<()>, ()>: Send); + assert_impl!(NextIfEq<'_, PinnedStream, PhantomPinned>: Unpin); + assert_impl!(Once<()>: Send); assert_not_impl!(Once<*const ()>: Send); assert_impl!(Once<()>: Sync); @@ -1410,6 +1450,14 @@ pub mod stream { assert_not_impl!(Peek<'_, LocalStream<()>>: Sync); assert_impl!(Peek<'_, PinnedStream>: Unpin); + assert_impl!(PeekMut<'_, SendStream<()>>: Send); + assert_not_impl!(PeekMut<'_, SendStream>: Send); + assert_not_impl!(PeekMut<'_, LocalStream<()>>: Send); + assert_impl!(PeekMut<'_, SyncStream<()>>: Sync); + assert_not_impl!(PeekMut<'_, SyncStream>: Sync); + assert_not_impl!(PeekMut<'_, LocalStream<()>>: Sync); + assert_impl!(PeekMut<'_, PinnedStream>: Unpin); + assert_impl!(Peekable>: Send); assert_not_impl!(Peekable: Send); assert_not_impl!(Peekable: Send); @@ -1431,11 +1479,18 @@ pub mod stream { assert_not_impl!(PollFn<*const ()>: Sync); assert_impl!(PollFn: Unpin); + assert_impl!(PollImmediate: Send); + assert_not_impl!(PollImmediate>: Send); + assert_impl!(PollImmediate: Sync); + assert_not_impl!(PollImmediate>: Sync); + assert_impl!(PollImmediate: Unpin); + assert_not_impl!(PollImmediate: Unpin); + assert_impl!(ReadyChunks>: Send); - assert_not_impl!(ReadyChunks: Send); + assert_impl!(ReadyChunks: Send); assert_not_impl!(ReadyChunks: Send); assert_impl!(ReadyChunks>: Sync); - assert_not_impl!(ReadyChunks: Sync); + assert_impl!(ReadyChunks: Sync); assert_not_impl!(ReadyChunks: Sync); assert_impl!(ReadyChunks: Unpin); assert_not_impl!(ReadyChunks: Unpin); @@ -1599,11 +1654,12 @@ pub mod stream { assert_not_impl!(TryBuffered>>: Send); assert_not_impl!(TryBuffered>>: Send); assert_not_impl!(TryBuffered>>: Send); - assert_impl!(TryBuffered>>: Sync); - assert_not_impl!(TryBuffered>>: Sync); - assert_not_impl!(TryBuffered>>: Sync); - assert_not_impl!(TryBuffered>>: Sync); - assert_not_impl!(TryBuffered>>: Sync); + assert_impl!(TryBuffered>>: Sync); + assert_not_impl!(TryBuffered>>: Sync); + assert_not_impl!(TryBuffered>>: Sync); + assert_not_impl!(TryBuffered>>: Sync); + assert_not_impl!(TryBuffered>>: Sync); + assert_not_impl!(TryBuffered>>: Sync); assert_impl!(TryBuffered>: Unpin); assert_not_impl!(TryBuffered>: Unpin); @@ -1780,29 +1836,44 @@ pub mod stream { assert_not_impl!(Zip: Unpin); assert_not_impl!(Zip: Unpin); - assert_not_impl!(futures_unordered::Iter<()>: Send); - assert_not_impl!(futures_unordered::Iter<()>: Sync); - assert_impl!(futures_unordered::Iter<()>: Unpin); - // futures_unordered::Iter requires `Fut: Unpin` - // assert_not_impl!(futures_unordered::Iter: Unpin); - - assert_not_impl!(futures_unordered::IterMut<()>: Send); - assert_not_impl!(futures_unordered::IterMut<()>: Sync); - assert_impl!(futures_unordered::IterMut<()>: Unpin); - // futures_unordered::IterMut requires `Fut: Unpin` - // assert_not_impl!(futures_unordered::IterMut: Unpin); - - assert_not_impl!(futures_unordered::IterPinMut<()>: Send); - assert_not_impl!(futures_unordered::IterPinMut<()>: Sync); - assert_impl!(futures_unordered::IterPinMut: Unpin); - - assert_not_impl!(futures_unordered::IterPinRef<()>: Send); - assert_not_impl!(futures_unordered::IterPinRef<()>: Sync); - assert_impl!(futures_unordered::IterPinRef: Unpin); + assert_impl!(futures_unordered::Iter<'_, ()>: Send); + assert_not_impl!(futures_unordered::Iter<'_, *const ()>: Send); + assert_impl!(futures_unordered::Iter<'_, ()>: Sync); + assert_not_impl!(futures_unordered::Iter<'_, *const ()>: Sync); + assert_impl!(futures_unordered::Iter<'_, ()>: Unpin); + // The definition of futures_unordered::Iter has `Fut: Unpin` bounds. + // assert_not_impl!(futures_unordered::Iter<'_, PhantomPinned>: Unpin); + + assert_impl!(futures_unordered::IterMut<'_, ()>: Send); + assert_not_impl!(futures_unordered::IterMut<'_, *const ()>: Send); + assert_impl!(futures_unordered::IterMut<'_, ()>: Sync); + assert_not_impl!(futures_unordered::IterMut<'_, *const ()>: Sync); + assert_impl!(futures_unordered::IterMut<'_, ()>: Unpin); + // The definition of futures_unordered::IterMut has `Fut: Unpin` bounds. + // assert_not_impl!(futures_unordered::IterMut<'_, PhantomPinned>: Unpin); + + assert_impl!(futures_unordered::IterPinMut<'_, ()>: Send); + assert_not_impl!(futures_unordered::IterPinMut<'_, *const ()>: Send); + assert_impl!(futures_unordered::IterPinMut<'_, ()>: Sync); + assert_not_impl!(futures_unordered::IterPinMut<'_, *const ()>: Sync); + assert_impl!(futures_unordered::IterPinMut<'_, PhantomPinned>: Unpin); + + assert_impl!(futures_unordered::IterPinRef<'_, ()>: Send); + assert_not_impl!(futures_unordered::IterPinRef<'_, *const ()>: Send); + assert_impl!(futures_unordered::IterPinRef<'_, ()>: Sync); + assert_not_impl!(futures_unordered::IterPinRef<'_, *const ()>: Sync); + assert_impl!(futures_unordered::IterPinRef<'_, PhantomPinned>: Unpin); + + assert_impl!(futures_unordered::IntoIter<()>: Send); + assert_not_impl!(futures_unordered::IntoIter<*const ()>: Send); + assert_impl!(futures_unordered::IntoIter<()>: Sync); + assert_not_impl!(futures_unordered::IntoIter<*const ()>: Sync); + // The definition of futures_unordered::IntoIter has `Fut: Unpin` bounds. + // assert_not_impl!(futures_unordered::IntoIter: Unpin); } /// Assert Send/Sync/Unpin for all public types in `futures::task`. -pub mod task { +mod task { use super::*; use futures::task::*; @@ -1810,13 +1881,13 @@ pub mod task { assert_impl!(AtomicWaker: Sync); assert_impl!(AtomicWaker: Unpin); - assert_impl!(FutureObj<*const ()>: Send); - assert_not_impl!(FutureObj<()>: Sync); - assert_impl!(FutureObj: Unpin); + assert_impl!(FutureObj<'_, *const ()>: Send); + assert_not_impl!(FutureObj<'_, ()>: Sync); + assert_impl!(FutureObj<'_, PhantomPinned>: Unpin); - assert_not_impl!(LocalFutureObj<()>: Send); - assert_not_impl!(LocalFutureObj<()>: Sync); - assert_impl!(LocalFutureObj: Unpin); + assert_not_impl!(LocalFutureObj<'_, ()>: Send); + assert_not_impl!(LocalFutureObj<'_, ()>: Sync); + assert_impl!(LocalFutureObj<'_, PhantomPinned>: Unpin); assert_impl!(SpawnError: Send); assert_impl!(SpawnError: Sync); diff --git a/futures/tests_disabled/bilock.rs b/futures/tests/bilock.rs similarity index 50% rename from futures/tests_disabled/bilock.rs rename to futures/tests/bilock.rs index c1bc33f507..b103487849 100644 --- a/futures/tests_disabled/bilock.rs +++ b/futures/tests/bilock.rs @@ -1,34 +1,38 @@ -use futures::task; -use futures::stream; +#![cfg(feature = "bilock")] + +use futures::executor::block_on; use futures::future; +use futures::stream; +use futures::task::{Context, Poll}; +use futures::Future; +use futures::StreamExt; +use futures_test::task::noop_context; use futures_util::lock::BiLock; +use std::pin::Pin; use std::thread; -mod support; -use support::*; - #[test] fn smoke() { - let future = future::lazy(|_| { + let future = future::lazy(|cx| { let (a, b) = BiLock::new(1); { - let mut lock = match a.poll_lock() { + let mut lock = match a.poll_lock(cx) { Poll::Ready(l) => l, Poll::Pending => panic!("poll not ready"), }; assert_eq!(*lock, 1); *lock = 2; - assert!(b.poll_lock().is_pending()); - assert!(a.poll_lock().is_pending()); + assert!(b.poll_lock(cx).is_pending()); + assert!(a.poll_lock(cx).is_pending()); } - assert!(b.poll_lock().is_ready()); - assert!(a.poll_lock().is_ready()); + assert!(b.poll_lock(cx).is_ready()); + assert!(a.poll_lock(cx).is_ready()); { - let lock = match b.poll_lock() { + let lock = match b.poll_lock(cx) { Poll::Ready(l) => l, Poll::Pending => panic!("poll not ready"), }; @@ -40,37 +44,32 @@ fn smoke() { Ok::<(), ()>(()) }); - assert!(task::spawn(future) - .poll_future_notify(¬ify_noop(), 0) - .expect("failure in poll") - .is_ready()); + assert_eq!(block_on(future), Ok(())); } #[test] fn concurrent() { const N: usize = 10000; + let mut cx = noop_context(); let (a, b) = BiLock::new(0); - let a = Increment { - a: Some(a), - remaining: N, - }; - let b = stream::iter_ok(0..N).fold(b, |b, _n| { - b.lock().map(|mut b| { - *b += 1; - b.unlock() - }) + let a = Increment { a: Some(a), remaining: N }; + let b = stream::iter(0..N).fold(b, |b, _n| async { + let mut g = b.lock().await; + *g += 1; + drop(g); + b }); - let t1 = thread::spawn(move || a.wait()); - let b = b.wait().expect("b error"); - let a = t1.join().unwrap().expect("a error"); + let t1 = thread::spawn(move || block_on(a)); + let b = block_on(b); + let a = t1.join().unwrap(); - match a.poll_lock() { + match a.poll_lock(&mut cx) { Poll::Ready(l) => assert_eq!(*l, 2 * N), Poll::Pending => panic!("poll not ready"), } - match b.poll_lock() { + match b.poll_lock(&mut cx) { Poll::Ready(l) => assert_eq!(*l, 2 * N), Poll::Pending => panic!("poll not ready"), } @@ -83,22 +82,22 @@ fn concurrent() { } impl Future for Increment { - type Item = BiLock; - type Error = (); + type Output = BiLock; - fn poll(&mut self) -> Poll, ()> { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { loop { if self.remaining == 0 { - return Ok(self.a.take().unwrap().into()) + return self.a.take().unwrap().into(); } - let a = self.a.as_ref().unwrap(); - let mut a = match a.poll_lock() { + let a = self.a.as_mut().unwrap(); + let mut a = match a.poll_lock(cx) { Poll::Ready(l) => l, - Poll::Pending => return Ok(Poll::Pending), + Poll::Pending => return Poll::Pending, }; - self.remaining -= 1; *a += 1; + drop(a); + self.remaining -= 1; } } } diff --git a/futures/tests/compat.rs b/futures/tests/compat.rs index 39adc7cba4..ac04a95ea8 100644 --- a/futures/tests/compat.rs +++ b/futures/tests/compat.rs @@ -1,16 +1,15 @@ #![cfg(feature = "compat")] +#![cfg(not(miri))] // Miri does not support epoll -use tokio::timer::Delay; -use tokio::runtime::Runtime; -use std::time::Instant; -use futures::prelude::*; use futures::compat::Future01CompatExt; +use futures::prelude::*; +use std::time::Instant; +use tokio::runtime::Runtime; +use tokio::timer::Delay; #[test] fn can_use_01_futures_in_a_03_future_running_on_a_01_executor() { - let f = async { - Delay::new(Instant::now()).compat().await - }; + let f = async { Delay::new(Instant::now()).compat().await }; let mut runtime = Runtime::new().unwrap(); runtime.block_on(f.boxed().compat()).unwrap(); diff --git a/futures/tests/eager_drop.rs b/futures/tests/eager_drop.rs index 11edb1b6de..992507774c 100644 --- a/futures/tests/eager_drop.rs +++ b/futures/tests/eager_drop.rs @@ -1,16 +1,23 @@ +use futures::channel::oneshot; +use futures::future::{self, Future, FutureExt, TryFutureExt}; +use futures::task::{Context, Poll}; +use futures_test::future::FutureTestExt; +use pin_project::pin_project; +use std::pin::Pin; +use std::sync::mpsc; + #[test] fn map_ok() { - use futures::future::{self, FutureExt, TryFutureExt}; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - // The closure given to `map_ok` should have been dropped by the time `map` // runs. let (tx1, rx1) = mpsc::channel::<()>(); let (tx2, rx2) = mpsc::channel::<()>(); future::ready::>(Err(1)) - .map_ok(move |_| { let _tx1 = tx1; panic!("should not run"); }) + .map_ok(move |_| { + let _tx1 = tx1; + panic!("should not run"); + }) .map(move |_| { assert!(rx1.recv().is_err()); tx2.send(()).unwrap() @@ -22,17 +29,16 @@ fn map_ok() { #[test] fn map_err() { - use futures::future::{self, FutureExt, TryFutureExt}; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - // The closure given to `map_err` should have been dropped by the time `map` // runs. let (tx1, rx1) = mpsc::channel::<()>(); let (tx2, rx2) = mpsc::channel::<()>(); future::ready::>(Ok(1)) - .map_err(move |_| { let _tx1 = tx1; panic!("should not run"); }) + .map_err(move |_| { + let _tx1 = tx1; + panic!("should not run"); + }) .map(move |_| { assert!(rx1.recv().is_err()); tx2.send(()).unwrap() @@ -42,96 +48,74 @@ fn map_err() { rx2.recv().unwrap(); } -mod channelled { - use futures::future::Future; - use futures::task::{Context,Poll}; - use pin_project::pin_project; - use std::pin::Pin; - - #[pin_project] - struct FutureData { - _data: T, - #[pin] - future: F, - } +#[pin_project] +struct FutureData { + _data: T, + #[pin] + future: F, +} - impl Future for FutureData { - type Output = F::Output; +impl Future for FutureData { + type Output = F::Output; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().future.poll(cx) - } + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + self.project().future.poll(cx) } +} - #[test] - fn then_drops_eagerly() { - use futures::channel::oneshot; - use futures::future::{self, FutureExt, TryFutureExt}; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - - let (tx0, rx0) = oneshot::channel::<()>(); - let (tx1, rx1) = mpsc::channel::<()>(); - let (tx2, rx2) = mpsc::channel::<()>(); - - FutureData { _data: tx1, future: rx0.unwrap_or_else(|_| { panic!() }) } - .then(move |_| { - assert!(rx1.recv().is_err()); // tx1 should have been dropped - tx2.send(()).unwrap(); - future::ready(()) - }) - .run_in_background(); - - assert_eq!(Err(mpsc::TryRecvError::Empty), rx2.try_recv()); - tx0.send(()).unwrap(); - rx2.recv().unwrap(); - } +#[test] +fn then_drops_eagerly() { + let (tx0, rx0) = oneshot::channel::<()>(); + let (tx1, rx1) = mpsc::channel::<()>(); + let (tx2, rx2) = mpsc::channel::<()>(); - #[test] - fn and_then_drops_eagerly() { - use futures::channel::oneshot; - use futures::future::{self, TryFutureExt}; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - - let (tx0, rx0) = oneshot::channel::>(); - let (tx1, rx1) = mpsc::channel::<()>(); - let (tx2, rx2) = mpsc::channel::<()>(); - - FutureData { _data: tx1, future: rx0.unwrap_or_else(|_| { panic!() }) } - .and_then(move |_| { - assert!(rx1.recv().is_err()); // tx1 should have been dropped - tx2.send(()).unwrap(); - future::ready(Ok(())) - }) - .run_in_background(); - - assert_eq!(Err(mpsc::TryRecvError::Empty), rx2.try_recv()); - tx0.send(Ok(())).unwrap(); - rx2.recv().unwrap(); - } + FutureData { _data: tx1, future: rx0.unwrap_or_else(|_| panic!()) } + .then(move |_| { + assert!(rx1.recv().is_err()); // tx1 should have been dropped + tx2.send(()).unwrap(); + future::ready(()) + }) + .run_in_background(); - #[test] - fn or_else_drops_eagerly() { - use futures::channel::oneshot; - use futures::future::{self, TryFutureExt}; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - - let (tx0, rx0) = oneshot::channel::>(); - let (tx1, rx1) = mpsc::channel::<()>(); - let (tx2, rx2) = mpsc::channel::<()>(); - - FutureData { _data: tx1, future: rx0.unwrap_or_else(|_| { panic!() }) } - .or_else(move |_| { - assert!(rx1.recv().is_err()); // tx1 should have been dropped - tx2.send(()).unwrap(); - future::ready::>(Ok(())) - }) - .run_in_background(); - - assert_eq!(Err(mpsc::TryRecvError::Empty), rx2.try_recv()); - tx0.send(Err(())).unwrap(); - rx2.recv().unwrap(); - } + assert_eq!(Err(mpsc::TryRecvError::Empty), rx2.try_recv()); + tx0.send(()).unwrap(); + rx2.recv().unwrap(); +} + +#[test] +fn and_then_drops_eagerly() { + let (tx0, rx0) = oneshot::channel::>(); + let (tx1, rx1) = mpsc::channel::<()>(); + let (tx2, rx2) = mpsc::channel::<()>(); + + FutureData { _data: tx1, future: rx0.unwrap_or_else(|_| panic!()) } + .and_then(move |_| { + assert!(rx1.recv().is_err()); // tx1 should have been dropped + tx2.send(()).unwrap(); + future::ready(Ok(())) + }) + .run_in_background(); + + assert_eq!(Err(mpsc::TryRecvError::Empty), rx2.try_recv()); + tx0.send(Ok(())).unwrap(); + rx2.recv().unwrap(); +} + +#[test] +fn or_else_drops_eagerly() { + let (tx0, rx0) = oneshot::channel::>(); + let (tx1, rx1) = mpsc::channel::<()>(); + let (tx2, rx2) = mpsc::channel::<()>(); + + FutureData { _data: tx1, future: rx0.unwrap_or_else(|_| panic!()) } + .or_else(move |_| { + assert!(rx1.recv().is_err()); // tx1 should have been dropped + tx2.send(()).unwrap(); + future::ready::>(Ok(())) + }) + .run_in_background(); + + assert_eq!(Err(mpsc::TryRecvError::Empty), rx2.try_recv()); + tx0.send(Err(())).unwrap(); + rx2.recv().unwrap(); } diff --git a/futures/tests/eventual.rs b/futures/tests/eventual.rs index bff000dd09..57a49b2417 100644 --- a/futures/tests/eventual.rs +++ b/futures/tests/eventual.rs @@ -16,6 +16,8 @@ fn join1() { run(future::try_join(ok::(1), ok(2)).map_ok(move |v| tx.send(v).unwrap())); assert_eq!(rx.recv(), Ok((1, 2))); assert!(rx.recv().is_err()); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] @@ -30,6 +32,8 @@ fn join2() { c2.send(2).unwrap(); assert_eq!(rx.recv(), Ok((1, 2))); assert!(rx.recv().is_err()); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] @@ -43,6 +47,8 @@ fn join3() { assert_eq!(rx.recv(), Ok(1)); assert!(rx.recv().is_err()); drop(c2); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] @@ -56,6 +62,8 @@ fn join4() { assert!(rx.recv().is_ok()); drop(c2); assert!(rx.recv().is_err()); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] @@ -73,6 +81,8 @@ fn join5() { c3.send(3).unwrap(); assert_eq!(rx.recv(), Ok(((1, 2), 3))); assert!(rx.recv().is_err()); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] @@ -92,6 +102,8 @@ fn select1() { c2.send(2).unwrap(); assert_eq!(rx.recv(), Ok(2)); assert!(rx.recv().is_err()); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] @@ -111,6 +123,8 @@ fn select2() { c2.send(2).unwrap(); assert_eq!(rx.recv(), Ok(2)); assert!(rx.recv().is_err()); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] @@ -130,10 +144,14 @@ fn select3() { drop(c2); assert_eq!(rx.recv(), Ok(2)); assert!(rx.recv().is_err()); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } #[test] fn select4() { + const N: usize = if cfg!(miri) { 100 } else { 10000 }; + let (tx, rx) = mpsc::channel::>(); let t = thread::spawn(move || { @@ -143,7 +161,7 @@ fn select4() { }); let (tx2, rx2) = mpsc::channel(); - for _ in 0..10000 { + for _ in 0..N { let (c1, p1) = oneshot::channel::(); let (c2, p2) = oneshot::channel::(); @@ -156,4 +174,6 @@ fn select4() { drop(tx); t.join().unwrap(); + + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 } diff --git a/futures/tests/abortable.rs b/futures/tests/future_abortable.rs similarity index 69% rename from futures/tests/abortable.rs rename to futures/tests/future_abortable.rs index 6b5a25c365..e119f0b719 100644 --- a/futures/tests/abortable.rs +++ b/futures/tests/future_abortable.rs @@ -1,45 +1,44 @@ +use futures::channel::oneshot; +use futures::executor::block_on; +use futures::future::{abortable, Aborted, FutureExt}; +use futures::task::{Context, Poll}; +use futures_test::task::new_count_waker; + #[test] fn abortable_works() { - use futures::channel::oneshot; - use futures::future::{abortable, Aborted}; - use futures::executor::block_on; - let (_tx, a_rx) = oneshot::channel::<()>(); let (abortable_rx, abort_handle) = abortable(a_rx); abort_handle.abort(); + assert!(abortable_rx.is_aborted()); assert_eq!(Err(Aborted), block_on(abortable_rx)); } #[test] fn abortable_awakens() { - use futures::channel::oneshot; - use futures::future::{abortable, Aborted, FutureExt}; - use futures::task::{Context, Poll}; - use futures_test::task::new_count_waker; - let (_tx, a_rx) = oneshot::channel::<()>(); let (mut abortable_rx, abort_handle) = abortable(a_rx); let (waker, counter) = new_count_waker(); let mut cx = Context::from_waker(&waker); + assert_eq!(counter, 0); assert_eq!(Poll::Pending, abortable_rx.poll_unpin(&mut cx)); assert_eq!(counter, 0); + abort_handle.abort(); assert_eq!(counter, 1); + assert!(abortable_rx.is_aborted()); assert_eq!(Poll::Ready(Err(Aborted)), abortable_rx.poll_unpin(&mut cx)); } #[test] fn abortable_resolves() { - use futures::channel::oneshot; - use futures::future::abortable; - use futures::executor::block_on; let (tx, a_rx) = oneshot::channel::<()>(); let (abortable_rx, _abort_handle) = abortable(a_rx); tx.send(()).unwrap(); + assert!(!abortable_rx.is_aborted()); assert_eq!(Ok(Ok(())), block_on(abortable_rx)); } diff --git a/futures/tests/basic_combinators.rs b/futures/tests/future_basic_combinators.rs similarity index 92% rename from futures/tests/basic_combinators.rs rename to futures/tests/future_basic_combinators.rs index fa65b6f5f1..372ab48b79 100644 --- a/futures/tests/basic_combinators.rs +++ b/futures/tests/future_basic_combinators.rs @@ -13,17 +13,21 @@ fn basic_future_combinators() { tx1.send(x).unwrap(); // Send 1 tx1.send(2).unwrap(); // Send 2 future::ready(3) - }).map(move |x| { + }) + .map(move |x| { tx2.send(x).unwrap(); // Send 3 tx2.send(4).unwrap(); // Send 4 5 - }).map(move |x| { + }) + .map(move |x| { tx3.send(x).unwrap(); // Send 5 }); assert!(rx.try_recv().is_err()); // Not started yet fut.run_in_background(); // Start it - for i in 1..=5 { assert_eq!(rx.recv(), Ok(i)); } // Check it + for i in 1..=5 { + assert_eq!(rx.recv(), Ok(i)); + } // Check it assert!(rx.recv().is_err()); // Should be done } @@ -93,6 +97,8 @@ fn basic_try_future_combinators() { assert!(rx.try_recv().is_err()); // Not started yet fut.run_in_background(); // Start it - for i in 1..=12 { assert_eq!(rx.recv(), Ok(i)); } // Check it + for i in 1..=12 { + assert_eq!(rx.recv(), Ok(i)); + } // Check it assert!(rx.recv().is_err()); // Should be done } diff --git a/futures/tests/fuse.rs b/futures/tests/future_fuse.rs similarity index 100% rename from futures/tests/fuse.rs rename to futures/tests/future_fuse.rs diff --git a/futures/tests/future_inspect.rs b/futures/tests/future_inspect.rs new file mode 100644 index 0000000000..eacd1f78a2 --- /dev/null +++ b/futures/tests/future_inspect.rs @@ -0,0 +1,16 @@ +use futures::executor::block_on; +use futures::future::{self, FutureExt}; + +#[test] +fn smoke() { + let mut counter = 0; + + { + let work = future::ready::(40).inspect(|val| { + counter += *val; + }); + assert_eq!(block_on(work), 40); + } + + assert_eq!(counter, 40); +} diff --git a/futures/tests/future_join.rs b/futures/tests/future_join.rs new file mode 100644 index 0000000000..0556a6e62c --- /dev/null +++ b/futures/tests/future_join.rs @@ -0,0 +1,32 @@ +use futures::executor::block_on; +use futures::future::{self, Future}; +use std::task::Poll; + +/// This tests verifies (through miri) that self-referencing +/// futures are not invalidated when joining them. +#[test] +fn futures_join_macro_self_referential() { + block_on(async { futures::join!(yield_now(), trouble()) }); +} + +async fn trouble() { + let lucky_number = 42; + let problematic_variable = &lucky_number; + + yield_now().await; + + // problematic dereference + let _ = { *problematic_variable }; +} + +fn yield_now() -> impl Future { + let mut yielded = false; + future::poll_fn(move |cx| { + if core::mem::replace(&mut yielded, true) { + Poll::Ready(()) + } else { + cx.waker().wake_by_ref(); + Poll::Pending + } + }) +} diff --git a/futures/tests/future_join_all.rs b/futures/tests/future_join_all.rs new file mode 100644 index 0000000000..44486e1ca3 --- /dev/null +++ b/futures/tests/future_join_all.rs @@ -0,0 +1,41 @@ +use futures::executor::block_on; +use futures::future::{join_all, ready, Future, JoinAll}; +use futures::pin_mut; +use std::fmt::Debug; + +#[track_caller] +fn assert_done(actual_fut: impl Future, expected: T) +where + T: PartialEq + Debug, +{ + pin_mut!(actual_fut); + let output = block_on(actual_fut); + assert_eq!(output, expected); +} + +#[test] +fn collect_collects() { + assert_done(join_all(vec![ready(1), ready(2)]), vec![1, 2]); + assert_done(join_all(vec![ready(1)]), vec![1]); + // REVIEW: should this be implemented? + // assert_done(join_all(Vec::::new()), vec![]); + + // TODO: needs more tests +} + +#[test] +fn join_all_iter_lifetime() { + // In futures-rs version 0.1, this function would fail to typecheck due to an overly + // conservative type parameterization of `JoinAll`. + fn sizes(bufs: Vec<&[u8]>) -> impl Future> { + let iter = bufs.into_iter().map(|b| ready::(b.len())); + join_all(iter) + } + + assert_done(sizes(vec![&[1, 2, 3], &[], &[0]]), vec![3_usize, 0, 1]); +} + +#[test] +fn join_all_from_iter() { + assert_done(vec![ready(1), ready(2)].into_iter().collect::>(), vec![1, 2]) +} diff --git a/futures/tests/future_obj.rs b/futures/tests/future_obj.rs index c6b18fc85c..0e5253464e 100644 --- a/futures/tests/future_obj.rs +++ b/futures/tests/future_obj.rs @@ -1,6 +1,6 @@ -use futures::future::{Future, FutureObj, FutureExt}; -use std::pin::Pin; +use futures::future::{Future, FutureExt, FutureObj}; use futures::task::{Context, Poll}; +use std::pin::Pin; #[test] fn dropping_does_not_segfault() { diff --git a/futures/tests/select_all.rs b/futures/tests/future_select_all.rs similarity index 69% rename from futures/tests/select_all.rs rename to futures/tests/future_select_all.rs index 540db2c410..299b479044 100644 --- a/futures/tests/select_all.rs +++ b/futures/tests/future_select_all.rs @@ -1,14 +1,10 @@ +use futures::executor::block_on; +use futures::future::{ready, select_all}; +use std::collections::HashSet; + #[test] fn smoke() { - use futures::executor::block_on; - use futures::future::{ready, select_all}; - use std::collections::HashSet; - - let v = vec![ - ready(1), - ready(2), - ready(3), - ]; + let v = vec![ready(1), ready(2), ready(3)]; let mut c = vec![1, 2, 3].into_iter().collect::>(); diff --git a/futures/tests/select_ok.rs b/futures/tests/future_select_ok.rs similarity index 57% rename from futures/tests/select_ok.rs rename to futures/tests/future_select_ok.rs index 81cadb7bae..8aec00362d 100644 --- a/futures/tests/select_ok.rs +++ b/futures/tests/future_select_ok.rs @@ -1,14 +1,9 @@ +use futures::executor::block_on; +use futures::future::{err, ok, select_ok}; + #[test] fn ignore_err() { - use futures::executor::block_on; - use futures::future::{err, ok, select_ok}; - - let v = vec![ - err(1), - err(2), - ok(3), - ok(4), - ]; + let v = vec![err(1), err(2), ok(3), ok(4)]; let (i, v) = block_on(select_ok(v)).ok().unwrap(); assert_eq!(i, 3); @@ -23,14 +18,7 @@ fn ignore_err() { #[test] fn last_err() { - use futures::executor::block_on; - use futures::future::{err, ok, select_ok}; - - let v = vec![ - ok(1), - err(2), - err(3), - ]; + let v = vec![ok(1), err(2), err(3)]; let (i, v) = block_on(select_ok(v)).ok().unwrap(); assert_eq!(i, 1); diff --git a/futures/tests/shared.rs b/futures/tests/future_shared.rs similarity index 62% rename from futures/tests/shared.rs rename to futures/tests/future_shared.rs index cc0c6a20cf..bd69c1d7c1 100644 --- a/futures/tests/shared.rs +++ b/futures/tests/future_shared.rs @@ -1,22 +1,23 @@ -mod count_clone { - use std::cell::Cell; - use std::rc::Rc; - - pub struct CountClone(pub Rc>); - - impl Clone for CountClone { - fn clone(&self) -> Self { - self.0.set(self.0.get() + 1); - Self(self.0.clone()) - } +use futures::channel::oneshot; +use futures::executor::{block_on, LocalPool}; +use futures::future::{self, FutureExt, LocalFutureObj, TryFutureExt}; +use futures::task::LocalSpawn; +use std::cell::{Cell, RefCell}; +use std::panic::AssertUnwindSafe; +use std::rc::Rc; +use std::task::Poll; +use std::thread; + +struct CountClone(Rc>); + +impl Clone for CountClone { + fn clone(&self) -> Self { + self.0.set(self.0.get() + 1); + Self(self.0.clone()) } } fn send_shared_oneshot_and_wait_on_multiple_threads(threads_number: u32) { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::FutureExt; - use std::thread; let (tx, rx) = oneshot::channel::(); let f = rx.shared(); let join_handles = (0..threads_number) @@ -53,11 +54,6 @@ fn many_threads() { #[test] fn drop_on_one_task_ok() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::{self, FutureExt, TryFutureExt}; - use std::thread; - let (tx, rx) = oneshot::channel::(); let f1 = rx.shared(); let f2 = f1.clone(); @@ -86,11 +82,6 @@ fn drop_on_one_task_ok() { #[test] fn drop_in_poll() { - use futures::executor::block_on; - use futures::future::{self, FutureExt, LocalFutureObj}; - use std::cell::RefCell; - use std::rc::Rc; - let slot1 = Rc::new(RefCell::new(None)); let slot2 = slot1.clone(); @@ -108,11 +99,6 @@ fn drop_in_poll() { #[test] fn peek() { - use futures::channel::oneshot; - use futures::executor::LocalPool; - use futures::future::{FutureExt, LocalFutureObj}; - use futures::task::LocalSpawn; - let mut local_pool = LocalPool::new(); let spawn = &mut local_pool.spawner(); @@ -134,9 +120,7 @@ fn peek() { } // Once the Shared has been polled, the value is peekable on the clone. - spawn - .spawn_local_obj(LocalFutureObj::new(Box::new(f1.map(|_| ())))) - .unwrap(); + spawn.spawn_local_obj(LocalFutureObj::new(Box::new(f1.map(|_| ())))).unwrap(); local_pool.run(); for _ in 0..2 { assert_eq!(*f2.peek().unwrap(), Ok(42)); @@ -145,10 +129,6 @@ fn peek() { #[test] fn downgrade() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::FutureExt; - let (tx, rx) = oneshot::channel::(); let shared = rx.shared(); // Since there are outstanding `Shared`s, we can get a `WeakShared`. @@ -172,15 +152,53 @@ fn downgrade() { } #[test] -fn dont_clone_in_single_owner_shared_future() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::FutureExt; - use std::cell::Cell; - use std::rc::Rc; +fn ptr_eq() { + use future::FusedFuture; + use std::collections::hash_map::DefaultHasher; + use std::hash::Hasher; + + let (tx, rx) = oneshot::channel::(); + let shared = rx.shared(); + let mut shared2 = shared.clone(); + let mut hasher = DefaultHasher::new(); + let mut hasher2 = DefaultHasher::new(); + + // Because these two futures share the same underlying future, + // `ptr_eq` should return true. + assert!(shared.ptr_eq(&shared2)); + // Equivalence relations are symmetric + assert!(shared2.ptr_eq(&shared)); + + // If `ptr_eq` returns true, they should hash to the same value. + shared.ptr_hash(&mut hasher); + shared2.ptr_hash(&mut hasher2); + assert_eq!(hasher.finish(), hasher2.finish()); + + tx.send(42).unwrap(); + assert_eq!(block_on(&mut shared2).unwrap(), 42); + + // Now that `shared2` has completed, `ptr_eq` should return false. + assert!(shared2.is_terminated()); + assert!(!shared.ptr_eq(&shared2)); + + // `ptr_eq` should continue to work for the other `Shared`. + let shared3 = shared.clone(); + let mut hasher3 = DefaultHasher::new(); + assert!(shared.ptr_eq(&shared3)); + + shared3.ptr_hash(&mut hasher3); + assert_eq!(hasher.finish(), hasher3.finish()); - use count_clone::CountClone; + let (_tx, rx) = oneshot::channel::(); + let shared4 = rx.shared(); + // And `ptr_eq` should return false for two futures that don't share + // the underlying future. + assert!(!shared.ptr_eq(&shared4)); +} + +#[test] +fn dont_clone_in_single_owner_shared_future() { let counter = CountClone(Rc::new(Cell::new(0))); let (tx, rx) = oneshot::channel(); @@ -193,14 +211,6 @@ fn dont_clone_in_single_owner_shared_future() { #[test] fn dont_do_unnecessary_clones_on_output() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::FutureExt; - use std::cell::Cell; - use std::rc::Rc; - - use count_clone::CountClone; - let counter = CountClone(Rc::new(Cell::new(0))); let (tx, rx) = oneshot::channel(); @@ -215,11 +225,6 @@ fn dont_do_unnecessary_clones_on_output() { #[test] fn shared_future_that_wakes_itself_until_pending_is_returned() { - use futures::executor::block_on; - use futures::future::FutureExt; - use std::cell::Cell; - use std::task::Poll; - let proceed = Cell::new(false); let fut = futures::future::poll_fn(|cx| { if proceed.get() { @@ -233,8 +238,36 @@ fn shared_future_that_wakes_itself_until_pending_is_returned() { // The join future can only complete if the second future gets a chance to run after the first // has returned pending - assert_eq!( - block_on(futures::future::join(fut, async { proceed.set(true) })), - ((), ()) - ); + assert_eq!(block_on(futures::future::join(fut, async { proceed.set(true) })), ((), ())); +} + +#[test] +#[should_panic(expected = "inner future panicked during poll")] +fn panic_while_poll() { + let fut = futures::future::poll_fn::(|_cx| panic!("test")).shared(); + + let fut_captured = fut.clone(); + std::panic::catch_unwind(AssertUnwindSafe(|| { + block_on(fut_captured); + })) + .unwrap_err(); + + block_on(fut); +} + +#[test] +#[should_panic(expected = "test_marker")] +fn poll_while_panic() { + struct S; + + impl Drop for S { + fn drop(&mut self) { + let fut = futures::future::ready(1).shared(); + assert_eq!(block_on(fut.clone()), 1); + assert_eq!(block_on(fut), 1); + } + } + + let _s = S {}; + panic!("test_marker"); } diff --git a/futures/tests/future_try_flatten_stream.rs b/futures/tests/future_try_flatten_stream.rs index 4a614f9c14..82ae1baf2c 100644 --- a/futures/tests/future_try_flatten_stream.rs +++ b/futures/tests/future_try_flatten_stream.rs @@ -1,9 +1,14 @@ +use futures::executor::block_on_stream; +use futures::future::{err, ok, TryFutureExt}; +use futures::sink::Sink; +use futures::stream::Stream; +use futures::stream::{self, StreamExt}; +use futures::task::{Context, Poll}; +use std::marker::PhantomData; +use std::pin::Pin; + #[test] fn successful_future() { - use futures::executor::block_on_stream; - use futures::future::{ok, TryFutureExt}; - use futures::stream::{self, StreamExt}; - let stream_items = vec![17, 19]; let future_of_a_stream = ok::<_, bool>(stream::iter(stream_items).map(Ok)); @@ -17,15 +22,8 @@ fn successful_future() { #[test] fn failed_future() { - use core::marker::PhantomData; - use core::pin::Pin; - use futures::executor::block_on_stream; - use futures::future::{err, TryFutureExt}; - use futures::stream::Stream; - use futures::task::{Context, Poll}; - struct PanickingStream { - _marker: PhantomData<(T, E)> + _marker: PhantomData<(T, E)>, } impl Stream for PanickingStream { @@ -45,13 +43,6 @@ fn failed_future() { #[test] fn assert_impls() { - use core::marker::PhantomData; - use core::pin::Pin; - use futures::sink::Sink; - use futures::stream::Stream; - use futures::task::{Context, Poll}; - use futures::future::{ok, TryFutureExt}; - struct StreamSink(PhantomData<(T, E, Item)>); impl Stream for StreamSink { diff --git a/futures/tests/future_try_join_all.rs b/futures/tests/future_try_join_all.rs new file mode 100644 index 0000000000..892e775920 --- /dev/null +++ b/futures/tests/future_try_join_all.rs @@ -0,0 +1,45 @@ +use futures::executor::block_on; +use futures::future::{err, ok, try_join_all, Future, TryJoinAll}; +use futures::pin_mut; +use std::fmt::Debug; + +#[track_caller] +fn assert_done(actual_fut: impl Future, expected: T) +where + T: PartialEq + Debug, +{ + pin_mut!(actual_fut); + let output = block_on(actual_fut); + assert_eq!(output, expected); +} + +#[test] +fn collect_collects() { + assert_done(try_join_all(vec![ok(1), ok(2)]), Ok::<_, usize>(vec![1, 2])); + assert_done(try_join_all(vec![ok(1), err(2)]), Err(2)); + assert_done(try_join_all(vec![ok(1)]), Ok::<_, usize>(vec![1])); + // REVIEW: should this be implemented? + // assert_done(try_join_all(Vec::::new()), Ok(vec![])); + + // TODO: needs more tests +} + +#[test] +fn try_join_all_iter_lifetime() { + // In futures-rs version 0.1, this function would fail to typecheck due to an overly + // conservative type parameterization of `TryJoinAll`. + fn sizes(bufs: Vec<&[u8]>) -> impl Future, ()>> { + let iter = bufs.into_iter().map(|b| ok::(b.len())); + try_join_all(iter) + } + + assert_done(sizes(vec![&[1, 2, 3], &[], &[0]]), Ok(vec![3_usize, 0, 1])); +} + +#[test] +fn try_join_all_from_iter() { + assert_done( + vec![ok(1), ok(2)].into_iter().collect::>(), + Ok::<_, usize>(vec![1, 2]), + ) +} diff --git a/futures/tests/futures_ordered.rs b/futures/tests/futures_ordered.rs deleted file mode 100644 index 7f21c829e3..0000000000 --- a/futures/tests/futures_ordered.rs +++ /dev/null @@ -1,96 +0,0 @@ -#[test] -fn works_1() { - use futures::channel::oneshot; - use futures::executor::block_on_stream; - use futures::stream::{StreamExt, FuturesOrdered}; - use futures_test::task::noop_context; - - let (a_tx, a_rx) = oneshot::channel::(); - let (b_tx, b_rx) = oneshot::channel::(); - let (c_tx, c_rx) = oneshot::channel::(); - - let mut stream = vec![a_rx, b_rx, c_rx].into_iter().collect::>(); - - b_tx.send(99).unwrap(); - assert!(stream.poll_next_unpin(&mut noop_context()).is_pending()); - - a_tx.send(33).unwrap(); - c_tx.send(33).unwrap(); - - let mut iter = block_on_stream(stream); - assert_eq!(Some(Ok(33)), iter.next()); - assert_eq!(Some(Ok(99)), iter.next()); - assert_eq!(Some(Ok(33)), iter.next()); - assert_eq!(None, iter.next()); -} - -#[test] -fn works_2() { - use futures::channel::oneshot; - use futures::future::{join, FutureExt}; - use futures::stream::{StreamExt, FuturesOrdered}; - use futures_test::task::noop_context; - - let (a_tx, a_rx) = oneshot::channel::(); - let (b_tx, b_rx) = oneshot::channel::(); - let (c_tx, c_rx) = oneshot::channel::(); - - let mut stream = vec![ - a_rx.boxed(), - join(b_rx, c_rx).map(|(a, b)| Ok(a? + b?)).boxed(), - ].into_iter().collect::>(); - - let mut cx = noop_context(); - a_tx.send(33).unwrap(); - b_tx.send(33).unwrap(); - assert!(stream.poll_next_unpin(&mut cx).is_ready()); - assert!(stream.poll_next_unpin(&mut cx).is_pending()); - c_tx.send(33).unwrap(); - assert!(stream.poll_next_unpin(&mut cx).is_ready()); -} - -#[test] -fn from_iterator() { - use futures::executor::block_on; - use futures::future; - use futures::stream::{StreamExt, FuturesOrdered}; - - let stream = vec![ - future::ready::(1), - future::ready::(2), - future::ready::(3) - ].into_iter().collect::>(); - assert_eq!(stream.len(), 3); - assert_eq!(block_on(stream.collect::>()), vec![1,2,3]); -} - -#[test] -fn queue_never_unblocked() { - use futures::channel::oneshot; - use futures::future::{self, Future, TryFutureExt}; - use futures::stream::{StreamExt, FuturesOrdered}; - use futures_test::task::noop_context; - use std::any::Any; - - let (_a_tx, a_rx) = oneshot::channel::>(); - let (b_tx, b_rx) = oneshot::channel::>(); - let (c_tx, c_rx) = oneshot::channel::>(); - - let mut stream = vec![ - Box::new(a_rx) as Box + Unpin>, - Box::new(future::try_select(b_rx, c_rx) - .map_err(|e| e.factor_first().0) - .and_then(|e| future::ok(Box::new(e) as Box))) as _, - ].into_iter().collect::>(); - - let cx = &mut noop_context(); - for _ in 0..10 { - assert!(stream.poll_next_unpin(cx).is_pending()); - } - - b_tx.send(Box::new(())).unwrap(); - assert!(stream.poll_next_unpin(cx).is_pending()); - c_tx.send(Box::new(())).unwrap(); - assert!(stream.poll_next_unpin(cx).is_pending()); - assert!(stream.poll_next_unpin(cx).is_pending()); -} diff --git a/futures/tests/inspect.rs b/futures/tests/inspect.rs deleted file mode 100644 index 375778b63d..0000000000 --- a/futures/tests/inspect.rs +++ /dev/null @@ -1,14 +0,0 @@ -#[test] -fn smoke() { - use futures::executor::block_on; - use futures::future::{self, FutureExt}; - - let mut counter = 0; - - { - let work = future::ready::(40).inspect(|val| { counter += *val; }); - assert_eq!(block_on(work), 40); - } - - assert_eq!(counter, 40); -} diff --git a/futures/tests/io_buf_reader.rs b/futures/tests/io_buf_reader.rs index f8f9d140e3..717297ccea 100644 --- a/futures/tests/io_buf_reader.rs +++ b/futures/tests/io_buf_reader.rs @@ -1,156 +1,240 @@ -macro_rules! run_fill_buf { - ($reader:expr) => {{ - use futures_test::task::noop_context; - use futures::task::Poll; - use std::pin::Pin; - - let mut cx = noop_context(); - loop { - if let Poll::Ready(x) = Pin::new(&mut $reader).poll_fill_buf(&mut cx) { - break x; - } +use futures::executor::block_on; +use futures::future::{Future, FutureExt}; +use futures::io::{ + AllowStdIo, AsyncBufRead, AsyncBufReadExt, AsyncRead, AsyncReadExt, AsyncSeek, AsyncSeekExt, + BufReader, SeekFrom, +}; +use futures::pin_mut; +use futures::task::{Context, Poll}; +use futures_test::task::noop_context; +use pin_project::pin_project; +use std::cmp; +use std::io; +use std::pin::Pin; + +// helper for maybe_pending_* tests +fn run(mut f: F) -> F::Output { + let mut cx = noop_context(); + loop { + if let Poll::Ready(x) = f.poll_unpin(&mut cx) { + return x; } - }}; + } } -mod util { - use futures::future::Future; - pub fn run(mut f: F) -> F::Output { - use futures_test::task::noop_context; - use futures::task::Poll; - use futures::future::FutureExt; - - let mut cx = noop_context(); - loop { - if let Poll::Ready(x) = f.poll_unpin(&mut cx) { - return x; - } - } +// https://github.com/rust-lang/futures-rs/pull/2489#discussion_r697865719 +#[pin_project(!Unpin)] +struct Cursor { + #[pin] + inner: futures::io::Cursor, +} + +impl Cursor { + fn new(inner: T) -> Self { + Self { inner: futures::io::Cursor::new(inner) } } } -mod maybe_pending { - use futures::task::{Context,Poll}; - use std::{cmp,io}; - use std::pin::Pin; - use futures::io::{AsyncRead,AsyncBufRead}; +impl AsyncRead for Cursor<&[u8]> { + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + self.project().inner.poll_read(cx, buf) + } +} - pub struct MaybePending<'a> { - inner: &'a [u8], - ready_read: bool, - ready_fill_buf: bool, +impl AsyncBufRead for Cursor<&[u8]> { + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().inner.poll_fill_buf(cx) } - impl<'a> MaybePending<'a> { - pub fn new(inner: &'a [u8]) -> Self { - Self { inner, ready_read: false, ready_fill_buf: false } - } + fn consume(self: Pin<&mut Self>, amt: usize) { + self.project().inner.consume(amt) } +} - impl AsyncRead for MaybePending<'_> { - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { - if self.ready_read { - self.ready_read = false; - Pin::new(&mut self.inner).poll_read(cx, buf) - } else { - self.ready_read = true; - Poll::Pending - } +impl AsyncSeek for Cursor<&[u8]> { + fn poll_seek( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + pos: SeekFrom, + ) -> Poll> { + self.project().inner.poll_seek(cx, pos) + } +} + +struct MaybePending<'a> { + inner: &'a [u8], + ready_read: bool, + ready_fill_buf: bool, +} + +impl<'a> MaybePending<'a> { + fn new(inner: &'a [u8]) -> Self { + Self { inner, ready_read: false, ready_fill_buf: false } + } +} + +impl AsyncRead for MaybePending<'_> { + fn poll_read( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + if self.ready_read { + self.ready_read = false; + Pin::new(&mut self.inner).poll_read(cx, buf) + } else { + self.ready_read = true; + Poll::Pending } } +} - impl AsyncBufRead for MaybePending<'_> { - fn poll_fill_buf(mut self: Pin<&mut Self>, _: &mut Context<'_>) - -> Poll> - { - if self.ready_fill_buf { - self.ready_fill_buf = false; - if self.inner.is_empty() { return Poll::Ready(Ok(&[])) } - let len = cmp::min(2, self.inner.len()); - Poll::Ready(Ok(&self.inner[0..len])) - } else { - self.ready_fill_buf = true; - Poll::Pending +impl AsyncBufRead for MaybePending<'_> { + fn poll_fill_buf(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + if self.ready_fill_buf { + self.ready_fill_buf = false; + if self.inner.is_empty() { + return Poll::Ready(Ok(&[])); } + let len = cmp::min(2, self.inner.len()); + Poll::Ready(Ok(&self.inner[0..len])) + } else { + self.ready_fill_buf = true; + Poll::Pending } + } - fn consume(mut self: Pin<&mut Self>, amt: usize) { - self.inner = &self.inner[amt..]; - } + fn consume(mut self: Pin<&mut Self>, amt: usize) { + self.inner = &self.inner[amt..]; } } #[test] fn test_buffered_reader() { - use futures::executor::block_on; - use futures::io::{AsyncReadExt, BufReader}; - - let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; - let mut reader = BufReader::with_capacity(2, inner); - - let mut buf = [0, 0, 0]; - let nread = block_on(reader.read(&mut buf)); - assert_eq!(nread.unwrap(), 3); - assert_eq!(buf, [5, 6, 7]); - assert_eq!(reader.buffer(), []); - - let mut buf = [0, 0]; - let nread = block_on(reader.read(&mut buf)); - assert_eq!(nread.unwrap(), 2); - assert_eq!(buf, [0, 1]); - assert_eq!(reader.buffer(), []); - - let mut buf = [0]; - let nread = block_on(reader.read(&mut buf)); - assert_eq!(nread.unwrap(), 1); - assert_eq!(buf, [2]); - assert_eq!(reader.buffer(), [3]); - - let mut buf = [0, 0, 0]; - let nread = block_on(reader.read(&mut buf)); - assert_eq!(nread.unwrap(), 1); - assert_eq!(buf, [3, 0, 0]); - assert_eq!(reader.buffer(), []); + block_on(async { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let mut reader = BufReader::with_capacity(2, inner); + + let mut buf = [0, 0, 0]; + let nread = reader.read(&mut buf).await.unwrap(); + assert_eq!(nread, 3); + assert_eq!(buf, [5, 6, 7]); + assert_eq!(reader.buffer(), []); + + let mut buf = [0, 0]; + let nread = reader.read(&mut buf).await.unwrap(); + assert_eq!(nread, 2); + assert_eq!(buf, [0, 1]); + assert_eq!(reader.buffer(), []); + + let mut buf = [0]; + let nread = reader.read(&mut buf).await.unwrap(); + assert_eq!(nread, 1); + assert_eq!(buf, [2]); + assert_eq!(reader.buffer(), [3]); + + let mut buf = [0, 0, 0]; + let nread = reader.read(&mut buf).await.unwrap(); + assert_eq!(nread, 1); + assert_eq!(buf, [3, 0, 0]); + assert_eq!(reader.buffer(), []); + + let nread = reader.read(&mut buf).await.unwrap(); + assert_eq!(nread, 1); + assert_eq!(buf, [4, 0, 0]); + assert_eq!(reader.buffer(), []); + + assert_eq!(reader.read(&mut buf).await.unwrap(), 0); + }); +} - let nread = block_on(reader.read(&mut buf)); - assert_eq!(nread.unwrap(), 1); - assert_eq!(buf, [4, 0, 0]); - assert_eq!(reader.buffer(), []); +#[test] +fn test_buffered_reader_seek() { + block_on(async { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let reader = BufReader::with_capacity(2, Cursor::new(inner)); + pin_mut!(reader); + + assert_eq!(reader.seek(SeekFrom::Start(3)).await.unwrap(), 3); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[0, 1][..]); + assert!(reader.seek(SeekFrom::Current(i64::MIN)).await.is_err()); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[0, 1][..]); + assert_eq!(reader.seek(SeekFrom::Current(1)).await.unwrap(), 4); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[1, 2][..]); + reader.as_mut().consume(1); + assert_eq!(reader.seek(SeekFrom::Current(-2)).await.unwrap(), 3); + }); +} - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 0); +#[test] +fn test_buffered_reader_seek_relative() { + block_on(async { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let reader = BufReader::with_capacity(2, Cursor::new(inner)); + pin_mut!(reader); + + assert!(reader.as_mut().seek_relative(3).await.is_ok()); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[0, 1][..]); + assert!(reader.as_mut().seek_relative(0).await.is_ok()); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[0, 1][..]); + assert!(reader.as_mut().seek_relative(1).await.is_ok()); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[1][..]); + assert!(reader.as_mut().seek_relative(-1).await.is_ok()); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[0, 1][..]); + assert!(reader.as_mut().seek_relative(2).await.is_ok()); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[2, 3][..]); + }); } #[test] -fn test_buffered_reader_seek() { - use futures::executor::block_on; - use futures::io::{AsyncSeekExt, AsyncBufRead, BufReader, Cursor, SeekFrom}; - use std::pin::Pin; - use util::run; +fn test_buffered_reader_invalidated_after_read() { + block_on(async { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let reader = BufReader::with_capacity(3, Cursor::new(inner)); + pin_mut!(reader); + + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[5, 6, 7][..]); + reader.as_mut().consume(3); + + let mut buffer = [0, 0, 0, 0, 0]; + assert_eq!(reader.read(&mut buffer).await.unwrap(), 5); + assert_eq!(buffer, [0, 1, 2, 3, 4]); + + assert!(reader.as_mut().seek_relative(-2).await.is_ok()); + let mut buffer = [0, 0]; + assert_eq!(reader.read(&mut buffer).await.unwrap(), 2); + assert_eq!(buffer, [3, 4]); + }); +} - let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; - let mut reader = BufReader::with_capacity(2, Cursor::new(inner)); - - assert_eq!(block_on(reader.seek(SeekFrom::Start(3))).ok(), Some(3)); - assert_eq!(run_fill_buf!(reader).ok(), Some(&[0, 1][..])); - assert_eq!(run(reader.seek(SeekFrom::Current(i64::min_value()))).ok(), None); - assert_eq!(run_fill_buf!(reader).ok(), Some(&[0, 1][..])); - assert_eq!(block_on(reader.seek(SeekFrom::Current(1))).ok(), Some(4)); - assert_eq!(run_fill_buf!(reader).ok(), Some(&[1, 2][..])); - Pin::new(&mut reader).consume(1); - assert_eq!(block_on(reader.seek(SeekFrom::Current(-2))).ok(), Some(3)); +#[test] +fn test_buffered_reader_invalidated_after_seek() { + block_on(async { + let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; + let reader = BufReader::with_capacity(3, Cursor::new(inner)); + pin_mut!(reader); + + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[5, 6, 7][..]); + reader.as_mut().consume(3); + + assert!(reader.seek(SeekFrom::Current(5)).await.is_ok()); + + assert!(reader.as_mut().seek_relative(-2).await.is_ok()); + let mut buffer = [0, 0]; + assert_eq!(reader.read(&mut buffer).await.unwrap(), 2); + assert_eq!(buffer, [3, 4]); + }); } #[test] fn test_buffered_reader_seek_underflow() { - use futures::executor::block_on; - use futures::io::{AsyncSeekExt, AsyncBufRead, AllowStdIo, BufReader, SeekFrom}; - use std::io; - // gimmick reader that yields its position modulo 256 for each byte struct PositionReader { - pos: u64 + pos: u64, } impl io::Read for PositionReader { fn read(&mut self, buf: &mut [u8]) -> io::Result { @@ -172,32 +256,31 @@ fn test_buffered_reader_seek_underflow() { self.pos = self.pos.wrapping_add(n as u64); } SeekFrom::End(n) => { - self.pos = u64::max_value().wrapping_add(n as u64); + self.pos = u64::MAX.wrapping_add(n as u64); } } Ok(self.pos) } } - let mut reader = BufReader::with_capacity(5, AllowStdIo::new(PositionReader { pos: 0 })); - assert_eq!(run_fill_buf!(reader).ok(), Some(&[0, 1, 2, 3, 4][..])); - assert_eq!(block_on(reader.seek(SeekFrom::End(-5))).ok(), Some(u64::max_value()-5)); - assert_eq!(run_fill_buf!(reader).ok().map(|s| s.len()), Some(5)); - // the following seek will require two underlying seeks - let expected = 9_223_372_036_854_775_802; - assert_eq!(block_on(reader.seek(SeekFrom::Current(i64::min_value()))).ok(), Some(expected)); - assert_eq!(run_fill_buf!(reader).ok().map(|s| s.len()), Some(5)); - // seeking to 0 should empty the buffer. - assert_eq!(block_on(reader.seek(SeekFrom::Current(0))).ok(), Some(expected)); - assert_eq!(reader.get_ref().get_ref().pos, expected); + block_on(async { + let reader = BufReader::with_capacity(5, AllowStdIo::new(PositionReader { pos: 0 })); + pin_mut!(reader); + assert_eq!(reader.as_mut().fill_buf().await.unwrap(), &[0, 1, 2, 3, 4][..]); + assert_eq!(reader.seek(SeekFrom::End(-5)).await.unwrap(), u64::MAX - 5); + assert_eq!(reader.as_mut().fill_buf().await.unwrap().len(), 5); + // the following seek will require two underlying seeks + let expected = 9_223_372_036_854_775_802; + assert_eq!(reader.seek(SeekFrom::Current(i64::MIN)).await.unwrap(), expected); + assert_eq!(reader.as_mut().fill_buf().await.unwrap().len(), 5); + // seeking to 0 should empty the buffer. + assert_eq!(reader.seek(SeekFrom::Current(0)).await.unwrap(), expected); + assert_eq!(reader.get_ref().get_ref().pos, expected); + }); } #[test] fn test_short_reads() { - use futures::executor::block_on; - use futures::io::{AsyncReadExt, AllowStdIo, BufReader}; - use std::io; - /// A dummy reader intended at testing short-reads propagation. struct ShortReader { lengths: Vec, @@ -213,24 +296,22 @@ fn test_short_reads() { } } - let inner = ShortReader { lengths: vec![0, 1, 2, 0, 1, 0] }; - let mut reader = BufReader::new(AllowStdIo::new(inner)); - let mut buf = [0, 0]; - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 0); - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 1); - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 2); - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 0); - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 1); - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 0); - assert_eq!(block_on(reader.read(&mut buf)).unwrap(), 0); + block_on(async { + let inner = ShortReader { lengths: vec![0, 1, 2, 0, 1, 0] }; + let mut reader = BufReader::new(AllowStdIo::new(inner)); + let mut buf = [0, 0]; + assert_eq!(reader.read(&mut buf).await.unwrap(), 0); + assert_eq!(reader.read(&mut buf).await.unwrap(), 1); + assert_eq!(reader.read(&mut buf).await.unwrap(), 2); + assert_eq!(reader.read(&mut buf).await.unwrap(), 0); + assert_eq!(reader.read(&mut buf).await.unwrap(), 1); + assert_eq!(reader.read(&mut buf).await.unwrap(), 0); + assert_eq!(reader.read(&mut buf).await.unwrap(), 0); + }); } #[test] fn maybe_pending() { - use futures::io::{AsyncReadExt, BufReader}; - use util::run; - use maybe_pending::MaybePending; - let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; let mut reader = BufReader::with_capacity(2, MaybePending::new(inner)); @@ -268,10 +349,6 @@ fn maybe_pending() { #[test] fn maybe_pending_buf_read() { - use futures::io::{AsyncBufReadExt, BufReader}; - use util::run; - use maybe_pending::MaybePending; - let inner = MaybePending::new(&[0, 1, 2, 3, 1, 0]); let mut reader = BufReader::with_capacity(2, inner); let mut v = Vec::new(); @@ -291,68 +368,65 @@ fn maybe_pending_buf_read() { // https://github.com/rust-lang/futures-rs/pull/1573#discussion_r281162309 #[test] fn maybe_pending_seek() { - use futures::io::{AsyncBufRead, AsyncSeek, AsyncSeekExt, AsyncRead, BufReader, - Cursor, SeekFrom - }; - use futures::task::{Context,Poll}; - use std::io; - use std::pin::Pin; - use util::run; - pub struct MaybePendingSeek<'a> { + #[pin_project] + struct MaybePendingSeek<'a> { + #[pin] inner: Cursor<&'a [u8]>, ready: bool, } impl<'a> MaybePendingSeek<'a> { - pub fn new(inner: &'a [u8]) -> Self { + fn new(inner: &'a [u8]) -> Self { Self { inner: Cursor::new(inner), ready: true } } } impl AsyncRead for MaybePendingSeek<'_> { - fn poll_read(mut self: Pin<&mut Self>, cx: &mut Context<'_>, buf: &mut [u8]) - -> Poll> - { - Pin::new(&mut self.inner).poll_read(cx, buf) + fn poll_read( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + self.project().inner.poll_read(cx, buf) } } impl AsyncBufRead for MaybePendingSeek<'_> { - fn poll_fill_buf(mut self: Pin<&mut Self>, cx: &mut Context<'_>) - -> Poll> - { - let this: *mut Self = &mut *self as *mut _; - Pin::new(&mut unsafe { &mut *this }.inner).poll_fill_buf(cx) + fn poll_fill_buf(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().inner.poll_fill_buf(cx) } - fn consume(mut self: Pin<&mut Self>, amt: usize) { - Pin::new(&mut self.inner).consume(amt) + fn consume(self: Pin<&mut Self>, amt: usize) { + self.project().inner.consume(amt) } } impl AsyncSeek for MaybePendingSeek<'_> { - fn poll_seek(mut self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom) - -> Poll> - { + fn poll_seek( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + pos: SeekFrom, + ) -> Poll> { if self.ready { - self.ready = false; - Pin::new(&mut self.inner).poll_seek(cx, pos) + *self.as_mut().project().ready = false; + self.project().inner.poll_seek(cx, pos) } else { - self.ready = true; + *self.project().ready = true; Poll::Pending } } } let inner: &[u8] = &[5, 6, 7, 0, 1, 2, 3, 4]; - let mut reader = BufReader::with_capacity(2, MaybePendingSeek::new(inner)); + let reader = BufReader::with_capacity(2, MaybePendingSeek::new(inner)); + pin_mut!(reader); assert_eq!(run(reader.seek(SeekFrom::Current(3))).ok(), Some(3)); - assert_eq!(run_fill_buf!(reader).ok(), Some(&[0, 1][..])); - assert_eq!(run(reader.seek(SeekFrom::Current(i64::min_value()))).ok(), None); - assert_eq!(run_fill_buf!(reader).ok(), Some(&[0, 1][..])); + assert_eq!(run(reader.as_mut().fill_buf()).ok(), Some(&[0, 1][..])); + assert_eq!(run(reader.seek(SeekFrom::Current(i64::MIN))).ok(), None); + assert_eq!(run(reader.as_mut().fill_buf()).ok(), Some(&[0, 1][..])); assert_eq!(run(reader.seek(SeekFrom::Current(1))).ok(), Some(4)); - assert_eq!(run_fill_buf!(reader).ok(), Some(&[1, 2][..])); + assert_eq!(run(reader.as_mut().fill_buf()).ok(), Some(&[1, 2][..])); Pin::new(&mut reader).consume(1); assert_eq!(run(reader.seek(SeekFrom::Current(-2))).ok(), Some(3)); } diff --git a/futures/tests/io_buf_writer.rs b/futures/tests/io_buf_writer.rs index d58a6d801f..b264cd54c2 100644 --- a/futures/tests/io_buf_writer.rs +++ b/futures/tests/io_buf_writer.rs @@ -1,67 +1,59 @@ -mod maybe_pending { - use futures::io::AsyncWrite; - use futures::task::{Context, Poll}; - use std::io; - use std::pin::Pin; - - pub struct MaybePending { - pub inner: Vec, - ready: bool, - } +use futures::executor::block_on; +use futures::future::{Future, FutureExt}; +use futures::io::{ + AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt, BufWriter, Cursor, SeekFrom, +}; +use futures::task::{Context, Poll}; +use futures_test::task::noop_context; +use std::io; +use std::pin::Pin; + +struct MaybePending { + inner: Vec, + ready: bool, +} - impl MaybePending { - pub fn new(inner: Vec) -> Self { - Self { inner, ready: false } - } +impl MaybePending { + fn new(inner: Vec) -> Self { + Self { inner, ready: false } } +} - impl AsyncWrite for MaybePending { - fn poll_write( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - if self.ready { - self.ready = false; - Pin::new(&mut self.inner).poll_write(cx, buf) - } else { - self.ready = true; - Poll::Pending - } +impl AsyncWrite for MaybePending { + fn poll_write( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + if self.ready { + self.ready = false; + Pin::new(&mut self.inner).poll_write(cx, buf) + } else { + self.ready = true; + Poll::Pending } + } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.inner).poll_flush(cx) - } + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_flush(cx) + } - fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.inner).poll_close(cx) - } + fn poll_close(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + Pin::new(&mut self.inner).poll_close(cx) } } -mod util { - use futures::future::Future; - - pub fn run(mut f: F) -> F::Output { - use futures::future::FutureExt; - use futures::task::Poll; - use futures_test::task::noop_context; - - let mut cx = noop_context(); - loop { - if let Poll::Ready(x) = f.poll_unpin(&mut cx) { - return x; - } +fn run(mut f: F) -> F::Output { + let mut cx = noop_context(); + loop { + if let Poll::Ready(x) = f.poll_unpin(&mut cx) { + return x; } } } #[test] fn buf_writer() { - use futures::executor::block_on; - use futures::io::{AsyncWriteExt, BufWriter}; - let mut writer = BufWriter::with_capacity(2, Vec::new()); block_on(writer.write(&[0, 1])).unwrap(); @@ -104,9 +96,6 @@ fn buf_writer() { #[test] fn buf_writer_inner_flushes() { - use futures::executor::block_on; - use futures::io::{AsyncWriteExt, BufWriter}; - let mut w = BufWriter::with_capacity(3, Vec::new()); block_on(w.write(&[0, 1])).unwrap(); assert_eq!(*w.get_ref(), []); @@ -117,9 +106,6 @@ fn buf_writer_inner_flushes() { #[test] fn buf_writer_seek() { - use futures::executor::block_on; - use futures::io::{AsyncSeekExt, AsyncWriteExt, BufWriter, Cursor, SeekFrom}; - // FIXME: when https://github.com/rust-lang/futures-rs/issues/1510 fixed, // use `Vec::new` instead of `vec![0; 8]`. let mut w = BufWriter::with_capacity(3, Cursor::new(vec![0; 8])); @@ -135,11 +121,6 @@ fn buf_writer_seek() { #[test] fn maybe_pending_buf_writer() { - use futures::io::{AsyncWriteExt, BufWriter}; - - use maybe_pending::MaybePending; - use util::run; - let mut writer = BufWriter::with_capacity(2, MaybePending::new(Vec::new())); run(writer.write(&[0, 1])).unwrap(); @@ -182,11 +163,6 @@ fn maybe_pending_buf_writer() { #[test] fn maybe_pending_buf_writer_inner_flushes() { - use futures::io::{AsyncWriteExt, BufWriter}; - - use maybe_pending::MaybePending; - use util::run; - let mut w = BufWriter::with_capacity(3, MaybePending::new(Vec::new())); run(w.write(&[0, 1])).unwrap(); assert_eq!(&w.get_ref().inner, &[]); @@ -197,13 +173,6 @@ fn maybe_pending_buf_writer_inner_flushes() { #[test] fn maybe_pending_buf_writer_seek() { - use futures::io::{AsyncSeek, AsyncSeekExt, AsyncWrite, AsyncWriteExt, BufWriter, Cursor, SeekFrom}; - use futures::task::{Context, Poll}; - use std::io; - use std::pin::Pin; - - use util::run; - struct MaybePendingSeek { inner: Cursor>, ready_write: bool, @@ -241,9 +210,11 @@ fn maybe_pending_buf_writer_seek() { } impl AsyncSeek for MaybePendingSeek { - fn poll_seek(mut self: Pin<&mut Self>, cx: &mut Context<'_>, pos: SeekFrom) - -> Poll> - { + fn poll_seek( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + pos: SeekFrom, + ) -> Poll> { if self.ready_seek { self.ready_seek = false; Pin::new(&mut self.inner).poll_seek(cx, pos) diff --git a/futures/tests/io_cursor.rs b/futures/tests/io_cursor.rs index 4ba6342525..435ea5a155 100644 --- a/futures/tests/io_cursor.rs +++ b/futures/tests/io_cursor.rs @@ -1,13 +1,14 @@ +use assert_matches::assert_matches; +use futures::executor::block_on; +use futures::future::lazy; +use futures::io::{AsyncWrite, Cursor}; +use futures::task::Poll; +use std::pin::Pin; + #[test] fn cursor_asyncwrite_vec() { - use assert_matches::assert_matches; - use futures::future::lazy; - use futures::io::{AsyncWrite, Cursor}; - use futures::task::Poll; - use std::pin::Pin; - let mut cursor = Cursor::new(vec![0; 5]); - futures::executor::block_on(lazy(|cx| { + block_on(lazy(|cx| { assert_matches!(Pin::new(&mut cursor).poll_write(cx, &[1, 2]), Poll::Ready(Ok(2))); assert_matches!(Pin::new(&mut cursor).poll_write(cx, &[3, 4]), Poll::Ready(Ok(2))); assert_matches!(Pin::new(&mut cursor).poll_write(cx, &[5, 6]), Poll::Ready(Ok(2))); @@ -18,14 +19,8 @@ fn cursor_asyncwrite_vec() { #[test] fn cursor_asyncwrite_box() { - use assert_matches::assert_matches; - use futures::future::lazy; - use futures::io::{AsyncWrite, Cursor}; - use futures::task::Poll; - use std::pin::Pin; - let mut cursor = Cursor::new(vec![0; 5].into_boxed_slice()); - futures::executor::block_on(lazy(|cx| { + block_on(lazy(|cx| { assert_matches!(Pin::new(&mut cursor).poll_write(cx, &[1, 2]), Poll::Ready(Ok(2))); assert_matches!(Pin::new(&mut cursor).poll_write(cx, &[3, 4]), Poll::Ready(Ok(2))); assert_matches!(Pin::new(&mut cursor).poll_write(cx, &[5, 6]), Poll::Ready(Ok(1))); diff --git a/futures/tests/io_line_writer.rs b/futures/tests/io_line_writer.rs new file mode 100644 index 0000000000..b483e0ff77 --- /dev/null +++ b/futures/tests/io_line_writer.rs @@ -0,0 +1,73 @@ +use futures::executor::block_on; +use futures::io::{AsyncWriteExt, LineWriter}; +use std::io; + +#[test] +fn line_writer() { + let mut writer = LineWriter::new(Vec::new()); + + block_on(writer.write(&[0])).unwrap(); + assert_eq!(*writer.get_ref(), []); + + block_on(writer.write(&[1])).unwrap(); + assert_eq!(*writer.get_ref(), []); + + block_on(writer.flush()).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1]); + + block_on(writer.write(&[0, b'\n', 1, b'\n', 2])).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n']); + + block_on(writer.flush()).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n', 2]); + + block_on(writer.write(&[3, b'\n'])).unwrap(); + assert_eq!(*writer.get_ref(), [0, 1, 0, b'\n', 1, b'\n', 2, 3, b'\n']); +} + +#[test] +fn line_vectored() { + let mut line_writer = LineWriter::new(Vec::new()); + assert_eq!( + block_on(line_writer.write_vectored(&[ + io::IoSlice::new(&[]), + io::IoSlice::new(b"\n"), + io::IoSlice::new(&[]), + io::IoSlice::new(b"a"), + ])) + .unwrap(), + 2 + ); + assert_eq!(line_writer.get_ref(), b"\n"); + + assert_eq!( + block_on(line_writer.write_vectored(&[ + io::IoSlice::new(&[]), + io::IoSlice::new(b"b"), + io::IoSlice::new(&[]), + io::IoSlice::new(b"a"), + io::IoSlice::new(&[]), + io::IoSlice::new(b"c"), + ])) + .unwrap(), + 3 + ); + assert_eq!(line_writer.get_ref(), b"\n"); + block_on(line_writer.flush()).unwrap(); + assert_eq!(line_writer.get_ref(), b"\nabac"); + assert_eq!(block_on(line_writer.write_vectored(&[])).unwrap(), 0); + + assert_eq!( + block_on(line_writer.write_vectored(&[ + io::IoSlice::new(&[]), + io::IoSlice::new(&[]), + io::IoSlice::new(&[]), + io::IoSlice::new(&[]), + ])) + .unwrap(), + 0 + ); + + assert_eq!(block_on(line_writer.write_vectored(&[io::IoSlice::new(b"a\nb")])).unwrap(), 3); + assert_eq!(line_writer.get_ref(), b"\nabaca\nb"); +} diff --git a/futures/tests/io_lines.rs b/futures/tests/io_lines.rs index 2552c7c40a..62afef326a 100644 --- a/futures/tests/io_lines.rs +++ b/futures/tests/io_lines.rs @@ -1,32 +1,52 @@ -mod util { - use futures::future::Future; +use futures::executor::block_on; +use futures::future::{Future, FutureExt}; +use futures::io::{AsyncBufReadExt, AsyncRead, Cursor}; +use futures::stream::{self, StreamExt, TryStreamExt}; +use futures::task::Poll; +use futures_test::io::AsyncReadTestExt; +use futures_test::task::noop_context; - pub fn run(mut f: F) -> F::Output { - use futures_test::task::noop_context; - use futures::task::Poll; - use futures::future::FutureExt; - - let mut cx = noop_context(); - loop { - if let Poll::Ready(x) = f.poll_unpin(&mut cx) { - return x; - } +fn run(mut f: F) -> F::Output { + let mut cx = noop_context(); + loop { + if let Poll::Ready(x) = f.poll_unpin(&mut cx) { + return x; } } } -#[test] -fn lines() { - use futures::executor::block_on; - use futures::stream::StreamExt; - use futures::io::{AsyncBufReadExt, Cursor}; +macro_rules! block_on_next { + ($expr:expr) => { + block_on($expr.next()).unwrap().unwrap() + }; +} + +macro_rules! run_next { + ($expr:expr) => { + run($expr.next()).unwrap().unwrap() + }; +} - macro_rules! block_on_next { - ($expr:expr) => { - block_on($expr.next()).unwrap().unwrap() - }; +struct IOErrorRead(bool); + +impl AsyncRead for IOErrorRead { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + b: &mut [u8], + ) -> Poll> { + if self.0 { + Poll::Ready(Err(std::io::ErrorKind::InvalidInput.into())) + } else { + self.0 = true; + b[..16].fill(b'x'); + Ok(16).into() + } } +} +#[test] +fn lines() { let buf = Cursor::new(&b"12\r"[..]); let mut s = buf.lines(); assert_eq!(block_on_next!(s), "12\r".to_string()); @@ -41,22 +61,8 @@ fn lines() { #[test] fn maybe_pending() { - use futures::stream::{self, StreamExt, TryStreamExt}; - use futures::io::AsyncBufReadExt; - use futures_test::io::AsyncReadTestExt; - - use util::run; - - macro_rules! run_next { - ($expr:expr) => { - run($expr.next()).unwrap().unwrap() - }; - } - - let buf = stream::iter(vec![&b"12"[..], &b"\r"[..]]) - .map(Ok) - .into_async_read() - .interleave_pending(); + let buf = + stream::iter(vec![&b"12"[..], &b"\r"[..]]).map(Ok).into_async_read().interleave_pending(); let mut s = buf.lines(); assert_eq!(run_next!(s), "12\r".to_string()); assert!(run(s.next()).is_none()); @@ -70,3 +76,9 @@ fn maybe_pending() { assert_eq!(run_next!(s), "".to_string()); assert!(run(s.next()).is_none()); } + +#[test] +fn issue2862() { + let mut lines = futures::io::BufReader::new(IOErrorRead(false)).lines(); + assert!(block_on(lines.next()).unwrap().is_err()) +} diff --git a/futures/tests/io_read.rs b/futures/tests/io_read.rs index 5902ad0ed9..d39a6ea790 100644 --- a/futures/tests/io_read.rs +++ b/futures/tests/io_read.rs @@ -1,27 +1,26 @@ -mod mock_reader { - use futures::io::AsyncRead; - use std::io; - use std::pin::Pin; - use std::task::{Context, Poll}; +use futures::io::AsyncRead; +use futures_test::task::panic_context; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; - pub struct MockReader { - fun: Box Poll>>, - } +struct MockReader { + fun: Box Poll>>, +} - impl MockReader { - pub fn new(fun: impl FnMut(&mut [u8]) -> Poll> + 'static) -> Self { - Self { fun: Box::new(fun) } - } +impl MockReader { + fn new(fun: impl FnMut(&mut [u8]) -> Poll> + 'static) -> Self { + Self { fun: Box::new(fun) } } +} - impl AsyncRead for MockReader { - fn poll_read( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &mut [u8] - ) -> Poll> { - (self.get_mut().fun)(buf) - } +impl AsyncRead for MockReader { + fn poll_read( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + (self.get_mut().fun)(buf) } } @@ -29,14 +28,6 @@ mod mock_reader { /// calls `poll_read` with an empty slice if no buffers are provided. #[test] fn read_vectored_no_buffers() { - use futures::io::AsyncRead; - use futures_test::task::panic_context; - use std::io; - use std::pin::Pin; - use std::task::Poll; - - use mock_reader::MockReader; - let mut reader = MockReader::new(|buf| { assert_eq!(buf, b""); Err(io::ErrorKind::BrokenPipe.into()).into() @@ -53,14 +44,6 @@ fn read_vectored_no_buffers() { /// calls `poll_read` with the first non-empty buffer. #[test] fn read_vectored_first_non_empty() { - use futures::io::AsyncRead; - use futures_test::task::panic_context; - use std::io; - use std::pin::Pin; - use std::task::Poll; - - use mock_reader::MockReader; - let mut reader = MockReader::new(|buf| { assert_eq!(buf.len(), 4); buf.copy_from_slice(b"four"); diff --git a/futures/tests/io_read_exact.rs b/futures/tests/io_read_exact.rs index bd4b36deaf..6582e50b80 100644 --- a/futures/tests/io_read_exact.rs +++ b/futures/tests/io_read_exact.rs @@ -1,14 +1,14 @@ +use futures::executor::block_on; +use futures::io::AsyncReadExt; + #[test] fn read_exact() { - use futures::executor::block_on; - use futures::io::AsyncReadExt; - let mut reader: &[u8] = &[1, 2, 3, 4, 5]; let mut out = [0u8; 3]; let res = block_on(reader.read_exact(&mut out)); // read 3 bytes out assert!(res.is_ok()); - assert_eq!(out, [1,2,3]); + assert_eq!(out, [1, 2, 3]); assert_eq!(reader.len(), 2); let res = block_on(reader.read_exact(&mut out)); // read another 3 bytes, but only 2 bytes left diff --git a/futures/tests/io_read_line.rs b/futures/tests/io_read_line.rs index 51e8126ada..c7559c593b 100644 --- a/futures/tests/io_read_line.rs +++ b/futures/tests/io_read_line.rs @@ -1,8 +1,41 @@ +use futures::executor::block_on; +use futures::future::{Future, FutureExt}; +use futures::io::{AsyncBufReadExt, Cursor}; +use futures::stream::{self, StreamExt, TryStreamExt}; +use futures::task::Poll; +use futures::AsyncRead; +use futures_test::io::AsyncReadTestExt; +use futures_test::task::noop_context; + +fn run(mut f: F) -> F::Output { + let mut cx = noop_context(); + loop { + if let Poll::Ready(x) = f.poll_unpin(&mut cx) { + return x; + } + } +} + +struct IOErrorRead(bool); + +impl AsyncRead for IOErrorRead { + fn poll_read( + mut self: std::pin::Pin<&mut Self>, + _cx: &mut std::task::Context<'_>, + b: &mut [u8], + ) -> Poll> { + if self.0 { + Poll::Ready(Err(std::io::ErrorKind::InvalidInput.into())) + } else { + self.0 = true; + b[..16].fill(b'x'); + Ok(16).into() + } + } +} + #[test] fn read_line() { - use futures::executor::block_on; - use futures::io::{AsyncBufReadExt, Cursor}; - let mut buf = Cursor::new(b"12"); let mut v = String::new(); assert_eq!(block_on(buf.read_line(&mut v)).unwrap(), 2); @@ -21,35 +54,38 @@ fn read_line() { } #[test] -fn maybe_pending() { - use futures::future::Future; - - fn run(mut f: F) -> F::Output { - use futures::future::FutureExt; - use futures::task::Poll; - use futures_test::task::noop_context; +fn read_line_drop() { + // string contents should be preserved if the future is dropped + let mut buf = Cursor::new(b"12\n\n"); + let mut v = String::from("abc"); + drop(buf.read_line(&mut v)); + assert_eq!(v, "abc"); +} - let mut cx = noop_context(); - loop { - if let Poll::Ready(x) = f.poll_unpin(&mut cx) { - return x; - } - } - } +#[test] +fn read_line_io_error() { + let mut r = futures::io::BufReader::new(IOErrorRead(false)); + let _ = block_on(r.read_line(&mut String::new())); +} - use futures::stream::{self, StreamExt, TryStreamExt}; - use futures::io::AsyncBufReadExt; - use futures_test::io::AsyncReadTestExt; +#[test] +fn read_line_utf8_error() { + let mut buf = Cursor::new(b"12\xFF\n\n"); + let mut v = String::from("abc"); + let res = block_on(buf.read_line(&mut v)); + assert_eq!(res.unwrap_err().kind(), std::io::ErrorKind::InvalidData); + assert_eq!(v, "abc"); +} +#[test] +fn maybe_pending() { let mut buf = b"12".interleave_pending(); let mut v = String::new(); assert_eq!(run(buf.read_line(&mut v)).unwrap(), 2); assert_eq!(v, "12"); - let mut buf = stream::iter(vec![&b"12"[..], &b"\n\n"[..]]) - .map(Ok) - .into_async_read() - .interleave_pending(); + let mut buf = + stream::iter(vec![&b"12"[..], &b"\n\n"[..]]).map(Ok).into_async_read().interleave_pending(); let mut v = String::new(); assert_eq!(run(buf.read_line(&mut v)).unwrap(), 3); assert_eq!(v, "12\n"); diff --git a/futures/tests/io_read_to_end.rs b/futures/tests/io_read_to_end.rs index 892d463c2d..0441b3bc4f 100644 --- a/futures/tests/io_read_to_end.rs +++ b/futures/tests/io_read_to_end.rs @@ -1,4 +1,5 @@ use futures::{ + executor::block_on, io::{self, AsyncRead, AsyncReadExt}, task::{Context, Poll}, }; @@ -12,15 +13,15 @@ fn issue2310() { } impl MyRead { - pub fn new() -> Self { - MyRead { first: false } + fn new() -> Self { + Self { first: false } } } impl AsyncRead for MyRead { fn poll_read( mut self: Pin<&mut Self>, - _cx: &mut Context, + _cx: &mut Context<'_>, _buf: &mut [u8], ) -> Poll> { Poll::Ready(if !self.first { @@ -39,8 +40,8 @@ fn issue2310() { } impl VecWrapper { - pub fn new() -> Self { - VecWrapper { inner: Vec::new() } + fn new() -> Self { + Self { inner: Vec::new() } } } @@ -55,7 +56,7 @@ fn issue2310() { } } - futures::executor::block_on(async { + block_on(async { let mut vec = VecWrapper::new(); let mut read = MyRead::new(); diff --git a/futures/tests/io_read_to_string.rs b/futures/tests/io_read_to_string.rs index 2e9c00a138..ae6aaa21d8 100644 --- a/futures/tests/io_read_to_string.rs +++ b/futures/tests/io_read_to_string.rs @@ -1,8 +1,13 @@ +use futures::executor::block_on; +use futures::future::{Future, FutureExt}; +use futures::io::{AsyncReadExt, Cursor}; +use futures::stream::{self, StreamExt, TryStreamExt}; +use futures::task::Poll; +use futures_test::io::AsyncReadTestExt; +use futures_test::task::noop_context; + #[test] fn read_to_string() { - use futures::executor::block_on; - use futures::io::{AsyncReadExt, Cursor}; - let mut c = Cursor::new(&b""[..]); let mut v = String::new(); assert_eq!(block_on(c.read_to_string(&mut v)).unwrap(), 0); @@ -20,16 +25,7 @@ fn read_to_string() { #[test] fn interleave_pending() { - use futures::future::Future; - use futures::stream::{self, StreamExt, TryStreamExt}; - use futures::io::AsyncReadExt; - use futures_test::io::AsyncReadTestExt; - fn run(mut f: F) -> F::Output { - use futures::future::FutureExt; - use futures_test::task::noop_context; - use futures::task::Poll; - let mut cx = noop_context(); loop { if let Poll::Ready(x) = f.poll_unpin(&mut cx) { diff --git a/futures/tests/io_read_until.rs b/futures/tests/io_read_until.rs index 6fa22eee65..71f857f4b0 100644 --- a/futures/tests/io_read_until.rs +++ b/futures/tests/io_read_until.rs @@ -1,8 +1,22 @@ +use futures::executor::block_on; +use futures::future::{Future, FutureExt}; +use futures::io::{AsyncBufReadExt, Cursor}; +use futures::stream::{self, StreamExt, TryStreamExt}; +use futures::task::Poll; +use futures_test::io::AsyncReadTestExt; +use futures_test::task::noop_context; + +fn run(mut f: F) -> F::Output { + let mut cx = noop_context(); + loop { + if let Poll::Ready(x) = f.poll_unpin(&mut cx) { + return x; + } + } +} + #[test] fn read_until() { - use futures::executor::block_on; - use futures::io::{AsyncBufReadExt, Cursor}; - let mut buf = Cursor::new(b"12"); let mut v = Vec::new(); assert_eq!(block_on(buf.read_until(b'3', &mut v)).unwrap(), 2); @@ -22,25 +36,6 @@ fn read_until() { #[test] fn maybe_pending() { - use futures::future::Future; - - fn run(mut f: F) -> F::Output { - use futures::future::FutureExt; - use futures_test::task::noop_context; - use futures::task::Poll; - - let mut cx = noop_context(); - loop { - if let Poll::Ready(x) = f.poll_unpin(&mut cx) { - return x; - } - } - } - - use futures::stream::{self, StreamExt, TryStreamExt}; - use futures::io::AsyncBufReadExt; - use futures_test::io::AsyncReadTestExt; - let mut buf = b"12".interleave_pending(); let mut v = Vec::new(); assert_eq!(run(buf.read_until(b'3', &mut v)).unwrap(), 2); diff --git a/futures/tests/io_write.rs b/futures/tests/io_write.rs index 363f32b1a6..6af27553cb 100644 --- a/futures/tests/io_write.rs +++ b/futures/tests/io_write.rs @@ -1,35 +1,34 @@ -mod mock_writer { - use futures::io::AsyncWrite; - use std::io; - use std::pin::Pin; - use std::task::{Context, Poll}; +use futures::io::AsyncWrite; +use futures_test::task::panic_context; +use std::io; +use std::pin::Pin; +use std::task::{Context, Poll}; - pub struct MockWriter { - fun: Box Poll>>, - } +struct MockWriter { + fun: Box Poll>>, +} - impl MockWriter { - pub fn new(fun: impl FnMut(&[u8]) -> Poll> + 'static) -> Self { - Self { fun: Box::new(fun) } - } +impl MockWriter { + fn new(fun: impl FnMut(&[u8]) -> Poll> + 'static) -> Self { + Self { fun: Box::new(fun) } } +} - impl AsyncWrite for MockWriter { - fn poll_write( - self: Pin<&mut Self>, - _cx: &mut Context<'_>, - buf: &[u8], - ) -> Poll> { - (self.get_mut().fun)(buf) - } +impl AsyncWrite for MockWriter { + fn poll_write( + self: Pin<&mut Self>, + _cx: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + (self.get_mut().fun)(buf) + } - fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - panic!() - } + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + panic!() + } - fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { - panic!() - } + fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { + panic!() } } @@ -37,14 +36,6 @@ mod mock_writer { /// calls `poll_write` with an empty slice if no buffers are provided. #[test] fn write_vectored_no_buffers() { - use futures::io::AsyncWrite; - use futures_test::task::panic_context; - use std::io; - use std::pin::Pin; - use std::task::Poll; - - use mock_writer::MockWriter; - let mut writer = MockWriter::new(|buf| { assert_eq!(buf, b""); Err(io::ErrorKind::BrokenPipe.into()).into() @@ -61,24 +52,12 @@ fn write_vectored_no_buffers() { /// calls `poll_write` with the first non-empty buffer. #[test] fn write_vectored_first_non_empty() { - use futures::io::AsyncWrite; - use futures_test::task::panic_context; - use std::io; - use std::pin::Pin; - use std::task::Poll; - - use mock_writer::MockWriter; - let mut writer = MockWriter::new(|buf| { assert_eq!(buf, b"four"); Poll::Ready(Ok(4)) }); let cx = &mut panic_context(); - let bufs = &mut [ - io::IoSlice::new(&[]), - io::IoSlice::new(&[]), - io::IoSlice::new(b"four") - ]; + let bufs = &mut [io::IoSlice::new(&[]), io::IoSlice::new(&[]), io::IoSlice::new(b"four")]; let res = Pin::new(&mut writer).poll_write_vectored(cx, bufs); let res = res.map_err(|e| e.kind()); diff --git a/futures/tests/join_all.rs b/futures/tests/join_all.rs deleted file mode 100644 index c322e58a13..0000000000 --- a/futures/tests/join_all.rs +++ /dev/null @@ -1,51 +0,0 @@ -mod util { - use std::future::Future; - use std::fmt::Debug; - - pub fn assert_done(actual_fut: F, expected: T) - where - T: PartialEq + Debug, - F: FnOnce() -> Box + Unpin>, - { - use futures::executor::block_on; - - let output = block_on(actual_fut()); - assert_eq!(output, expected); - } -} - -#[test] -fn collect_collects() { - use futures_util::future::{join_all,ready}; - - util::assert_done(|| Box::new(join_all(vec![ready(1), ready(2)])), vec![1, 2]); - util::assert_done(|| Box::new(join_all(vec![ready(1)])), vec![1]); - // REVIEW: should this be implemented? - // assert_done(|| Box::new(join_all(Vec::::new())), vec![]); - - // TODO: needs more tests -} - -#[test] -fn join_all_iter_lifetime() { - use futures_util::future::{join_all,ready}; - use std::future::Future; - // In futures-rs version 0.1, this function would fail to typecheck due to an overly - // conservative type parameterization of `JoinAll`. - fn sizes(bufs: Vec<&[u8]>) -> Box> + Unpin> { - let iter = bufs.into_iter().map(|b| ready::(b.len())); - Box::new(join_all(iter)) - } - - util::assert_done(|| sizes(vec![&[1,2,3], &[], &[0]]), vec![3_usize, 0, 1]); -} - -#[test] -fn join_all_from_iter() { - use futures_util::future::{JoinAll,ready}; - - util::assert_done( - || Box::new(vec![ready(1), ready(2)].into_iter().collect::>()), - vec![1, 2], - ) -} diff --git a/futures/tests/lock_mutex.rs b/futures/tests/lock_mutex.rs new file mode 100644 index 0000000000..c15e76bd84 --- /dev/null +++ b/futures/tests/lock_mutex.rs @@ -0,0 +1,69 @@ +use futures::channel::mpsc; +use futures::executor::{block_on, ThreadPool}; +use futures::future::{ready, FutureExt}; +use futures::lock::Mutex; +use futures::stream::StreamExt; +use futures::task::{Context, SpawnExt}; +use futures_test::future::FutureTestExt; +use futures_test::task::{new_count_waker, panic_context}; +use std::sync::Arc; + +#[test] +fn mutex_acquire_uncontested() { + let mutex = Mutex::new(()); + for _ in 0..10 { + assert!(mutex.lock().poll_unpin(&mut panic_context()).is_ready()); + } +} + +#[test] +fn mutex_wakes_waiters() { + let mutex = Mutex::new(()); + let (waker, counter) = new_count_waker(); + let lock = mutex.lock().poll_unpin(&mut panic_context()); + assert!(lock.is_ready()); + + let mut cx = Context::from_waker(&waker); + let mut waiter = mutex.lock(); + assert!(waiter.poll_unpin(&mut cx).is_pending()); + assert_eq!(counter, 0); + + drop(lock); + + assert_eq!(counter, 1); + assert!(waiter.poll_unpin(&mut panic_context()).is_ready()); +} + +#[test] +fn mutex_contested() { + { + let (tx, mut rx) = mpsc::unbounded(); + let pool = ThreadPool::builder().pool_size(16).create().unwrap(); + + let tx = Arc::new(tx); + let mutex = Arc::new(Mutex::new(0)); + + let num_tasks = 1000; + for _ in 0..num_tasks { + let tx = tx.clone(); + let mutex = mutex.clone(); + pool.spawn(async move { + let mut lock = mutex.lock().await; + ready(()).pending_once().await; + *lock += 1; + tx.unbounded_send(()).unwrap(); + drop(lock); + }) + .unwrap(); + } + + block_on(async { + for _ in 0..num_tasks { + rx.next().await.unwrap(); + } + let lock = mutex.lock().await; + assert_eq!(num_tasks, *lock); + }); + } + std::thread::sleep(std::time::Duration::from_millis(500)); // wait for background threads closed: https://github.com/rust-lang/miri/issues/1371 +} diff --git a/futures/tests/macro-reexport/Cargo.toml b/futures/tests/macro-reexport/Cargo.toml index 940b537ea7..2ddd1587a0 100644 --- a/futures/tests/macro-reexport/Cargo.toml +++ b/futures/tests/macro-reexport/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "macro-reexport" -version = "0.1.0" -authors = ["Taiki Endo "] +version = "0.0.0" edition = "2018" publish = false [dependencies] futures03 = { path = "../..", package = "futures" } + +[lints] +workspace = true diff --git a/futures/tests/macro-reexport/src/lib.rs b/futures/tests/macro-reexport/src/lib.rs index 49691c9e07..82f939b343 100644 --- a/futures/tests/macro-reexport/src/lib.rs +++ b/futures/tests/macro-reexport/src/lib.rs @@ -1,8 +1,7 @@ // normal reexport -pub use futures03::{join, try_join, select, select_biased}; +pub use futures03::{join, select, select_biased, try_join}; // reexport + rename pub use futures03::{ - join as join2, try_join as try_join2, - select as select2, select_biased as select_biased2, + join as join2, select as select2, select_biased as select_biased2, try_join as try_join2, }; diff --git a/futures/tests/macro-tests/Cargo.toml b/futures/tests/macro-tests/Cargo.toml index 3d30563868..db72bd41b2 100644 --- a/futures/tests/macro-tests/Cargo.toml +++ b/futures/tests/macro-tests/Cargo.toml @@ -1,10 +1,12 @@ [package] name = "macro-tests" -version = "0.1.0" -authors = ["Taiki Endo "] +version = "0.0.0" edition = "2018" publish = false [dependencies] futures03 = { path = "../..", package = "futures" } macro-reexport = { path = "../macro-reexport" } + +[lints] +workspace = true diff --git a/futures/tests/macro-tests/src/main.rs b/futures/tests/macro-tests/src/main.rs index 5d11f60834..c654e0c92c 100644 --- a/futures/tests/macro-tests/src/main.rs +++ b/futures/tests/macro-tests/src/main.rs @@ -65,5 +65,4 @@ fn main() { _ = b => unreachable!(), }; }); - } diff --git a/futures/tests/macro_comma_support.rs b/futures/tests/macro_comma_support.rs index ca131639dd..85871e98be 100644 --- a/futures/tests/macro_comma_support.rs +++ b/futures/tests/macro_comma_support.rs @@ -1,12 +1,13 @@ +use futures::{ + executor::block_on, + future::{self, FutureExt}, + join, ready, + task::Poll, + try_join, +}; + #[test] fn ready() { - use futures::{ - executor::block_on, - future, - task::Poll, - ready, - }; - block_on(future::poll_fn(|_| { ready!(Poll::Ready(()),); Poll::Ready(()) @@ -15,11 +16,7 @@ fn ready() { #[test] fn poll() { - use futures::{ - executor::block_on, - future::FutureExt, - poll, - }; + use futures::poll; block_on(async { let _ = poll!(async {}.boxed(),); @@ -28,11 +25,6 @@ fn poll() { #[test] fn join() { - use futures::{ - executor::block_on, - join - }; - block_on(async { let future1 = async { 1 }; let future2 = async { 2 }; @@ -42,12 +34,6 @@ fn join() { #[test] fn try_join() { - use futures::{ - executor::block_on, - future::FutureExt, - try_join, - }; - block_on(async { let future1 = async { 1 }.never_error(); let future2 = async { 2 }.never_error(); diff --git a/futures/tests/mutex.rs b/futures/tests/mutex.rs deleted file mode 100644 index 68e0301426..0000000000 --- a/futures/tests/mutex.rs +++ /dev/null @@ -1,77 +0,0 @@ -#[test] -fn mutex_acquire_uncontested() { - use futures::future::FutureExt; - use futures::lock::Mutex; - use futures_test::task::panic_context; - - let mutex = Mutex::new(()); - for _ in 0..10 { - assert!(mutex.lock().poll_unpin(&mut panic_context()).is_ready()); - } -} - -#[test] -fn mutex_wakes_waiters() { - use futures::future::FutureExt; - use futures::lock::Mutex; - use futures::task::Context; - use futures_test::task::{new_count_waker, panic_context}; - - let mutex = Mutex::new(()); - let (waker, counter) = new_count_waker(); - let lock = mutex.lock().poll_unpin(&mut panic_context()); - assert!(lock.is_ready()); - - let mut cx = Context::from_waker(&waker); - let mut waiter = mutex.lock(); - assert!(waiter.poll_unpin(&mut cx).is_pending()); - assert_eq!(counter, 0); - - drop(lock); - - assert_eq!(counter, 1); - assert!(waiter.poll_unpin(&mut panic_context()).is_ready()); -} - -#[test] -fn mutex_contested() { - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::future::ready; - use futures::lock::Mutex; - use futures::stream::StreamExt; - use futures::task::SpawnExt; - use futures_test::future::FutureTestExt; - use std::sync::Arc; - - let (tx, mut rx) = mpsc::unbounded(); - let pool = futures::executor::ThreadPool::builder() - .pool_size(16) - .create() - .unwrap(); - - let tx = Arc::new(tx); - let mutex = Arc::new(Mutex::new(0)); - - let num_tasks = 1000; - for _ in 0..num_tasks { - let tx = tx.clone(); - let mutex = mutex.clone(); - pool.spawn(async move { - let mut lock = mutex.lock().await; - ready(()).pending_once().await; - *lock += 1; - tx.unbounded_send(()).unwrap(); - drop(lock); - }) - .unwrap(); - } - - block_on(async { - for _ in 0..num_tasks { - rx.next().await.unwrap(); - } - let lock = mutex.lock().await; - assert_eq!(num_tasks, *lock); - }) -} diff --git a/futures/tests/no-std/Cargo.toml b/futures/tests/no-std/Cargo.toml new file mode 100644 index 0000000000..ec7d4c7871 --- /dev/null +++ b/futures/tests/no-std/Cargo.toml @@ -0,0 +1,24 @@ +[package] +name = "no-std" +version = "0.0.0" +edition = "2018" +publish = false + +[features] +futures-core-alloc = ["futures-core/alloc"] +futures-task-alloc = ["futures-task/alloc"] +futures-channel-alloc = ["futures-channel/alloc"] +futures-util-alloc = ["futures-util/alloc"] +futures-util-async-await = ["futures-util/async-await"] +futures-alloc = ["futures/alloc"] +futures-async-await = ["futures/async-await"] + +[dependencies] +futures-core = { path = "../../../futures-core", optional = true, default-features = false } +futures-task = { path = "../../../futures-task", optional = true, default-features = false } +futures-channel = { path = "../../../futures-channel", optional = true, default-features = false } +futures-util = { path = "../../../futures-util", optional = true, default-features = false } +futures = { path = "../..", optional = true, default-features = false } + +[lints] +workspace = true diff --git a/futures/tests/no-std/build.rs b/futures/tests/no-std/build.rs new file mode 100644 index 0000000000..ba79f203f2 --- /dev/null +++ b/futures/tests/no-std/build.rs @@ -0,0 +1,15 @@ +use std::{env, process::Command}; + +fn main() { + println!("cargo:rustc-check-cfg=cfg(nightly)"); + if is_nightly() { + println!("cargo:rustc-cfg=nightly"); + } +} + +fn is_nightly() -> bool { + env::var_os("RUSTC") + .and_then(|rustc| Command::new(rustc).arg("--version").output().ok()) + .and_then(|output| String::from_utf8(output.stdout).ok()) + .map_or(false, |version| version.contains("nightly") || version.contains("dev")) +} diff --git a/futures/tests/no-std/src/lib.rs b/futures/tests/no-std/src/lib.rs new file mode 100644 index 0000000000..7209e7f6a1 --- /dev/null +++ b/futures/tests/no-std/src/lib.rs @@ -0,0 +1,31 @@ +#![cfg(nightly)] +#![no_std] +#![allow(unused_imports)] + +#[cfg(feature = "futures-core-alloc")] +#[cfg(target_has_atomic = "ptr")] +pub use futures_core::task::__internal::AtomicWaker as _; + +#[cfg(feature = "futures-task-alloc")] +#[cfg(target_has_atomic = "ptr")] +pub use futures_task::ArcWake as _; + +#[cfg(feature = "futures-channel-alloc")] +#[cfg(target_has_atomic = "ptr")] +pub use futures_channel::oneshot as _; + +#[cfg(any(feature = "futures", feature = "futures-alloc"))] +#[cfg(target_has_atomic = "ptr")] +pub use futures::task::AtomicWaker as _; + +#[cfg(feature = "futures-alloc")] +#[cfg(target_has_atomic = "ptr")] +pub use futures::stream::FuturesOrdered as _; + +#[cfg(any(feature = "futures-util", feature = "futures-util-alloc"))] +#[cfg(target_has_atomic = "ptr")] +pub use futures_util::task::AtomicWaker as _; + +#[cfg(feature = "futures-util-alloc")] +#[cfg(target_has_atomic = "ptr")] +pub use futures_util::stream::FuturesOrdered as _; diff --git a/futures/tests/oneshot.rs b/futures/tests/oneshot.rs index 2494306fad..34b78a33fb 100644 --- a/futures/tests/oneshot.rs +++ b/futures/tests/oneshot.rs @@ -1,11 +1,11 @@ +use futures::channel::oneshot; +use futures::future::{FutureExt, TryFutureExt}; +use futures_test::future::FutureTestExt; +use std::sync::mpsc; +use std::thread; + #[test] fn oneshot_send1() { - use futures::channel::oneshot; - use futures::future::TryFutureExt; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - use std::thread; - let (tx1, rx1) = oneshot::channel::(); let (tx2, rx2) = mpsc::channel(); @@ -17,12 +17,6 @@ fn oneshot_send1() { #[test] fn oneshot_send2() { - use futures::channel::oneshot; - use futures::future::TryFutureExt; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - use std::thread; - let (tx1, rx1) = oneshot::channel::(); let (tx2, rx2) = mpsc::channel(); @@ -33,12 +27,6 @@ fn oneshot_send2() { #[test] fn oneshot_send3() { - use futures::channel::oneshot; - use futures::future::TryFutureExt; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - use std::thread; - let (tx1, rx1) = oneshot::channel::(); let (tx2, rx2) = mpsc::channel(); @@ -49,11 +37,6 @@ fn oneshot_send3() { #[test] fn oneshot_drop_tx1() { - use futures::channel::oneshot; - use futures::future::FutureExt; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - let (tx1, rx1) = oneshot::channel::(); let (tx2, rx2) = mpsc::channel(); @@ -65,12 +48,6 @@ fn oneshot_drop_tx1() { #[test] fn oneshot_drop_tx2() { - use futures::channel::oneshot; - use futures::future::FutureExt; - use futures_test::future::FutureTestExt; - use std::sync::mpsc; - use std::thread; - let (tx1, rx1) = oneshot::channel::(); let (tx2, rx2) = mpsc::channel(); @@ -83,9 +60,19 @@ fn oneshot_drop_tx2() { #[test] fn oneshot_drop_rx() { - use futures::channel::oneshot; - let (tx, rx) = oneshot::channel::(); drop(rx); assert_eq!(Err(2), tx.send(2)); } + +#[test] +fn oneshot_debug() { + let (tx, rx) = oneshot::channel::(); + assert_eq!(format!("{:?}", tx), "Sender { complete: false }"); + assert_eq!(format!("{:?}", rx), "Receiver { complete: false }"); + drop(rx); + assert_eq!(format!("{:?}", tx), "Sender { complete: true }"); + let (tx, rx) = oneshot::channel::(); + drop(tx); + assert_eq!(format!("{:?}", rx), "Receiver { complete: true }"); +} diff --git a/futures/tests/ready_queue.rs b/futures/tests/ready_queue.rs index 9aa36362d0..c19d62593c 100644 --- a/futures/tests/ready_queue.rs +++ b/futures/tests/ready_queue.rs @@ -1,18 +1,15 @@ -mod assert_send_sync { - use futures::stream::FuturesUnordered; - - pub trait AssertSendSync: Send + Sync {} - impl AssertSendSync for FuturesUnordered<()> {} -} +use futures::channel::oneshot; +use futures::executor::{block_on, block_on_stream}; +use futures::future; +use futures::stream::{FuturesUnordered, StreamExt}; +use futures::task::Poll; +use futures_test::task::noop_context; +use std::panic::{self, AssertUnwindSafe}; +use std::sync::{Arc, Barrier}; +use std::thread; #[test] fn basic_usage() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures::task::Poll; - block_on(future::lazy(move |cx| { let mut queue = FuturesUnordered::new(); let (tx1, rx1) = oneshot::channel(); @@ -41,12 +38,6 @@ fn basic_usage() { #[test] fn resolving_errors() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures::task::Poll; - block_on(future::lazy(move |cx| { let mut queue = FuturesUnordered::new(); let (tx1, rx1) = oneshot::channel(); @@ -75,12 +66,6 @@ fn resolving_errors() { #[test] fn dropping_ready_queue() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future; - use futures::stream::FuturesUnordered; - use futures_test::task::noop_context; - block_on(future::lazy(move |_| { let queue = FuturesUnordered::new(); let (mut tx1, rx1) = oneshot::channel::<()>(); @@ -108,13 +93,7 @@ fn dropping_ready_queue() { #[test] fn stress() { - use futures::channel::oneshot; - use futures::executor::block_on_stream; - use futures::stream::FuturesUnordered; - use std::sync::{Arc, Barrier}; - use std::thread; - - const ITER: usize = 300; + const ITER: usize = if cfg!(miri) { 30 } else { 300 }; for i in 0..ITER { let n = (i % 10) + 1; @@ -157,12 +136,6 @@ fn stress() { #[test] fn panicking_future_dropped() { - use futures::executor::block_on; - use futures::future; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures::task::Poll; - use std::panic::{self, AssertUnwindSafe}; - block_on(future::lazy(move |cx| { let mut queue = FuturesUnordered::new(); queue.push(future::poll_fn(|_| -> Poll> { panic!() })); diff --git a/futures/tests/recurse.rs b/futures/tests/recurse.rs index a151f1b1d4..d81753c9d7 100644 --- a/futures/tests/recurse.rs +++ b/futures/tests/recurse.rs @@ -1,10 +1,10 @@ +use futures::executor::block_on; +use futures::future::{self, BoxFuture, FutureExt}; +use std::sync::mpsc; +use std::thread; + #[test] fn lots() { - use futures::executor::block_on; - use futures::future::{self, FutureExt, BoxFuture}; - use std::sync::mpsc; - use std::thread; - #[cfg(not(futures_sanitizer))] const N: i32 = 1_000; #[cfg(futures_sanitizer)] // If N is many, asan reports stack-overflow: https://gist.github.com/taiki-e/099446d21cbec69d4acbacf7a9646136 @@ -20,8 +20,6 @@ fn lots() { } let (tx, rx) = mpsc::channel(); - thread::spawn(|| { - block_on(do_it((N, 0)).map(move |x| tx.send(x).unwrap())) - }); + thread::spawn(|| block_on(do_it((N, 0)).map(move |x| tx.send(x).unwrap()))); assert_eq!((0..=N).sum::(), rx.recv().unwrap()); } diff --git a/futures/tests/sink.rs b/futures/tests/sink.rs index 597ed34c7a..5b691e74c6 100644 --- a/futures/tests/sink.rs +++ b/futures/tests/sink.rs @@ -1,264 +1,221 @@ -mod sassert_next { - use futures::stream::{Stream, StreamExt}; - use futures::task::Poll; - use futures_test::task::panic_context; - use std::fmt; - - pub fn sassert_next(s: &mut S, item: S::Item) - where - S: Stream + Unpin, - S::Item: Eq + fmt::Debug, - { - match s.poll_next_unpin(&mut panic_context()) { - Poll::Ready(None) => panic!("stream is at its end"), - Poll::Ready(Some(e)) => assert_eq!(e, item), - Poll::Pending => panic!("stream wasn't ready"), - } +use futures::channel::{mpsc, oneshot}; +use futures::executor::block_on; +use futures::future::{self, poll_fn, Future, FutureExt, TryFutureExt}; +use futures::never::Never; +use futures::ready; +use futures::sink::{self, Sink, SinkErrInto, SinkExt}; +use futures::stream::{self, Stream, StreamExt}; +use futures::task::{self, ArcWake, Context, Poll, Waker}; +use futures_test::task::panic_context; +use std::cell::{Cell, RefCell}; +use std::collections::VecDeque; +use std::fmt; +use std::mem; +use std::pin::Pin; +use std::rc::Rc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::Arc; + +fn sassert_next(s: &mut S, item: S::Item) +where + S: Stream + Unpin, + S::Item: Eq + fmt::Debug, +{ + match s.poll_next_unpin(&mut panic_context()) { + Poll::Ready(None) => panic!("stream is at its end"), + Poll::Ready(Some(e)) => assert_eq!(e, item), + Poll::Pending => panic!("stream wasn't ready"), } } -mod unwrap { - use futures::task::Poll; - use std::fmt; - - pub fn unwrap(x: Poll>) -> T { - match x { - Poll::Ready(Ok(x)) => x, - Poll::Ready(Err(_)) => panic!("Poll::Ready(Err(_))"), - Poll::Pending => panic!("Poll::Pending"), - } +fn unwrap(x: Poll>) -> T { + match x { + Poll::Ready(Ok(x)) => x, + Poll::Ready(Err(_)) => panic!("Poll::Ready(Err(_))"), + Poll::Pending => panic!("Poll::Pending"), } } -mod flag_cx { - use futures::task::{self, ArcWake, Context}; - use std::sync::Arc; - use std::sync::atomic::{AtomicBool, Ordering}; - - // An Unpark struct that records unpark events for inspection - pub struct Flag(AtomicBool); +// An Unpark struct that records unpark events for inspection +struct Flag(AtomicBool); - impl Flag { - pub fn new() -> Arc { - Arc::new(Self(AtomicBool::new(false))) - } - - pub fn take(&self) -> bool { - self.0.swap(false, Ordering::SeqCst) - } +impl Flag { + fn new() -> Arc { + Arc::new(Self(AtomicBool::new(false))) + } - pub fn set(&self, v: bool) { - self.0.store(v, Ordering::SeqCst) - } + fn take(&self) -> bool { + self.0.swap(false, Ordering::SeqCst) } - impl ArcWake for Flag { - fn wake_by_ref(arc_self: &Arc) { - arc_self.set(true) - } + fn set(&self, v: bool) { + self.0.store(v, Ordering::SeqCst) } +} - pub fn flag_cx(f: F) -> R - where - F: FnOnce(Arc, &mut Context<'_>) -> R, - { - let flag = Flag::new(); - let waker = task::waker_ref(&flag); - let cx = &mut Context::from_waker(&waker); - f(flag.clone(), cx) +impl ArcWake for Flag { + fn wake_by_ref(arc_self: &Arc) { + arc_self.set(true) } } -mod start_send_fut { - use futures::future::Future; - use futures::ready; - use futures::sink::Sink; - use futures::task::{Context, Poll}; - use std::pin::Pin; +fn flag_cx(f: F) -> R +where + F: FnOnce(Arc, &mut Context<'_>) -> R, +{ + let flag = Flag::new(); + let waker = task::waker_ref(&flag); + let cx = &mut Context::from_waker(&waker); + f(flag.clone(), cx) +} - // Sends a value on an i32 channel sink - pub struct StartSendFut + Unpin, Item: Unpin>(Option, Option); +// Sends a value on an i32 channel sink +struct StartSendFut + Unpin, Item: Unpin>(Option, Option); - impl + Unpin, Item: Unpin> StartSendFut { - pub fn new(sink: S, item: Item) -> Self { - Self(Some(sink), Some(item)) - } +impl + Unpin, Item: Unpin> StartSendFut { + fn new(sink: S, item: Item) -> Self { + Self(Some(sink), Some(item)) } +} - impl + Unpin, Item: Unpin> Future for StartSendFut { - type Output = Result; +impl + Unpin, Item: Unpin> Future for StartSendFut { + type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let Self(inner, item) = self.get_mut(); - { - let mut inner = inner.as_mut().unwrap(); - ready!(Pin::new(&mut inner).poll_ready(cx))?; - Pin::new(&mut inner).start_send(item.take().unwrap())?; - } - Poll::Ready(Ok(inner.take().unwrap())) + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let Self(inner, item) = self.get_mut(); + { + let mut inner = inner.as_mut().unwrap(); + ready!(Pin::new(&mut inner).poll_ready(cx))?; + Pin::new(&mut inner).start_send(item.take().unwrap())?; } + Poll::Ready(Ok(inner.take().unwrap())) } } -mod manual_flush { - use futures::sink::Sink; - use futures::task::{Context, Poll, Waker}; - use std::mem; - use std::pin::Pin; - - // Immediately accepts all requests to start pushing, but completion is managed - // by manually flushing - pub struct ManualFlush { - data: Vec, - waiting_tasks: Vec, - } +// Immediately accepts all requests to start pushing, but completion is managed +// by manually flushing +struct ManualFlush { + data: Vec, + waiting_tasks: Vec, +} - impl Sink> for ManualFlush { - type Error = (); +impl Sink> for ManualFlush { + type Error = (); - fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + fn poll_ready(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } - fn start_send(mut self: Pin<&mut Self>, item: Option) -> Result<(), Self::Error> { - if let Some(item) = item { - self.data.push(item); - } else { - self.force_flush(); - } - Ok(()) + fn start_send(mut self: Pin<&mut Self>, item: Option) -> Result<(), Self::Error> { + if let Some(item) = item { + self.data.push(item); + } else { + self.force_flush(); } + Ok(()) + } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.data.is_empty() { - Poll::Ready(Ok(())) - } else { - self.waiting_tasks.push(cx.waker().clone()); - Poll::Pending - } + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.data.is_empty() { + Poll::Ready(Ok(())) + } else { + self.waiting_tasks.push(cx.waker().clone()); + Poll::Pending } + } - fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - self.poll_flush(cx) - } + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.poll_flush(cx) } +} - impl ManualFlush { - pub fn new() -> Self { - Self { - data: Vec::new(), - waiting_tasks: Vec::new(), - } - } +impl ManualFlush { + fn new() -> Self { + Self { data: Vec::new(), waiting_tasks: Vec::new() } + } - pub fn force_flush(&mut self) -> Vec { - for task in self.waiting_tasks.drain(..) { - task.wake() - } - mem::replace(&mut self.data, Vec::new()) + fn force_flush(&mut self) -> Vec { + for task in self.waiting_tasks.drain(..) { + task.wake() } + mem::take(&mut self.data) } } -mod allowance { - use futures::sink::Sink; - use futures::task::{Context, Poll, Waker}; - use std::cell::{Cell, RefCell}; - use std::pin::Pin; - use std::rc::Rc; +struct ManualAllow { + data: Vec, + allow: Rc, +} - pub struct ManualAllow { - pub data: Vec, - allow: Rc, - } +struct Allow { + flag: Cell, + tasks: RefCell>, +} - pub struct Allow { - flag: Cell, - tasks: RefCell>, +impl Allow { + fn new() -> Self { + Self { flag: Cell::new(false), tasks: RefCell::new(Vec::new()) } } - impl Allow { - pub fn new() -> Self { - Self { - flag: Cell::new(false), - tasks: RefCell::new(Vec::new()), - } - } - - pub fn check(&self, cx: &mut Context<'_>) -> bool { - if self.flag.get() { - true - } else { - self.tasks.borrow_mut().push(cx.waker().clone()); - false - } - } - - pub fn start(&self) { - self.flag.set(true); - let mut tasks = self.tasks.borrow_mut(); - for task in tasks.drain(..) { - task.wake(); - } + fn check(&self, cx: &mut Context<'_>) -> bool { + if self.flag.get() { + true + } else { + self.tasks.borrow_mut().push(cx.waker().clone()); + false } } - impl Sink for ManualAllow { - type Error = (); - - fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - if self.allow.check(cx) { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } + fn start(&self) { + self.flag.set(true); + let mut tasks = self.tasks.borrow_mut(); + for task in tasks.drain(..) { + task.wake(); } + } +} - fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { - self.data.push(item); - Ok(()) - } +impl Sink for ManualAllow { + type Error = (); - fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + if self.allow.check(cx) { Poll::Ready(Ok(())) + } else { + Poll::Pending } + } - fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + fn start_send(mut self: Pin<&mut Self>, item: T) -> Result<(), Self::Error> { + self.data.push(item); + Ok(()) } - pub fn manual_allow() -> (ManualAllow, Rc) { - let allow = Rc::new(Allow::new()); - let manual_allow = ManualAllow { - data: Vec::new(), - allow: allow.clone(), - }; - (manual_allow, allow) + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) } + + fn poll_close(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} + +fn manual_allow() -> (ManualAllow, Rc) { + let allow = Rc::new(Allow::new()); + let manual_allow = ManualAllow { data: Vec::new(), allow: allow.clone() }; + (manual_allow, allow) } #[test] fn either_sink() { - use futures::sink::{Sink, SinkExt}; - use std::collections::VecDeque; - use std::pin::Pin; - - let mut s = if true { - Vec::::new().left_sink() - } else { - VecDeque::::new().right_sink() - }; + let mut s = + if true { Vec::::new().left_sink() } else { VecDeque::::new().right_sink() }; Pin::new(&mut s).start_send(0).unwrap(); } #[test] fn vec_sink() { - use futures::executor::block_on; - use futures::sink::{Sink, SinkExt}; - use std::pin::Pin; - let mut v = Vec::new(); Pin::new(&mut v).start_send(0).unwrap(); Pin::new(&mut v).start_send(1).unwrap(); @@ -269,10 +226,6 @@ fn vec_sink() { #[test] fn vecdeque_sink() { - use futures::sink::Sink; - use std::collections::VecDeque; - use std::pin::Pin; - let mut deque = VecDeque::new(); Pin::new(&mut deque).start_send(2).unwrap(); Pin::new(&mut deque).start_send(3).unwrap(); @@ -284,9 +237,6 @@ fn vecdeque_sink() { #[test] fn send() { - use futures::executor::block_on; - use futures::sink::SinkExt; - let mut v = Vec::new(); block_on(v.send(0)).unwrap(); @@ -301,10 +251,6 @@ fn send() { #[test] fn send_all() { - use futures::executor::block_on; - use futures::sink::SinkExt; - use futures::stream::{self, StreamExt}; - let mut v = Vec::new(); block_on(v.send_all(&mut stream::iter(vec![0, 1]).map(Ok))).unwrap(); @@ -321,15 +267,6 @@ fn send_all() { // channel is full #[test] fn mpsc_blocking_start_send() { - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::future::{self, FutureExt}; - - use start_send_fut::StartSendFut; - use flag_cx::flag_cx; - use sassert_next::sassert_next; - use unwrap::unwrap; - let (mut tx, mut rx) = mpsc::channel::(0); block_on(future::lazy(|_| { @@ -353,17 +290,6 @@ fn mpsc_blocking_start_send() { // until a oneshot is completed #[test] fn with_flush() { - use futures::channel::oneshot; - use futures::executor::block_on; - use futures::future::{self, FutureExt, TryFutureExt}; - use futures::never::Never; - use futures::sink::{Sink, SinkExt}; - use std::mem; - use std::pin::Pin; - - use flag_cx::flag_cx; - use unwrap::unwrap; - let (tx, rx) = oneshot::channel(); let mut block = rx.boxed(); let mut sink = Vec::new().with(|elem| { @@ -390,11 +316,6 @@ fn with_flush() { // test simple use of with to change data #[test] fn with_as_map() { - use futures::executor::block_on; - use futures::future; - use futures::never::Never; - use futures::sink::SinkExt; - let mut sink = Vec::new().with(|item| future::ok::(item * 2)); block_on(sink.send(0)).unwrap(); block_on(sink.send(1)).unwrap(); @@ -405,10 +326,6 @@ fn with_as_map() { // test simple use of with_flat_map #[test] fn with_flat_map() { - use futures::executor::block_on; - use futures::sink::SinkExt; - use futures::stream::{self, StreamExt}; - let mut sink = Vec::new().with_flat_map(|item| stream::iter(vec![item; item]).map(Ok)); block_on(sink.send(0)).unwrap(); block_on(sink.send(1)).unwrap(); @@ -421,16 +338,6 @@ fn with_flat_map() { // Regression test for the issue #1834. #[test] fn with_propagates_poll_ready() { - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::future; - use futures::sink::{Sink, SinkExt}; - use futures::task::Poll; - use std::pin::Pin; - - use flag_cx::flag_cx; - use sassert_next::sassert_next; - let (tx, mut rx) = mpsc::channel::(0); let mut tx = tx.with(|item: i32| future::ok::(item + 10)); @@ -457,14 +364,6 @@ fn with_propagates_poll_ready() { // but doesn't claim to be flushed until the underlying sink is #[test] fn with_flush_propagate() { - use futures::future::{self, FutureExt}; - use futures::sink::{Sink, SinkExt}; - use std::pin::Pin; - - use manual_flush::ManualFlush; - use flag_cx::flag_cx; - use unwrap::unwrap; - let mut sink = ManualFlush::new().with(future::ok::, ()>); flag_cx(|flag, cx| { unwrap(Pin::new(&mut sink).poll_ready(cx)); @@ -486,21 +385,13 @@ fn with_flush_propagate() { // test that `Clone` is implemented on `with` sinks #[test] fn with_implements_clone() { - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::future; - use futures::{SinkExt, StreamExt}; - let (mut tx, rx) = mpsc::channel(5); { - let mut is_positive = tx - .clone() - .with(|item| future::ok::(item > 0)); + let mut is_positive = tx.clone().with(|item| future::ok::(item > 0)); - let mut is_long = tx - .clone() - .with(|item: &str| future::ok::(item.len() > 5)); + let mut is_long = + tx.clone().with(|item: &str| future::ok::(item.len() > 5)); block_on(is_positive.clone().send(-1)).unwrap(); block_on(is_long.clone().send("123456")).unwrap(); @@ -512,18 +403,12 @@ fn with_implements_clone() { block_on(tx.close()).unwrap(); - assert_eq!( - block_on(rx.collect::>()), - vec![false, true, false, true, false] - ); + assert_eq!(block_on(rx.collect::>()), vec![false, true, false, true, false]); } // test that a buffer is a no-nop around a sink that always accepts sends #[test] fn buffer_noop() { - use futures::executor::block_on; - use futures::sink::SinkExt; - let mut sink = Vec::new().buffer(0); block_on(sink.send(0)).unwrap(); block_on(sink.send(1)).unwrap(); @@ -539,15 +424,6 @@ fn buffer_noop() { // and writing out when the underlying sink is ready #[test] fn buffer() { - use futures::executor::block_on; - use futures::future::FutureExt; - use futures::sink::SinkExt; - - use start_send_fut::StartSendFut; - use flag_cx::flag_cx; - use unwrap::unwrap; - use allowance::manual_allow; - let (sink, allow) = manual_allow::(); let sink = sink.buffer(2); @@ -567,10 +443,6 @@ fn buffer() { #[test] fn fanout_smoke() { - use futures::executor::block_on; - use futures::sink::SinkExt; - use futures::stream::{self, StreamExt}; - let sink1 = Vec::new(); let sink2 = Vec::new(); let mut sink = sink1.fanout(sink2); @@ -582,16 +454,6 @@ fn fanout_smoke() { #[test] fn fanout_backpressure() { - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::future::FutureExt; - use futures::sink::SinkExt; - use futures::stream::StreamExt; - - use start_send_fut::StartSendFut; - use flag_cx::flag_cx; - use unwrap::unwrap; - let (left_send, mut left_recv) = mpsc::channel(0); let (right_send, mut right_recv) = mpsc::channel(0); let sink = left_send.fanout(right_send); @@ -624,12 +486,6 @@ fn fanout_backpressure() { #[test] fn sink_map_err() { - use futures::channel::mpsc; - use futures::sink::{Sink, SinkExt}; - use futures::task::Poll; - use futures_test::task::panic_context; - use std::pin::Pin; - { let cx = &mut panic_context(); let (tx, _rx) = mpsc::channel(1); @@ -639,20 +495,11 @@ fn sink_map_err() { } let tx = mpsc::channel(0).0; - assert_eq!( - Pin::new(&mut tx.sink_map_err(|_| ())).start_send(()), - Err(()) - ); + assert_eq!(Pin::new(&mut tx.sink_map_err(|_| ())).start_send(()), Err(())); } #[test] fn sink_unfold() { - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::future::poll_fn; - use futures::sink::{self, Sink, SinkExt}; - use futures::task::Poll; - block_on(poll_fn(|cx| { let (tx, mut rx) = mpsc::channel(1); let unfold = sink::unfold((), |(), i: i32| { @@ -685,14 +532,8 @@ fn sink_unfold() { #[test] fn err_into() { - use futures::channel::mpsc; - use futures::sink::{Sink, SinkErrInto, SinkExt}; - use futures::task::Poll; - use futures_test::task::panic_context; - use std::pin::Pin; - #[derive(Copy, Clone, Debug, PartialEq, Eq)] - pub struct ErrIntoTest; + struct ErrIntoTest; impl From for ErrIntoTest { fn from(_: mpsc::SendError) -> Self { @@ -709,8 +550,5 @@ fn err_into() { } let tx = mpsc::channel(0).0; - assert_eq!( - Pin::new(&mut tx.sink_err_into()).start_send(()), - Err(ErrIntoTest) - ); + assert_eq!(Pin::new(&mut tx.sink_err_into()).start_send(()), Err(ErrIntoTest)); } diff --git a/futures/tests/sink_fanout.rs b/futures/tests/sink_fanout.rs index 7d1fa43790..e57b2d8c7b 100644 --- a/futures/tests/sink_fanout.rs +++ b/futures/tests/sink_fanout.rs @@ -1,11 +1,11 @@ +use futures::channel::mpsc; +use futures::executor::block_on; +use futures::future::join3; +use futures::sink::SinkExt; +use futures::stream::{self, StreamExt}; + #[test] fn it_works() { - use futures::channel::mpsc; - use futures::executor::block_on; - use futures::future::join3; - use futures::sink::SinkExt; - use futures::stream::{self, StreamExt}; - let (tx1, rx1) = mpsc::channel(1); let (tx2, rx2) = mpsc::channel(2); let tx = tx1.fanout(tx2).sink_map_err(|_| ()); diff --git a/futures/tests/split.rs b/futures/tests/split.rs deleted file mode 100644 index 86c2fc6b82..0000000000 --- a/futures/tests/split.rs +++ /dev/null @@ -1,75 +0,0 @@ -#[test] -fn test_split() { - use futures::executor::block_on; - use futures::sink::{Sink, SinkExt}; - use futures::stream::{self, Stream, StreamExt}; - use futures::task::{Context, Poll}; - use pin_project::pin_project; - use std::pin::Pin; - - #[pin_project] - struct Join { - #[pin] - stream: T, - #[pin] - sink: U, - } - - impl Stream for Join { - type Item = T::Item; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.project().stream.poll_next(cx) - } - } - - impl, Item> Sink for Join { - type Error = U::Error; - - fn poll_ready( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.project().sink.poll_ready(cx) - } - - fn start_send( - self: Pin<&mut Self>, - item: Item, - ) -> Result<(), Self::Error> { - self.project().sink.start_send(item) - } - - fn poll_flush( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.project().sink.poll_flush(cx) - } - - fn poll_close( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - self.project().sink.poll_close(cx) - } - } - - let mut dest: Vec = Vec::new(); - { - let join = Join { - stream: stream::iter(vec![10, 20, 30]), - sink: &mut dest - }; - - let (sink, stream) = join.split(); - let join = sink.reunite(stream).expect("test_split: reunite error"); - let (mut sink, stream) = join.split(); - let mut stream = stream.map(Ok); - block_on(sink.send_all(&mut stream)).unwrap(); - } - assert_eq!(dest, vec![10, 20, 30]); -} diff --git a/futures/tests/stream.rs b/futures/tests/stream.rs index 14b283db7a..3122b587d0 100644 --- a/futures/tests/stream.rs +++ b/futures/tests/stream.rs @@ -1,8 +1,24 @@ +use std::cell::Cell; +use std::iter; +use std::pin::Pin; +use std::rc::Rc; +use std::sync::Arc; +use std::task::Context; + +use futures::channel::mpsc; +use futures::executor::block_on; +use futures::future::{self, Future}; +use futures::lock::Mutex; +use futures::sink::SinkExt; +use futures::stream::{self, StreamExt}; +use futures::task::Poll; +use futures::{ready, FutureExt}; +use futures_core::Stream; +use futures_executor::ThreadPool; +use futures_test::task::noop_context; + #[test] fn select() { - use futures::executor::block_on; - use futures::stream::{self, StreamExt}; - fn select_and_compare(a: Vec, b: Vec, expected: Vec) { let a = stream::iter(a); let b = stream::iter(b); @@ -17,19 +33,12 @@ fn select() { #[test] fn flat_map() { - use futures::stream::{self, StreamExt}; - - futures::executor::block_on(async { - let st = stream::iter(vec![ - stream::iter(0..=4u8), - stream::iter(6..=10), - stream::iter(0..=2), - ]); - - let values: Vec<_> = st - .flat_map(|s| s.filter(|v| futures::future::ready(v % 2 == 0))) - .collect() - .await; + block_on(async { + let st = + stream::iter(vec![stream::iter(0..=4u8), stream::iter(6..=10), stream::iter(0..=2)]); + + let values: Vec<_> = + st.flat_map(|s| s.filter(|v| futures::future::ready(v % 2 == 0))).collect().await; assert_eq!(values, vec![0, 2, 4, 6, 8, 10, 0, 2]); }); @@ -37,28 +46,360 @@ fn flat_map() { #[test] fn scan() { - use futures::stream::{self, StreamExt}; - - futures::executor::block_on(async { - assert_eq!( - stream::iter(vec![1u8, 2, 3, 4, 6, 8, 2]) - .scan(1, |state, e| { - *state += 1; - futures::future::ready(if e < *state { Some(e) } else { None }) - }) + block_on(async { + let values = stream::iter(vec![1u8, 2, 3, 4, 6, 8, 2]) + .scan(1, |state, e| { + *state += 1; + futures::future::ready(if e < *state { Some(e) } else { None }) + }) + .collect::>() + .await; + + assert_eq!(values, vec![1u8, 2, 3, 4]); + }); +} + +#[test] +fn flatten_unordered() { + use futures::executor::block_on; + use futures::stream::*; + use futures::task::*; + use std::convert::identity; + use std::pin::Pin; + use std::sync::atomic::{AtomicBool, Ordering}; + use std::thread; + use std::time::Duration; + + struct DataStream { + data: Vec, + polled: bool, + wake_immediately: bool, + } + + impl Stream for DataStream { + type Item = u8; + + fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { + if !self.polled { + if !self.wake_immediately { + let waker = ctx.waker().clone(); + let sleep_time = + Duration::from_millis(*self.data.first().unwrap_or(&0) as u64 / 10); + thread::spawn(move || { + thread::sleep(sleep_time); + waker.wake_by_ref(); + }); + } else { + ctx.waker().wake_by_ref(); + } + self.polled = true; + Poll::Pending + } else { + self.polled = false; + Poll::Ready(self.data.pop()) + } + } + } + + struct Interchanger { + polled: bool, + base: u8, + wake_immediately: bool, + } + + impl Stream for Interchanger { + type Item = DataStream; + + fn poll_next(mut self: Pin<&mut Self>, ctx: &mut Context<'_>) -> Poll> { + if !self.polled { + self.polled = true; + if !self.wake_immediately { + let waker = ctx.waker().clone(); + let sleep_time = Duration::from_millis(self.base as u64); + thread::spawn(move || { + thread::sleep(sleep_time); + waker.wake_by_ref(); + }); + } else { + ctx.waker().wake_by_ref(); + } + Poll::Pending + } else { + let data: Vec<_> = (0..6).rev().map(|v| v + self.base * 6).collect(); + self.base += 1; + self.polled = false; + Poll::Ready(Some(DataStream { + polled: false, + data, + wake_immediately: self.wake_immediately && self.base % 2 == 0, + })) + } + } + } + + // basic behaviour + { + block_on(async { + let st = stream::iter(vec![ + stream::iter(0..=4u8), + stream::iter(6..=10), + stream::iter(10..=12), + ]); + + let fl_unordered = st.flatten_unordered(3).collect::>().await; + + assert_eq!(fl_unordered, vec![0, 6, 10, 1, 7, 11, 2, 8, 12, 3, 9, 4, 10]); + }); + + block_on(async { + let st = stream::iter(vec![ + stream::iter(0..=4u8), + stream::iter(6..=10), + stream::iter(0..=2), + ]); + + let mut fm_unordered = st + .flat_map_unordered(1, |s| s.filter(|v| futures::future::ready(v % 2 == 0))) .collect::>() - .await, - vec![1u8, 2, 3, 4] + .await; + + fm_unordered.sort_unstable(); + + assert_eq!(fm_unordered, vec![0, 0, 2, 2, 4, 6, 8, 10]); + }); + } + + // wake up immediately + { + block_on(async { + let mut fl_unordered = Interchanger { polled: false, base: 0, wake_immediately: true } + .take(10) + .map(|s| s.map(identity)) + .flatten_unordered(10) + .collect::>() + .await; + + fl_unordered.sort_unstable(); + + assert_eq!(fl_unordered, (0..60).collect::>()); + }); + + block_on(async { + let mut fm_unordered = Interchanger { polled: false, base: 0, wake_immediately: true } + .take(10) + .flat_map_unordered(10, |s| s.map(identity)) + .collect::>() + .await; + + fm_unordered.sort_unstable(); + + assert_eq!(fm_unordered, (0..60).collect::>()); + }); + } + + // wake up after delay + { + block_on(async { + let mut fl_unordered = Interchanger { polled: false, base: 0, wake_immediately: false } + .take(10) + .map(|s| s.map(identity)) + .flatten_unordered(10) + .collect::>() + .await; + + fl_unordered.sort_unstable(); + + assert_eq!(fl_unordered, (0..60).collect::>()); + }); + + block_on(async { + let mut fm_unordered = Interchanger { polled: false, base: 0, wake_immediately: false } + .take(10) + .flat_map_unordered(10, |s| s.map(identity)) + .collect::>() + .await; + + fm_unordered.sort_unstable(); + + assert_eq!(fm_unordered, (0..60).collect::>()); + }); + + block_on(async { + let (mut fm_unordered, mut fl_unordered) = futures_util::join!( + Interchanger { polled: false, base: 0, wake_immediately: false } + .take(10) + .flat_map_unordered(10, |s| s.map(identity)) + .collect::>(), + Interchanger { polled: false, base: 0, wake_immediately: false } + .take(10) + .map(|s| s.map(identity)) + .flatten_unordered(10) + .collect::>() + ); + + fm_unordered.sort_unstable(); + fl_unordered.sort_unstable(); + + assert_eq!(fm_unordered, fl_unordered); + assert_eq!(fm_unordered, (0..60).collect::>()); + }); + } + + // waker panics + { + let stream = Arc::new(Mutex::new( + Interchanger { polled: false, base: 0, wake_immediately: true } + .take(10) + .flat_map_unordered(10, |s| s.map(identity)), + )); + + struct PanicWaker; + + impl ArcWake for PanicWaker { + fn wake_by_ref(_arc_self: &Arc) { + panic!("WAKE UP"); + } + } + + std::thread::spawn({ + let stream = stream.clone(); + move || { + let mut st = poll_fn(|cx| { + let mut lock = ready!(stream.lock().poll_unpin(cx)); + + let panic_waker = waker(Arc::new(PanicWaker)); + let mut panic_cx = Context::from_waker(&panic_waker); + let _ = ready!(lock.poll_next_unpin(&mut panic_cx)); + + Poll::Ready(Some(())) + }); + + block_on(st.next()) + } + }) + .join() + .unwrap_err(); + + block_on(async move { + let mut values: Vec<_> = stream.lock().await.by_ref().collect().await; + values.sort_unstable(); + + assert_eq!(values, (0..60).collect::>()); + }); + } + + // stream panics + { + let st = stream::iter(iter::once( + once(Box::pin(async { panic!("Polled") })).left_stream::(), + )) + .chain( + Interchanger { polled: false, base: 0, wake_immediately: true } + .map(|stream| stream.right_stream()) + .take(10), ); - }); + + let stream = Arc::new(Mutex::new(st.flatten_unordered(10))); + + std::thread::spawn({ + let stream = stream.clone(); + move || { + let mut st = poll_fn(|cx| { + let mut lock = ready!(stream.lock().poll_unpin(cx)); + let data = ready!(lock.poll_next_unpin(cx)); + + Poll::Ready(data) + }); + + block_on(st.next()) + } + }) + .join() + .unwrap_err(); + + block_on(async move { + let mut values: Vec<_> = stream.lock().await.by_ref().collect().await; + values.sort_unstable(); + + assert_eq!(values, (0..60).collect::>()); + }); + } + + fn timeout(time: Duration, value: I) -> impl Future { + let ready = Arc::new(AtomicBool::new(false)); + let mut spawned = false; + + future::poll_fn(move |cx| { + if !spawned { + let waker = cx.waker().clone(); + let ready = ready.clone(); + + std::thread::spawn(move || { + std::thread::sleep(time); + ready.store(true, Ordering::Release); + + waker.wake_by_ref() + }); + spawned = true; + } + + if ready.load(Ordering::Acquire) { + Poll::Ready(value.clone()) + } else { + Poll::Pending + } + }) + } + + fn build_nested_fu(st: S) -> impl Stream + Unpin + where + S::Item: Clone, + { + let inner = st + .then(|item| timeout(Duration::from_millis(50), item)) + .enumerate() + .map(|(idx, value)| { + stream::once(if idx % 2 == 0 { + future::ready(value).left_future() + } else { + timeout(Duration::from_millis(100), value).right_future() + }) + }) + .flatten_unordered(None); + + stream::once(future::ready(inner)).flatten_unordered(None) + } + + // nested `flatten_unordered` + let te = ThreadPool::new().unwrap(); + let base_handle = te + .spawn_with_handle(async move { + let fu = build_nested_fu(stream::iter(1..=10)); + + assert_eq!(fu.count().await, 10); + }) + .unwrap(); + + block_on(base_handle); + + let empty_state_move_handle = te + .spawn_with_handle(async move { + let mut fu = build_nested_fu(stream::iter(1..10)); + { + let mut cx = noop_context(); + let _ = fu.poll_next_unpin(&mut cx); + let _ = fu.poll_next_unpin(&mut cx); + } + + assert_eq!(fu.count().await, 9); + }) + .unwrap(); + + block_on(empty_state_move_handle); } #[test] fn take_until() { - use futures::future::{self, Future}; - use futures::stream::{self, StreamExt}; - use futures::task::Poll; - fn make_stop_fut(stop_on: u32) -> impl Future { let mut i = 0; future::poll_fn(move |_cx| { @@ -71,7 +412,7 @@ fn take_until() { }) } - futures::executor::block_on(async { + block_on(async { // Verify stopping works: let stream = stream::iter(1u32..=10); let stop_fut = make_stop_fut(5); @@ -123,10 +464,15 @@ fn take_until() { #[test] #[should_panic] -fn ready_chunks_panic_on_cap_zero() { - use futures::channel::mpsc; - use futures::stream::StreamExt; +fn chunks_panic_on_cap_zero() { + let (_, rx1) = mpsc::channel::<()>(1); + + let _ = rx1.chunks(0); +} +#[test] +#[should_panic] +fn ready_chunks_panic_on_cap_zero() { let (_, rx1) = mpsc::channel::<()>(1); let _ = rx1.ready_chunks(0); @@ -134,12 +480,6 @@ fn ready_chunks_panic_on_cap_zero() { #[test] fn ready_chunks() { - use futures::channel::mpsc; - use futures::stream::StreamExt; - use futures::sink::SinkExt; - use futures::FutureExt; - use futures_test::task::noop_context; - let (mut tx, rx1) = mpsc::channel::(16); let mut s = rx1.ready_chunks(2); @@ -147,14 +487,91 @@ fn ready_chunks() { let mut cx = noop_context(); assert!(s.next().poll_unpin(&mut cx).is_pending()); - futures::executor::block_on(async { + block_on(async { tx.send(1).await.unwrap(); assert_eq!(s.next().await.unwrap(), vec![1]); tx.send(2).await.unwrap(); tx.send(3).await.unwrap(); tx.send(4).await.unwrap(); - assert_eq!(s.next().await.unwrap(), vec![2,3]); + assert_eq!(s.next().await.unwrap(), vec![2, 3]); assert_eq!(s.next().await.unwrap(), vec![4]); }); } + +struct SlowStream { + times_should_poll: usize, + times_polled: Rc>, +} +impl Stream for SlowStream { + type Item = usize; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.times_polled.set(self.times_polled.get() + 1); + if self.times_polled.get() % 2 == 0 { + cx.waker().wake_by_ref(); + return Poll::Pending; + } + if self.times_polled.get() >= self.times_should_poll { + return Poll::Ready(None); + } + Poll::Ready(Some(self.times_polled.get())) + } +} + +#[test] +fn select_with_strategy_doesnt_terminate_early() { + for side in [stream::PollNext::Left, stream::PollNext::Right] { + let times_should_poll = 10; + let count = Rc::new(Cell::new(0)); + let b = stream::iter([10, 20]); + + let mut selected = stream::select_with_strategy( + SlowStream { times_should_poll, times_polled: count.clone() }, + b, + |_: &mut ()| side, + ); + block_on(async move { while selected.next().await.is_some() {} }); + assert_eq!(count.get(), times_should_poll + 1); + } +} + +async fn is_even(number: u8) -> bool { + number % 2 == 0 +} + +#[test] +fn all() { + block_on(async { + let empty: [u8; 0] = []; + let st = stream::iter(empty); + let all = st.all(is_even).await; + assert!(all); + + let st = stream::iter([2, 4, 6, 8]); + let all = st.all(is_even).await; + assert!(all); + + let st = stream::iter([2, 3, 4]); + let all = st.all(is_even).await; + assert!(!all); + }); +} + +#[test] +fn any() { + block_on(async { + let empty: [u8; 0] = []; + let st = stream::iter(empty); + let any = st.any(is_even).await; + assert!(!any); + + let st = stream::iter([1, 2, 3]); + let any = st.any(is_even).await; + assert!(any); + + let st = stream::iter([1, 3, 5]); + let any = st.any(is_even).await; + assert!(!any); + }); +} diff --git a/futures/tests/stream_abortable.rs b/futures/tests/stream_abortable.rs new file mode 100644 index 0000000000..2339dd0522 --- /dev/null +++ b/futures/tests/stream_abortable.rs @@ -0,0 +1,46 @@ +use futures::channel::mpsc; +use futures::executor::block_on; +use futures::stream::{abortable, Stream, StreamExt}; +use futures::task::{Context, Poll}; +use futures::SinkExt; +use futures_test::task::new_count_waker; +use std::pin::Pin; + +#[test] +fn abortable_works() { + let (_tx, a_rx) = mpsc::channel::<()>(1); + let (mut abortable_rx, abort_handle) = abortable(a_rx); + + abort_handle.abort(); + assert!(abortable_rx.is_aborted()); + assert_eq!(None, block_on(abortable_rx.next())); +} + +#[test] +fn abortable_awakens() { + let (_tx, a_rx) = mpsc::channel::<()>(1); + let (mut abortable_rx, abort_handle) = abortable(a_rx); + + let (waker, counter) = new_count_waker(); + let mut cx = Context::from_waker(&waker); + + assert_eq!(counter, 0); + assert_eq!(Poll::Pending, Pin::new(&mut abortable_rx).poll_next(&mut cx)); + assert_eq!(counter, 0); + + abort_handle.abort(); + assert_eq!(counter, 1); + assert!(abortable_rx.is_aborted()); + assert_eq!(Poll::Ready(None), Pin::new(&mut abortable_rx).poll_next(&mut cx)); +} + +#[test] +fn abortable_resolves() { + let (mut tx, a_rx) = mpsc::channel::<()>(1); + let (mut abortable_rx, _abort_handle) = abortable(a_rx); + + block_on(tx.send(())).unwrap(); + + assert!(!abortable_rx.is_aborted()); + assert_eq!(Some(()), block_on(abortable_rx.next())); +} diff --git a/futures/tests/buffer_unordered.rs b/futures/tests/stream_buffer_unordered.rs similarity index 89% rename from futures/tests/buffer_unordered.rs rename to futures/tests/stream_buffer_unordered.rs index 5c8b8bf7e5..9a2ee174ed 100644 --- a/futures/tests/buffer_unordered.rs +++ b/futures/tests/stream_buffer_unordered.rs @@ -1,13 +1,13 @@ +use futures::channel::{mpsc, oneshot}; +use futures::executor::{block_on, block_on_stream}; +use futures::sink::SinkExt; +use futures::stream::StreamExt; +use std::sync::mpsc as std_mpsc; +use std::thread; + #[test] #[ignore] // FIXME: https://github.com/rust-lang/futures-rs/issues/1790 fn works() { - use futures::channel::{oneshot, mpsc}; - use futures::executor::{block_on, block_on_stream}; - use futures::sink::SinkExt; - use futures::stream::StreamExt; - use std::sync::mpsc as std_mpsc; - use std::thread; - const N: usize = 4; let (mut tx, rx) = mpsc::channel(1); diff --git a/futures/tests/stream_catch_unwind.rs b/futures/tests/stream_catch_unwind.rs index 272558cc61..8b23a0a7ef 100644 --- a/futures/tests/stream_catch_unwind.rs +++ b/futures/tests/stream_catch_unwind.rs @@ -1,8 +1,8 @@ +use futures::executor::block_on_stream; +use futures::stream::{self, StreamExt}; + #[test] fn panic_in_the_middle_of_the_stream() { - use futures::executor::block_on_stream; - use futures::stream::{self, StreamExt}; - let stream = stream::iter(vec![Some(10), None, Some(11)]); // panic on second element @@ -16,9 +16,6 @@ fn panic_in_the_middle_of_the_stream() { #[test] fn no_panic() { - use futures::executor::block_on_stream; - use futures::stream::{self, StreamExt}; - let stream = stream::iter(vec![10, 11, 12]); let mut iter = block_on_stream(stream.catch_unwind()); diff --git a/futures/tests/stream_futures_ordered.rs b/futures/tests/stream_futures_ordered.rs new file mode 100644 index 0000000000..f06ce263eb --- /dev/null +++ b/futures/tests/stream_futures_ordered.rs @@ -0,0 +1,172 @@ +use futures::channel::oneshot; +use futures::executor::{block_on, block_on_stream}; +use futures::future::{self, join, Future, FutureExt, TryFutureExt}; +use futures::stream::{FuturesOrdered, StreamExt}; +use futures::task::Poll; +use futures_test::task::noop_context; +use std::any::Any; + +#[test] +fn works_1() { + let (a_tx, a_rx) = oneshot::channel::(); + let (b_tx, b_rx) = oneshot::channel::(); + let (c_tx, c_rx) = oneshot::channel::(); + + let mut stream = vec![a_rx, b_rx, c_rx].into_iter().collect::>(); + + b_tx.send(99).unwrap(); + assert!(stream.poll_next_unpin(&mut noop_context()).is_pending()); + + a_tx.send(33).unwrap(); + c_tx.send(33).unwrap(); + + let mut iter = block_on_stream(stream); + assert_eq!(Some(Ok(33)), iter.next()); + assert_eq!(Some(Ok(99)), iter.next()); + assert_eq!(Some(Ok(33)), iter.next()); + assert_eq!(None, iter.next()); +} + +#[test] +fn works_2() { + let (a_tx, a_rx) = oneshot::channel::(); + let (b_tx, b_rx) = oneshot::channel::(); + let (c_tx, c_rx) = oneshot::channel::(); + + let mut stream = vec![a_rx.boxed(), join(b_rx, c_rx).map(|(a, b)| Ok(a? + b?)).boxed()] + .into_iter() + .collect::>(); + + let mut cx = noop_context(); + a_tx.send(33).unwrap(); + b_tx.send(33).unwrap(); + assert!(stream.poll_next_unpin(&mut cx).is_ready()); + assert!(stream.poll_next_unpin(&mut cx).is_pending()); + c_tx.send(33).unwrap(); + assert!(stream.poll_next_unpin(&mut cx).is_ready()); +} + +#[test] +fn test_push_front() { + let (a_tx, a_rx) = oneshot::channel::(); + let (b_tx, b_rx) = oneshot::channel::(); + let (c_tx, c_rx) = oneshot::channel::(); + let (d_tx, d_rx) = oneshot::channel::(); + + let mut stream = FuturesOrdered::new(); + + let mut cx = noop_context(); + + stream.push_back(a_rx); + stream.push_back(b_rx); + stream.push_back(c_rx); + + a_tx.send(1).unwrap(); + b_tx.send(2).unwrap(); + c_tx.send(3).unwrap(); + + // 1 and 2 should be received in order + assert_eq!(Poll::Ready(Some(Ok(1))), stream.poll_next_unpin(&mut cx)); + assert_eq!(Poll::Ready(Some(Ok(2))), stream.poll_next_unpin(&mut cx)); + + stream.push_front(d_rx); + d_tx.send(4).unwrap(); + + // we pushed `d_rx` to the front and sent 4, so we should receive 4 next + // and then 3 after it + assert_eq!(Poll::Ready(Some(Ok(4))), stream.poll_next_unpin(&mut cx)); + assert_eq!(Poll::Ready(Some(Ok(3))), stream.poll_next_unpin(&mut cx)); +} + +#[test] +fn test_push_back() { + let (a_tx, a_rx) = oneshot::channel::(); + let (b_tx, b_rx) = oneshot::channel::(); + let (c_tx, c_rx) = oneshot::channel::(); + let (d_tx, d_rx) = oneshot::channel::(); + + let mut stream = FuturesOrdered::new(); + + let mut cx = noop_context(); + + stream.push_back(a_rx); + stream.push_back(b_rx); + stream.push_back(c_rx); + + a_tx.send(1).unwrap(); + b_tx.send(2).unwrap(); + c_tx.send(3).unwrap(); + + // All results should be received in order + + assert_eq!(Poll::Ready(Some(Ok(1))), stream.poll_next_unpin(&mut cx)); + assert_eq!(Poll::Ready(Some(Ok(2))), stream.poll_next_unpin(&mut cx)); + + stream.push_back(d_rx); + d_tx.send(4).unwrap(); + + assert_eq!(Poll::Ready(Some(Ok(3))), stream.poll_next_unpin(&mut cx)); + assert_eq!(Poll::Ready(Some(Ok(4))), stream.poll_next_unpin(&mut cx)); +} + +#[test] +fn from_iterator() { + let stream = vec![future::ready::(1), future::ready::(2), future::ready::(3)] + .into_iter() + .collect::>(); + assert_eq!(stream.len(), 3); + assert_eq!(block_on(stream.collect::>()), vec![1, 2, 3]); +} + +#[test] +fn queue_never_unblocked() { + let (_a_tx, a_rx) = oneshot::channel::>(); + let (b_tx, b_rx) = oneshot::channel::>(); + let (c_tx, c_rx) = oneshot::channel::>(); + + let mut stream = vec![ + Box::new(a_rx) as Box + Unpin>, + Box::new( + future::try_select(b_rx, c_rx) + .map_err(|e| e.factor_first().0) + .and_then(|e| future::ok(Box::new(e) as Box)), + ) as _, + ] + .into_iter() + .collect::>(); + + let cx = &mut noop_context(); + for _ in 0..10 { + assert!(stream.poll_next_unpin(cx).is_pending()); + } + + b_tx.send(Box::new(())).unwrap(); + assert!(stream.poll_next_unpin(cx).is_pending()); + c_tx.send(Box::new(())).unwrap(); + assert!(stream.poll_next_unpin(cx).is_pending()); + assert!(stream.poll_next_unpin(cx).is_pending()); +} + +#[test] +fn test_push_front_negative() { + let (a_tx, a_rx) = oneshot::channel::(); + let (b_tx, b_rx) = oneshot::channel::(); + let (c_tx, c_rx) = oneshot::channel::(); + + let mut stream = FuturesOrdered::new(); + + let mut cx = noop_context(); + + stream.push_front(a_rx); + stream.push_front(b_rx); + stream.push_front(c_rx); + + a_tx.send(1).unwrap(); + b_tx.send(2).unwrap(); + c_tx.send(3).unwrap(); + + // These should all be received in reverse order + assert_eq!(Poll::Ready(Some(Ok(3))), stream.poll_next_unpin(&mut cx)); + assert_eq!(Poll::Ready(Some(Ok(2))), stream.poll_next_unpin(&mut cx)); + assert_eq!(Poll::Ready(Some(Ok(1))), stream.poll_next_unpin(&mut cx)); +} diff --git a/futures/tests/futures_unordered.rs b/futures/tests/stream_futures_unordered.rs similarity index 62% rename from futures/tests/futures_unordered.rs rename to futures/tests/stream_futures_unordered.rs index ac0817e079..e25e755cde 100644 --- a/futures/tests/futures_unordered.rs +++ b/futures/tests/stream_futures_unordered.rs @@ -1,17 +1,17 @@ -use futures::future::Future; -use futures::stream::{FuturesUnordered, StreamExt}; +use futures::channel::oneshot; +use futures::executor::{block_on, block_on_stream}; +use futures::future::{self, join, Future, FutureExt}; +use futures::stream::{FusedStream, FuturesUnordered, StreamExt}; use futures::task::{Context, Poll}; +use futures_test::future::FutureTestExt; use futures_test::task::noop_context; +use futures_test::{assert_stream_done, assert_stream_next, assert_stream_pending}; use std::iter::FromIterator; use std::pin::Pin; +use std::sync::atomic::{AtomicBool, Ordering}; #[test] fn is_terminated() { - use futures::future; - use futures::stream::{FusedStream, FuturesUnordered, StreamExt}; - use futures::task::Poll; - use futures_test::task::noop_context; - let mut cx = noop_context(); let mut tasks = FuturesUnordered::new(); @@ -39,19 +39,12 @@ fn is_terminated() { #[test] fn works_1() { - use futures::channel::oneshot; - use futures::executor::block_on_stream; - use futures::stream::FuturesUnordered; - let (a_tx, a_rx) = oneshot::channel::(); let (b_tx, b_rx) = oneshot::channel::(); let (c_tx, c_rx) = oneshot::channel::(); - let mut iter = block_on_stream( - vec![a_rx, b_rx, c_rx] - .into_iter() - .collect::>(), - ); + let mut iter = + block_on_stream(vec![a_rx, b_rx, c_rx].into_iter().collect::>()); b_tx.send(99).unwrap(); assert_eq!(Some(Ok(99)), iter.next()); @@ -65,22 +58,13 @@ fn works_1() { #[test] fn works_2() { - use futures::channel::oneshot; - use futures::future::{join, FutureExt}; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures::task::Poll; - use futures_test::task::noop_context; - let (a_tx, a_rx) = oneshot::channel::(); let (b_tx, b_rx) = oneshot::channel::(); let (c_tx, c_rx) = oneshot::channel::(); - let mut stream = vec![ - a_rx.boxed(), - join(b_rx, c_rx).map(|(a, b)| Ok(a? + b?)).boxed(), - ] - .into_iter() - .collect::>(); + let mut stream = vec![a_rx.boxed(), join(b_rx, c_rx).map(|(a, b)| Ok(a? + b?)).boxed()] + .into_iter() + .collect::>(); a_tx.send(9).unwrap(); b_tx.send(10).unwrap(); @@ -94,29 +78,15 @@ fn works_2() { #[test] fn from_iterator() { - use futures::executor::block_on; - use futures::future; - use futures::stream::{FuturesUnordered, StreamExt}; - - let stream = vec![ - future::ready::(1), - future::ready::(2), - future::ready::(3), - ] - .into_iter() - .collect::>(); + let stream = vec![future::ready::(1), future::ready::(2), future::ready::(3)] + .into_iter() + .collect::>(); assert_eq!(stream.len(), 3); assert_eq!(block_on(stream.collect::>()), vec![1, 2, 3]); } #[test] fn finished_future() { - use std::marker::Unpin; - use futures::channel::oneshot; - use futures::future::{self, Future, FutureExt}; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures_test::task::noop_context; - let (_a_tx, a_rx) = oneshot::channel::(); let (b_tx, b_rx) = oneshot::channel::(); let (c_tx, c_rx) = oneshot::channel::(); @@ -142,17 +112,11 @@ fn finished_future() { #[test] fn iter_mut_cancel() { - use futures::channel::oneshot; - use futures::executor::block_on_stream; - use futures::stream::FuturesUnordered; - let (a_tx, a_rx) = oneshot::channel::(); let (b_tx, b_rx) = oneshot::channel::(); let (c_tx, c_rx) = oneshot::channel::(); - let mut stream = vec![a_rx, b_rx, c_rx] - .into_iter() - .collect::>(); + let mut stream = vec![a_rx, b_rx, c_rx].into_iter().collect::>(); for rx in stream.iter_mut() { rx.close(); @@ -172,16 +136,10 @@ fn iter_mut_cancel() { #[test] fn iter_mut_len() { - use futures::future; - use futures::stream::FuturesUnordered; - - let mut stream = vec![ - future::pending::<()>(), - future::pending::<()>(), - future::pending::<()>(), - ] - .into_iter() - .collect::>(); + let mut stream = + vec![future::pending::<()>(), future::pending::<()>(), future::pending::<()>()] + .into_iter() + .collect::>(); let mut iter_mut = stream.iter_mut(); assert_eq!(iter_mut.len(), 3); @@ -196,15 +154,6 @@ fn iter_mut_len() { #[test] fn iter_cancel() { - use std::marker::Unpin; - use std::pin::Pin; - use std::sync::atomic::{AtomicBool, Ordering}; - - use futures::executor::block_on_stream; - use futures::future::{self, Future, FutureExt}; - use futures::stream::FuturesUnordered; - use futures::task::{Context, Poll}; - struct AtomicCancel { future: F, cancel: AtomicBool, @@ -250,16 +199,9 @@ fn iter_cancel() { #[test] fn iter_len() { - use futures::future; - use futures::stream::FuturesUnordered; - - let stream = vec![ - future::pending::<()>(), - future::pending::<()>(), - future::pending::<()>(), - ] - .into_iter() - .collect::>(); + let stream = vec![future::pending::<()>(), future::pending::<()>(), future::pending::<()>()] + .into_iter() + .collect::>(); let mut iter = stream.iter(); assert_eq!(iter.len(), 3); @@ -273,12 +215,66 @@ fn iter_len() { } #[test] -fn futures_not_moved_after_poll() { - use futures::future; - use futures::stream::FuturesUnordered; - use futures_test::future::FutureTestExt; - use futures_test::{assert_stream_done, assert_stream_next, assert_stream_pending}; +fn into_iter_cancel() { + let (a_tx, a_rx) = oneshot::channel::(); + let (b_tx, b_rx) = oneshot::channel::(); + let (c_tx, c_rx) = oneshot::channel::(); + + let stream = vec![a_rx, b_rx, c_rx].into_iter().collect::>(); + + let stream = stream + .into_iter() + .map(|mut rx| { + rx.close(); + rx + }) + .collect::>(); + + let mut iter = block_on_stream(stream); + assert!(a_tx.is_canceled()); + assert!(b_tx.is_canceled()); + assert!(c_tx.is_canceled()); + + assert_eq!(iter.next(), Some(Err(futures::channel::oneshot::Canceled))); + assert_eq!(iter.next(), Some(Err(futures::channel::oneshot::Canceled))); + assert_eq!(iter.next(), Some(Err(futures::channel::oneshot::Canceled))); + assert_eq!(iter.next(), None); +} + +#[test] +fn into_iter_len() { + let stream = vec![future::pending::<()>(), future::pending::<()>(), future::pending::<()>()] + .into_iter() + .collect::>(); + + let mut into_iter = stream.into_iter(); + assert_eq!(into_iter.len(), 3); + assert!(into_iter.next().is_some()); + assert_eq!(into_iter.len(), 2); + assert!(into_iter.next().is_some()); + assert_eq!(into_iter.len(), 1); + assert!(into_iter.next().is_some()); + assert_eq!(into_iter.len(), 0); + assert!(into_iter.next().is_none()); +} + +#[test] +fn into_iter_partial() { + let stream = vec![future::ready(1), future::ready(2), future::ready(3), future::ready(4)] + .into_iter() + .collect::>(); + + let mut into_iter = stream.into_iter(); + assert!(into_iter.next().is_some()); + assert!(into_iter.next().is_some()); + assert!(into_iter.next().is_some()); + assert_eq!(into_iter.len(), 1); + // don't panic when iterator is dropped before completing +} + +#[test] +fn futures_not_moved_after_poll() { // Future that will be ready after being polled twice, // asserting that it does not move. let fut = future::ready(()).pending_once().assert_unmoved(); @@ -292,11 +288,6 @@ fn futures_not_moved_after_poll() { #[test] fn len_valid_during_out_of_order_completion() { - use futures::channel::oneshot; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures::task::Poll; - use futures_test::task::noop_context; - // Complete futures out-of-order and add new futures afterwards to ensure // length values remain correct. let (a_tx, a_rx) = oneshot::channel::(); @@ -345,7 +336,7 @@ fn polled_only_once_at_most_per_iteration() { impl Future for F { type Output = (); - fn poll(mut self: Pin<&mut Self>, _: &mut Context) -> Poll { + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { if self.polled { panic!("polled twice") } else { @@ -368,3 +359,73 @@ fn polled_only_once_at_most_per_iteration() { let mut tasks = FuturesUnordered::::new(); assert_eq!(Poll::Ready(None), tasks.poll_next_unpin(cx)); } + +#[test] +fn clear() { + let mut tasks = FuturesUnordered::from_iter(vec![future::ready(1), future::ready(2)]); + + assert_eq!(block_on(tasks.next()), Some(1)); + assert!(!tasks.is_empty()); + + tasks.clear(); + assert!(tasks.is_empty()); + + tasks.push(future::ready(3)); + assert!(!tasks.is_empty()); + + tasks.clear(); + assert!(tasks.is_empty()); + + assert_eq!(block_on(tasks.next()), None); + assert!(tasks.is_terminated()); + tasks.clear(); + assert!(!tasks.is_terminated()); +} + +// https://github.com/rust-lang/futures-rs/issues/2529#issuecomment-997290279 +#[test] +fn clear_in_loop() { + const N: usize = + if cfg!(miri) || option_env!("QEMU_LD_PREFIX").is_some() { 100 } else { 10_000 }; + futures::executor::block_on(async { + async fn task() { + let (s, r) = oneshot::channel(); + std::thread::spawn(|| { + std::thread::sleep(std::time::Duration::from_micros(100)); + let _ = s.send(()); + }); + r.await.unwrap() + } + let mut futures = FuturesUnordered::new(); + for _ in 0..N { + for _ in 0..24 { + futures.push(task()); + } + let _ = futures.next().await; + futures.clear(); + } + }); +} + +// https://github.com/rust-lang/futures-rs/issues/2863#issuecomment-2219441515 +#[test] +#[should_panic] +fn panic_on_drop_fut() { + struct BadFuture; + + impl Drop for BadFuture { + fn drop(&mut self) { + panic!() + } + } + + impl Future for BadFuture { + type Output = (); + + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { + Poll::Pending + } + } + + FuturesUnordered::default().push(BadFuture); +} diff --git a/futures/tests/stream_into_async_read.rs b/futures/tests/stream_into_async_read.rs index 222c985706..60188d3e58 100644 --- a/futures/tests/stream_into_async_read.rs +++ b/futures/tests/stream_into_async_read.rs @@ -1,31 +1,51 @@ -#[test] -fn test_into_async_read() { - use core::pin::Pin; - use futures::io::AsyncRead; - use futures::stream::{self, TryStreamExt}; - use futures::task::Poll; - use futures_test::{task::noop_context, stream::StreamTestExt}; - - macro_rules! assert_read { - ($reader:expr, $buf:expr, $item:expr) => { - let mut cx = noop_context(); - loop { - match Pin::new(&mut $reader).poll_read(&mut cx, $buf) { - Poll::Ready(Ok(x)) => { - assert_eq!(x, $item); - break; - } - Poll::Ready(Err(err)) => { - panic!("assertion failed: expected value but got {}", err); - } - Poll::Pending => { - continue; - } +use core::pin::Pin; +use futures::io::{AsyncBufRead, AsyncRead}; +use futures::stream::{self, TryStreamExt}; +use futures::task::Poll; +use futures_test::{stream::StreamTestExt, task::noop_context}; + +macro_rules! assert_read { + ($reader:expr, $buf:expr, $item:expr) => { + let mut cx = noop_context(); + loop { + match Pin::new(&mut $reader).poll_read(&mut cx, $buf) { + Poll::Ready(Ok(x)) => { + assert_eq!(x, $item); + break; + } + Poll::Ready(Err(err)) => { + panic!("assertion failed: expected value but got {}", err); + } + Poll::Pending => { + continue; + } + } + } + }; +} + +macro_rules! assert_fill_buf { + ($reader:expr, $buf:expr) => { + let mut cx = noop_context(); + loop { + match Pin::new(&mut $reader).poll_fill_buf(&mut cx) { + Poll::Ready(Ok(x)) => { + assert_eq!(x, $buf); + break; + } + Poll::Ready(Err(err)) => { + panic!("assertion failed: expected value but got {}", err); + } + Poll::Pending => { + continue; } } - }; - } + } + }; +} +#[test] +fn test_into_async_read() { let stream = stream::iter((1..=3).flat_map(|_| vec![Ok(vec![]), Ok(vec![1, 2, 3, 4, 5])])); let mut reader = stream.interleave_pending().into_async_read(); let mut buf = vec![0; 3]; @@ -53,32 +73,6 @@ fn test_into_async_read() { #[test] fn test_into_async_bufread() { - use core::pin::Pin; - use futures::io::AsyncBufRead; - use futures::stream::{self, TryStreamExt}; - use futures::task::Poll; - use futures_test::{task::noop_context, stream::StreamTestExt}; - - macro_rules! assert_fill_buf { - ($reader:expr, $buf:expr) => { - let mut cx = noop_context(); - loop { - match Pin::new(&mut $reader).poll_fill_buf(&mut cx) { - Poll::Ready(Ok(x)) => { - assert_eq!(x, $buf); - break; - } - Poll::Ready(Err(err)) => { - panic!("assertion failed: expected value but got {}", err); - } - Poll::Pending => { - continue; - } - } - } - }; - } - let stream = stream::iter((1..=2).flat_map(|_| vec![Ok(vec![]), Ok(vec![1, 2, 3, 4, 5])])); let mut reader = stream.interleave_pending().into_async_read(); diff --git a/futures/tests/stream_peekable.rs b/futures/tests/stream_peekable.rs index 66a7385ae9..153fcc25b4 100644 --- a/futures/tests/stream_peekable.rs +++ b/futures/tests/stream_peekable.rs @@ -1,13 +1,58 @@ +use futures::executor::block_on; +use futures::pin_mut; +use futures::stream::{self, Peekable, StreamExt}; + #[test] fn peekable() { - use futures::executor::block_on; - use futures::pin_mut; - use futures::stream::{self, Peekable, StreamExt}; - block_on(async { let peekable: Peekable<_> = stream::iter(vec![1u8, 2, 3]).peekable(); pin_mut!(peekable); assert_eq!(peekable.as_mut().peek().await, Some(&1u8)); assert_eq!(peekable.collect::>().await, vec![1, 2, 3]); + + let s = stream::once(async { 1 }).peekable(); + pin_mut!(s); + assert_eq!(s.as_mut().peek().await, Some(&1u8)); + assert_eq!(s.collect::>().await, vec![1]); + }); +} + +#[test] +fn peekable_mut() { + block_on(async { + let s = stream::iter(vec![1u8, 2, 3]).peekable(); + pin_mut!(s); + if let Some(p) = s.as_mut().peek_mut().await { + if *p == 1 { + *p = 5; + } + } + assert_eq!(s.collect::>().await, vec![5, 2, 3]); + }); +} + +#[test] +fn peekable_next_if_eq() { + block_on(async { + // first, try on references + let s = stream::iter(vec!["Heart", "of", "Gold"]).peekable(); + pin_mut!(s); + // try before `peek()` + assert_eq!(s.as_mut().next_if_eq(&"trillian").await, None); + assert_eq!(s.as_mut().next_if_eq(&"Heart").await, Some("Heart")); + // try after peek() + assert_eq!(s.as_mut().peek().await, Some(&"of")); + assert_eq!(s.as_mut().next_if_eq(&"of").await, Some("of")); + assert_eq!(s.as_mut().next_if_eq(&"zaphod").await, None); + // make sure `next()` still behaves + assert_eq!(s.next().await, Some("Gold")); + + // make sure comparison works for owned values + let s = stream::iter(vec![String::from("Ludicrous"), "speed".into()]).peekable(); + pin_mut!(s); + // make sure basic functionality works + assert_eq!(s.as_mut().next_if_eq("Ludicrous").await, Some("Ludicrous".into())); + assert_eq!(s.as_mut().next_if_eq("speed").await, Some("speed".into())); + assert_eq!(s.as_mut().next_if_eq("").await, None); }); } diff --git a/futures/tests/stream_select_all.rs b/futures/tests/stream_select_all.rs index 6178412f4d..4ae0735762 100644 --- a/futures/tests/stream_select_all.rs +++ b/futures/tests/stream_select_all.rs @@ -1,10 +1,12 @@ +use futures::channel::mpsc; +use futures::executor::{block_on, block_on_stream}; +use futures::future::{self, FutureExt}; +use futures::stream::{self, select_all, FusedStream, SelectAll, StreamExt}; +use futures::task::Poll; +use futures_test::task::noop_context; + #[test] fn is_terminated() { - use futures::future::{self, FutureExt}; - use futures::stream::{FusedStream, SelectAll, StreamExt}; - use futures::task::Poll; - use futures_test::task::noop_context; - let mut cx = noop_context(); let mut tasks = SelectAll::new(); @@ -30,9 +32,6 @@ fn is_terminated() { #[test] fn issue_1626() { - use futures::executor::block_on_stream; - use futures::stream; - let a = stream::iter(0..=2); let b = stream::iter(10..=14); @@ -51,10 +50,6 @@ fn issue_1626() { #[test] fn works_1() { - use futures::channel::mpsc; - use futures::executor::block_on_stream; - use futures::stream::select_all; - let (a_tx, a_rx) = mpsc::unbounded::(); let (b_tx, b_rx) = mpsc::unbounded::(); let (c_tx, c_rx) = mpsc::unbounded::(); @@ -81,3 +76,122 @@ fn works_1() { drop((a_tx, b_tx, c_tx)); assert_eq!(None, stream.next()); } + +#[test] +fn clear() { + let mut tasks = + select_all(vec![stream::iter(vec![1].into_iter()), stream::iter(vec![2].into_iter())]); + + assert_eq!(block_on(tasks.next()), Some(1)); + assert!(!tasks.is_empty()); + + tasks.clear(); + assert!(tasks.is_empty()); + + tasks.push(stream::iter(vec![3].into_iter())); + assert!(!tasks.is_empty()); + + tasks.clear(); + assert!(tasks.is_empty()); + + assert_eq!(block_on(tasks.next()), None); + assert!(tasks.is_terminated()); + tasks.clear(); + assert!(!tasks.is_terminated()); +} + +#[test] +fn iter_mut() { + let mut stream = + vec![stream::pending::<()>(), stream::pending::<()>(), stream::pending::<()>()] + .into_iter() + .collect::>(); + + let mut iter = stream.iter_mut(); + assert_eq!(iter.len(), 3); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 2); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 1); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); + + let mut stream = vec![stream::iter(vec![]), stream::iter(vec![1]), stream::iter(vec![2])] + .into_iter() + .collect::>(); + + assert_eq!(stream.len(), 3); + assert_eq!(block_on(stream.next()), Some(1)); + assert_eq!(stream.len(), 2); + let mut iter = stream.iter_mut(); + assert_eq!(iter.len(), 2); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 1); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); + + assert_eq!(block_on(stream.next()), Some(2)); + assert_eq!(stream.len(), 2); + assert_eq!(block_on(stream.next()), None); + let mut iter = stream.iter_mut(); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); +} + +#[test] +fn iter() { + let stream = vec![stream::pending::<()>(), stream::pending::<()>(), stream::pending::<()>()] + .into_iter() + .collect::>(); + + let mut iter = stream.iter(); + assert_eq!(iter.len(), 3); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 2); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 1); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); + + let mut stream = vec![stream::iter(vec![]), stream::iter(vec![1]), stream::iter(vec![2])] + .into_iter() + .collect::>(); + + assert_eq!(stream.len(), 3); + assert_eq!(block_on(stream.next()), Some(1)); + assert_eq!(stream.len(), 2); + let mut iter = stream.iter(); + assert_eq!(iter.len(), 2); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 1); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); + + assert_eq!(block_on(stream.next()), Some(2)); + assert_eq!(stream.len(), 2); + assert_eq!(block_on(stream.next()), None); + let mut iter = stream.iter(); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); +} + +#[test] +fn into_iter() { + let stream = vec![stream::pending::<()>(), stream::pending::<()>(), stream::pending::<()>()] + .into_iter() + .collect::>(); + + let mut iter = stream.into_iter(); + assert_eq!(iter.len(), 3); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 2); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 1); + assert!(iter.next().is_some()); + assert_eq!(iter.len(), 0); + assert!(iter.next().is_none()); +} diff --git a/futures/tests/stream_select_next_some.rs b/futures/tests/stream_select_next_some.rs index bec5262c1d..8252ad7b54 100644 --- a/futures/tests/stream_select_next_some.rs +++ b/futures/tests/stream_select_next_some.rs @@ -1,11 +1,13 @@ +use futures::executor::block_on; +use futures::future::{self, FusedFuture, FutureExt}; +use futures::select; +use futures::stream::{FuturesUnordered, StreamExt}; +use futures::task::{Context, Poll}; +use futures_test::future::FutureTestExt; +use futures_test::task::new_count_waker; + #[test] fn is_terminated() { - use futures::future; - use futures::future::{FusedFuture, FutureExt}; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures::task::{Context, Poll}; - use futures_test::task::new_count_waker; - let (waker, counter) = new_count_waker(); let mut cx = Context::from_waker(&waker); @@ -30,15 +32,11 @@ fn is_terminated() { #[test] fn select() { - use futures::{future, select}; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures_test::future::FutureTestExt; - // Checks that even though `async_tasks` will yield a `None` and return // `is_terminated() == true` during the first poll, it manages to toggle // back to having items after a future is pushed into it during the second // poll (after pending_once completes). - futures::executor::block_on(async { + block_on(async { let mut fut = future::ready(1).pending_once(); let mut async_tasks = FuturesUnordered::new(); let mut total = 0; @@ -61,17 +59,13 @@ fn select() { // Check that `select!` macro does not fail when importing from `futures_util`. #[test] fn futures_util_select() { - use futures::future; - use futures::stream::{FuturesUnordered, StreamExt}; - use futures_test::future::FutureTestExt; - use futures_util::select; // Checks that even though `async_tasks` will yield a `None` and return // `is_terminated() == true` during the first poll, it manages to toggle // back to having items after a future is pushed into it during the second // poll (after pending_once completes). - futures::executor::block_on(async { + block_on(async { let mut fut = future::ready(1).pending_once(); let mut async_tasks = FuturesUnordered::new(); let mut total = 0; diff --git a/futures/tests/stream_split.rs b/futures/tests/stream_split.rs new file mode 100644 index 0000000000..694c151807 --- /dev/null +++ b/futures/tests/stream_split.rs @@ -0,0 +1,57 @@ +use futures::executor::block_on; +use futures::sink::{Sink, SinkExt}; +use futures::stream::{self, Stream, StreamExt}; +use futures::task::{Context, Poll}; +use pin_project::pin_project; +use std::pin::Pin; + +#[test] +fn test_split() { + #[pin_project] + struct Join { + #[pin] + stream: T, + #[pin] + sink: U, + } + + impl Stream for Join { + type Item = T::Item; + + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().stream.poll_next(cx) + } + } + + impl, Item> Sink for Join { + type Error = U::Error; + + fn poll_ready(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().sink.poll_ready(cx) + } + + fn start_send(self: Pin<&mut Self>, item: Item) -> Result<(), Self::Error> { + self.project().sink.start_send(item) + } + + fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().sink.poll_flush(cx) + } + + fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { + self.project().sink.poll_close(cx) + } + } + + let mut dest: Vec = Vec::new(); + { + let join = Join { stream: stream::iter(vec![10, 20, 30]), sink: &mut dest }; + + let (sink, stream) = join.split(); + let join = sink.reunite(stream).expect("test_split: reunite error"); + let (mut sink, stream) = join.split(); + let mut stream = stream.map(Ok); + block_on(sink.send_all(&mut stream)).unwrap(); + } + assert_eq!(dest, vec![10, 20, 30]); +} diff --git a/futures/tests/stream_try_stream.rs b/futures/tests/stream_try_stream.rs new file mode 100644 index 0000000000..57bdd4f3f3 --- /dev/null +++ b/futures/tests/stream_try_stream.rs @@ -0,0 +1,183 @@ +use core::pin::Pin; +use std::convert::Infallible; + +use futures::{ + stream::{self, repeat, Repeat, StreamExt, TryStreamExt}, + task::Poll, + Stream, +}; +use futures_executor::block_on; +use futures_task::Context; +use futures_test::task::noop_context; + +#[test] +fn try_filter_map_after_err() { + let cx = &mut noop_context(); + let mut s = stream::iter(1..=3) + .map(Ok) + .try_filter_map(|v| async move { Err::, _>(v) }) + .filter_map(|r| async move { r.ok() }) + .boxed(); + assert_eq!(Poll::Ready(None), s.poll_next_unpin(cx)); +} + +#[test] +fn try_skip_while_after_err() { + let cx = &mut noop_context(); + let mut s = stream::iter(1..=3) + .map(Ok) + .try_skip_while(|_| async move { Err::<_, ()>(()) }) + .filter_map(|r| async move { r.ok() }) + .boxed(); + assert_eq!(Poll::Ready(None), s.poll_next_unpin(cx)); +} + +#[test] +fn try_take_while_after_err() { + let cx = &mut noop_context(); + let mut s = stream::iter(1..=3) + .map(Ok) + .try_take_while(|_| async move { Err::<_, ()>(()) }) + .filter_map(|r| async move { r.ok() }) + .boxed(); + assert_eq!(Poll::Ready(None), s.poll_next_unpin(cx)); +} + +#[test] +fn try_flatten_unordered() { + let test_st = stream::iter(1..7) + .map(|val: u32| { + if val % 2 == 0 { + Ok(stream::unfold((val, 1), |(val, pow)| async move { + Some((val.pow(pow), (val, pow + 1))) + }) + .take(3) + .map(move |val| if val % 16 != 0 { Ok(val) } else { Err(val) })) + } else { + Err(val) + } + }) + .map_ok(Box::pin) + .try_flatten_unordered(None); + + block_on(async move { + assert_eq!( + // All numbers can be divided by 16 and odds must be `Err` + // For all basic evens we must have powers from 1 to 3 + vec![ + Err(1), + Err(3), + Err(5), + Ok(2), + Ok(4), + Ok(6), + Ok(4), + Err(16), + Ok(36), + Ok(8), + Err(64), + Ok(216) + ], + test_st.collect::>().await + ) + }); + + #[derive(Clone, Debug)] + struct ErrorStream { + error_after: usize, + polled: usize, + } + + impl Stream for ErrorStream { + type Item = Result>, ()>; + + fn poll_next(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + if self.polled > self.error_after { + panic!("Polled after error"); + } else { + let out = + if self.polled == self.error_after { Err(()) } else { Ok(repeat(Ok(()))) }; + self.polled += 1; + Poll::Ready(Some(out)) + } + } + } + + block_on(async move { + let mut st = ErrorStream { error_after: 3, polled: 0 }.try_flatten_unordered(None); + let mut ctr = 0; + while (st.try_next().await).is_ok() { + ctr += 1; + } + assert_eq!(ctr, 0); + + assert_eq!( + ErrorStream { error_after: 10, polled: 0 } + .try_flatten_unordered(None) + .inspect_ok(|_| panic!("Unexpected `Ok`")) + .try_collect::>() + .await, + Err(()) + ); + + let mut taken = 0; + assert_eq!( + ErrorStream { error_after: 10, polled: 0 } + .map_ok(|st| st.take(3)) + .try_flatten_unordered(1) + .inspect(|_| taken += 1) + .try_fold((), |(), res| async move { Ok(res) }) + .await, + Err(()) + ); + assert_eq!(taken, 31); + }) +} + +async fn is_even(number: u8) -> bool { + number % 2 == 0 +} + +#[test] +fn try_all() { + block_on(async { + let empty: [Result; 0] = []; + let st = stream::iter(empty); + let all = st.try_all(is_even).await; + assert_eq!(Ok(true), all); + + let st = stream::iter([Ok::<_, Infallible>(2), Ok(4), Ok(6), Ok(8)]); + let all = st.try_all(is_even).await; + assert_eq!(Ok(true), all); + + let st = stream::iter([Ok::<_, Infallible>(2), Ok(3), Ok(4)]); + let all = st.try_all(is_even).await; + assert_eq!(Ok(false), all); + + let st = stream::iter([Ok(2), Ok(4), Err("err"), Ok(8)]); + let all = st.try_all(is_even).await; + assert_eq!(Err("err"), all); + }); +} + +#[test] +fn try_any() { + block_on(async { + let empty: [Result; 0] = []; + let st = stream::iter(empty); + let any = st.try_any(is_even).await; + assert_eq!(Ok(false), any); + + let st = stream::iter([Ok::<_, Infallible>(1), Ok(2), Ok(3)]); + let any = st.try_any(is_even).await; + assert_eq!(Ok(true), any); + + let st = stream::iter([Ok::<_, Infallible>(1), Ok(3), Ok(5)]); + let any = st.try_any(is_even).await; + assert_eq!(Ok(false), any); + + let st = stream::iter([Ok(1), Ok(3), Err("err"), Ok(8)]); + let any = st.try_any(is_even).await; + assert_eq!(Err("err"), any); + }); +} diff --git a/futures/tests/unfold.rs b/futures/tests/stream_unfold.rs similarity index 89% rename from futures/tests/unfold.rs rename to futures/tests/stream_unfold.rs index 95722cf8a6..16b10813b1 100644 --- a/futures/tests/unfold.rs +++ b/futures/tests/stream_unfold.rs @@ -1,10 +1,7 @@ use futures::future; use futures::stream; - use futures_test::future::FutureTestExt; -use futures_test::{ - assert_stream_done, assert_stream_next, assert_stream_pending, -}; +use futures_test::{assert_stream_done, assert_stream_next, assert_stream_pending}; #[test] fn unfold1() { diff --git a/futures/tests/task_arc_wake.rs b/futures/tests/task_arc_wake.rs new file mode 100644 index 0000000000..aedc15bcb8 --- /dev/null +++ b/futures/tests/task_arc_wake.rs @@ -0,0 +1,79 @@ +use futures::task::{self, ArcWake, Waker}; +use std::panic; +use std::sync::{Arc, Mutex}; + +struct CountingWaker { + nr_wake: Mutex, +} + +impl CountingWaker { + fn new() -> Self { + Self { nr_wake: Mutex::new(0) } + } + + fn wakes(&self) -> i32 { + *self.nr_wake.lock().unwrap() + } +} + +impl ArcWake for CountingWaker { + fn wake_by_ref(arc_self: &Arc) { + let mut lock = arc_self.nr_wake.lock().unwrap(); + *lock += 1; + } +} + +#[test] +fn create_from_arc() { + let some_w = Arc::new(CountingWaker::new()); + + let w1: Waker = task::waker(some_w.clone()); + assert_eq!(2, Arc::strong_count(&some_w)); + w1.wake_by_ref(); + assert_eq!(1, some_w.wakes()); + + let w2 = w1.clone(); + assert_eq!(3, Arc::strong_count(&some_w)); + + w2.wake_by_ref(); + assert_eq!(2, some_w.wakes()); + + drop(w2); + assert_eq!(2, Arc::strong_count(&some_w)); + drop(w1); + assert_eq!(1, Arc::strong_count(&some_w)); +} + +#[test] +fn ref_wake_same() { + let some_w = Arc::new(CountingWaker::new()); + + let w1: Waker = task::waker(some_w.clone()); + let w2 = task::waker_ref(&some_w); + let w3 = w2.clone(); + + assert!(w1.will_wake(&w2)); + assert!(w2.will_wake(&w3)); +} + +#[test] +fn proper_refcount_on_wake_panic() { + struct PanicWaker; + + impl ArcWake for PanicWaker { + fn wake_by_ref(_arc_self: &Arc) { + panic!("WAKE UP"); + } + } + + let some_w = Arc::new(PanicWaker); + + let w1: Waker = task::waker(some_w.clone()); + assert_eq!( + "WAKE UP", + *panic::catch_unwind(|| w1.wake_by_ref()).unwrap_err().downcast::<&str>().unwrap() + ); + assert_eq!(2, Arc::strong_count(&some_w)); // some_w + w1 + drop(w1); + assert_eq!(1, Arc::strong_count(&some_w)); // some_w +} diff --git a/futures/tests/atomic_waker.rs b/futures/tests/task_atomic_waker.rs similarity index 82% rename from futures/tests/atomic_waker.rs rename to futures/tests/task_atomic_waker.rs index bf15d0f0c7..cec3db2876 100644 --- a/futures/tests/atomic_waker.rs +++ b/futures/tests/task_atomic_waker.rs @@ -1,14 +1,13 @@ +use futures::executor::block_on; +use futures::future::poll_fn; +use futures::task::{AtomicWaker, Poll}; +use std::sync::atomic::AtomicUsize; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::thread; + #[test] fn basic() { - use std::sync::atomic::AtomicUsize; - use std::sync::atomic::Ordering; - use std::sync::Arc; - use std::thread; - - use futures::executor::block_on; - use futures::future::poll_fn; - use futures::task::{AtomicWaker, Poll}; - let atomic_waker = Arc::new(AtomicWaker::new()); let atomic_waker_copy = atomic_waker.clone(); diff --git a/futures/tests/test_macro.rs b/futures/tests/test_macro.rs new file mode 100644 index 0000000000..6adf51d8bb --- /dev/null +++ b/futures/tests/test_macro.rs @@ -0,0 +1,20 @@ +#[futures_test::test] +async fn it_works() { + let fut = async { true }; + assert!(fut.await); + + let fut = async { false }; + assert!(!fut.await); +} + +#[should_panic] +#[futures_test::test] +async fn it_is_being_run() { + let fut = async { false }; + assert!(fut.await); +} + +#[futures_test::test] +async fn return_ty() -> Result<(), ()> { + Ok(()) +} diff --git a/futures/tests/try_join.rs b/futures/tests/try_join.rs index 6c6d0843d5..0281ab897d 100644 --- a/futures/tests/try_join.rs +++ b/futures/tests/try_join.rs @@ -1,9 +1,9 @@ #![deny(unreachable_code)] -use futures::{try_join, executor::block_on}; +use futures::{executor::block_on, try_join}; // TODO: This abuses https://github.com/rust-lang/rust/issues/58733 in order to -// test behaviour of the `try_join!` macro with the never type before it is +// test behavior of the `try_join!` macro with the never type before it is // stabilized. Once `!` is again stabilized this can be removed and replaced // with direct use of `!` below where `Never` is used. trait MyTrait { @@ -14,7 +14,6 @@ impl MyTrait for fn() -> T { } type Never = ! as MyTrait>::Output; - #[test] fn try_join_never_error() { block_on(async { diff --git a/futures/tests/try_join_all.rs b/futures/tests/try_join_all.rs deleted file mode 100644 index 8e579a2800..0000000000 --- a/futures/tests/try_join_all.rs +++ /dev/null @@ -1,58 +0,0 @@ -mod util { - use std::future::Future; - use futures::executor::block_on; - use std::fmt::Debug; - - pub fn assert_done(actual_fut: F, expected: T) - where - T: PartialEq + Debug, - F: FnOnce() -> Box + Unpin>, - { - let output = block_on(actual_fut()); - assert_eq!(output, expected); - } -} - -#[test] -fn collect_collects() { - use futures_util::future::{err, ok, try_join_all}; - - use util::assert_done; - - assert_done(|| Box::new(try_join_all(vec![ok(1), ok(2)])), Ok::<_, usize>(vec![1, 2])); - assert_done(|| Box::new(try_join_all(vec![ok(1), err(2)])), Err(2)); - assert_done(|| Box::new(try_join_all(vec![ok(1)])), Ok::<_, usize>(vec![1])); - // REVIEW: should this be implemented? - // assert_done(|| Box::new(try_join_all(Vec::::new())), Ok(vec![])); - - // TODO: needs more tests -} - -#[test] -fn try_join_all_iter_lifetime() { - use futures_util::future::{ok, try_join_all}; - use std::future::Future; - - use util::assert_done; - - // In futures-rs version 0.1, this function would fail to typecheck due to an overly - // conservative type parameterization of `TryJoinAll`. - fn sizes(bufs: Vec<&[u8]>) -> Box, ()>> + Unpin> { - let iter = bufs.into_iter().map(|b| ok::(b.len())); - Box::new(try_join_all(iter)) - } - - assert_done(|| sizes(vec![&[1,2,3], &[], &[0]]), Ok(vec![3_usize, 0, 1])); -} - -#[test] -fn try_join_all_from_iter() { - use futures_util::future::{ok, TryJoinAll}; - - use util::assert_done; - - assert_done( - || Box::new(vec![ok(1), ok(2)].into_iter().collect::>()), - Ok::<_, usize>(vec![1, 2]), - ) -} diff --git a/futures/tests/try_stream.rs b/futures/tests/try_stream.rs deleted file mode 100644 index 194e74db74..0000000000 --- a/futures/tests/try_stream.rs +++ /dev/null @@ -1,38 +0,0 @@ -use futures::{ - stream::{self, StreamExt, TryStreamExt}, - task::Poll, -}; -use futures_test::task::noop_context; - -#[test] -fn try_filter_map_after_err() { - let cx = &mut noop_context(); - let mut s = stream::iter(1..=3) - .map(Ok) - .try_filter_map(|v| async move { Err::, _>(v) }) - .filter_map(|r| async move { r.ok() }) - .boxed(); - assert_eq!(Poll::Ready(None), s.poll_next_unpin(cx)); -} - -#[test] -fn try_skip_while_after_err() { - let cx = &mut noop_context(); - let mut s = stream::iter(1..=3) - .map(Ok) - .try_skip_while(|_| async move { Err::<_, ()>(()) }) - .filter_map(|r| async move { r.ok() }) - .boxed(); - assert_eq!(Poll::Ready(None), s.poll_next_unpin(cx)); -} - -#[test] -fn try_take_while_after_err() { - let cx = &mut noop_context(); - let mut s = stream::iter(1..=3) - .map(Ok) - .try_take_while(|_| async move { Err::<_, ()>(()) }) - .filter_map(|r| async move { r.ok() }) - .boxed(); - assert_eq!(Poll::Ready(None), s.poll_next_unpin(cx)); -} diff --git a/futures/tests_disabled/all.rs b/futures/tests_disabled/all.rs index 6c7e11cf7b..a7a571040a 100644 --- a/futures/tests_disabled/all.rs +++ b/futures/tests_disabled/all.rs @@ -1,27 +1,27 @@ -use futures::future; -use futures::executor::block_on; use futures::channel::oneshot::{self, Canceled}; +use futures::executor::block_on; +use futures::future; use std::sync::mpsc::{channel, TryRecvError}; -mod support; -use support::*; +// mod support; +// use support::*; fn unselect(r: Result, Either<(E, B), (E, A)>>) -> Result { match r { - Ok(Either::Left((t, _))) | - Ok(Either::Right((t, _))) => Ok(t), - Err(Either::Left((e, _))) | - Err(Either::Right((e, _))) => Err(e), + Ok(Either::Left((t, _))) | Ok(Either::Right((t, _))) => Ok(t), + Err(Either::Left((e, _))) | Err(Either::Right((e, _))) => Err(e), } } #[test] fn result_smoke() { fn is_future_v(_: C) - where A: Send + 'static, - B: Send + 'static, - C: Future - {} + where + A: Send + 'static, + B: Send + 'static, + C: Future, + { + } is_future_v::(f_ok(1).map(|a| a + 1)); is_future_v::(f_ok(1).map_err(|a| a + 1)); @@ -64,7 +64,9 @@ fn result_smoke() { #[test] fn test_empty() { - fn empty() -> Empty { future::empty() } + fn empty() -> Empty { + future::empty() + } assert_empty(|| empty()); assert_empty(|| empty().select(empty())); @@ -105,16 +107,22 @@ fn flatten() { #[test] fn smoke_oneshot() { - assert_done(|| { - let (c, p) = oneshot::channel(); - c.send(1).unwrap(); - p - }, Ok(1)); - assert_done(|| { - let (c, p) = oneshot::channel::(); - drop(c); - p - }, Err(Canceled)); + assert_done( + || { + let (c, p) = oneshot::channel(); + c.send(1).unwrap(); + p + }, + Ok(1), + ); + assert_done( + || { + let (c, p) = oneshot::channel::(); + drop(c); + p + }, + Err(Canceled), + ); let mut completes = Vec::new(); assert_empty(|| { let (a, b) = oneshot::channel::(); @@ -129,9 +137,7 @@ fn smoke_oneshot() { let (c, p) = oneshot::channel::(); drop(c); let (tx, rx) = channel(); - p.then(move |_| { - tx.send(()) - }).forget(); + p.then(move |_| tx.send(())).forget(); rx.recv().unwrap(); } @@ -139,8 +145,14 @@ fn smoke_oneshot() { fn select_cancels() { let ((a, b), (c, d)) = (oneshot::channel::(), oneshot::channel::()); let ((btx, brx), (dtx, drx)) = (channel(), channel()); - let b = b.map(move |b| { btx.send(b).unwrap(); b }); - let d = d.map(move |d| { dtx.send(d).unwrap(); d }); + let b = b.map(move |b| { + btx.send(b).unwrap(); + b + }); + let d = d.map(move |d| { + dtx.send(d).unwrap(); + d + }); let mut f = b.select(d).then(unselect); // assert!(f.poll(&mut Task::new()).is_pending()); @@ -156,8 +168,14 @@ fn select_cancels() { let ((a, b), (c, d)) = (oneshot::channel::(), oneshot::channel::()); let ((btx, _brx), (dtx, drx)) = (channel(), channel()); - let b = b.map(move |b| { btx.send(b).unwrap(); b }); - let d = d.map(move |d| { dtx.send(d).unwrap(); d }); + let b = b.map(move |b| { + btx.send(b).unwrap(); + b + }); + let d = d.map(move |d| { + dtx.send(d).unwrap(); + d + }); let mut f = b.select(d).then(unselect); assert!(f.poll(lw).ok().unwrap().is_pending()); @@ -173,8 +191,14 @@ fn select_cancels() { fn join_cancels() { let ((a, b), (c, d)) = (oneshot::channel::(), oneshot::channel::()); let ((btx, _brx), (dtx, drx)) = (channel(), channel()); - let b = b.map(move |b| { btx.send(b).unwrap(); b }); - let d = d.map(move |d| { dtx.send(d).unwrap(); d }); + let b = b.map(move |b| { + btx.send(b).unwrap(); + b + }); + let d = d.map(move |d| { + dtx.send(d).unwrap(); + d + }); let mut f = b.join(d); drop(a); @@ -185,8 +209,14 @@ fn join_cancels() { let ((a, b), (c, d)) = (oneshot::channel::(), oneshot::channel::()); let ((btx, _brx), (dtx, drx)) = (channel(), channel()); - let b = b.map(move |b| { btx.send(b).unwrap(); b }); - let d = d.map(move |d| { dtx.send(d).unwrap(); d }); + let b = b.map(move |b| { + btx.send(b).unwrap(); + b + }); + let d = d.map(move |d| { + dtx.send(d).unwrap(); + d + }); let (tx, rx) = channel(); let f = b.join(d); @@ -194,7 +224,8 @@ fn join_cancels() { tx.send(()).unwrap(); let res: Result<(), ()> = Ok(()); res - }).forget(); + }) + .forget(); assert!(rx.try_recv().is_err()); drop(a); rx.recv().unwrap(); @@ -243,7 +274,6 @@ fn join_incomplete() { }) } - #[test] fn select2() { assert_done(|| f_ok(2).select(empty()).then(unselect), Ok(2)); @@ -251,14 +281,15 @@ fn select2() { assert_done(|| f_err(2).select(empty()).then(unselect), Err(2)); assert_done(|| empty().select(f_err(2)).then(unselect), Err(2)); - assert_done(|| { - f_ok(1).select(f_ok(2)) - .map_err(|_| 0) - .and_then(|either_tup| { - let (a, b) = either_tup.into_inner(); - b.map(move |b| a + b) - }) - }, Ok(3)); + assert_done( + || { + f_ok(1).select(f_ok(2)).map_err(|_| 0).and_then(|either_tup| { + let (a, b) = either_tup.into_inner(); + b.map(move |b| a + b) + }) + }, + Ok(3), + ); // Finish one half of a select and then fail the second, ensuring that we // get the notification of the second one. @@ -297,8 +328,14 @@ fn select2() { { let ((_a, b), (_c, d)) = (oneshot::channel::(), oneshot::channel::()); let ((btx, brx), (dtx, drx)) = (channel(), channel()); - let b = b.map(move |v| { btx.send(v).unwrap(); v }); - let d = d.map(move |v| { dtx.send(v).unwrap(); v }); + let b = b.map(move |v| { + btx.send(v).unwrap(); + v + }); + let d = d.map(move |v| { + dtx.send(v).unwrap(); + v + }); let f = b.select(d); drop(f); assert!(drx.recv().is_err()); @@ -309,8 +346,14 @@ fn select2() { { let ((_a, b), (_c, d)) = (oneshot::channel::(), oneshot::channel::()); let ((btx, brx), (dtx, drx)) = (channel(), channel()); - let b = b.map(move |v| { btx.send(v).unwrap(); v }); - let d = d.map(move |v| { dtx.send(v).unwrap(); v }); + let b = b.map(move |v| { + btx.send(v).unwrap(); + v + }); + let d = d.map(move |v| { + dtx.send(v).unwrap(); + v + }); let mut f = b.select(d); let _res = noop_waker_lw(|lw| f.poll(lw)); drop(f); @@ -322,8 +365,14 @@ fn select2() { { let ((a, b), (_c, d)) = (oneshot::channel::(), oneshot::channel::()); let ((btx, brx), (dtx, drx)) = (channel(), channel()); - let b = b.map(move |v| { btx.send(v).unwrap(); v }); - let d = d.map(move |v| { dtx.send(v).unwrap(); v }); + let b = b.map(move |v| { + btx.send(v).unwrap(); + v + }); + let d = d.map(move |v| { + dtx.send(v).unwrap(); + v + }); let (tx, rx) = channel(); b.select(d).map(move |_| tx.send(()).unwrap()).forget(); drop(a); diff --git a/futures/tests_disabled/stream.rs b/futures/tests_disabled/stream.rs index ef0676db35..a4eec2c7aa 100644 --- a/futures/tests_disabled/stream.rs +++ b/futures/tests_disabled/stream.rs @@ -1,26 +1,26 @@ +use futures::channel::mpsc; +use futures::channel::oneshot; use futures::executor::{block_on, block_on_stream}; use futures::future::{err, ok}; use futures::stream::{empty, iter_ok, poll_fn, Peekable}; -use futures::channel::oneshot; -use futures::channel::mpsc; -mod support; -use support::*; +// mod support; +// use support::*; pub struct Iter { iter: I, } pub fn iter(i: J) -> Iter - where J: IntoIterator>, +where + J: IntoIterator>, { - Iter { - iter: i.into_iter(), - } + Iter { iter: i.into_iter() } } impl Stream for Iter - where I: Iterator>, +where + I: Iterator>, { type Item = T; type Error = E; @@ -34,21 +34,15 @@ impl Stream for Iter } } -fn list() -> Box + Send> { +fn list() -> Box + Send> { let (tx, rx) = mpsc::channel(1); - tx.send(Ok(1)) - .and_then(|tx| tx.send(Ok(2))) - .and_then(|tx| tx.send(Ok(3))) - .forget(); + tx.send(Ok(1)).and_then(|tx| tx.send(Ok(2))).and_then(|tx| tx.send(Ok(3))).forget(); Box::new(rx.then(|r| r.unwrap())) } -fn err_list() -> Box + Send> { +fn err_list() -> Box + Send> { let (tx, rx) = mpsc::channel(1); - tx.send(Ok(1)) - .and_then(|tx| tx.send(Ok(2))) - .and_then(|tx| tx.send(Err(3))) - .forget(); + tx.send(Ok(1)).and_then(|tx| tx.send(Ok(2))).and_then(|tx| tx.send(Err(3))).forget(); Box::new(rx.then(|r| r.unwrap())) } @@ -89,40 +83,31 @@ fn filter() { #[test] fn filter_map() { - assert_done(|| list().filter_map(|x| { - ok(if x % 2 == 0 { - Some(x + 10) - } else { - None - }) - }).collect(), Ok(vec![12])); + assert_done( + || list().filter_map(|x| ok(if x % 2 == 0 { Some(x + 10) } else { None })).collect(), + Ok(vec![12]), + ); } #[test] fn and_then() { assert_done(|| list().and_then(|a| Ok(a + 1)).collect(), Ok(vec![2, 3, 4])); - assert_done(|| list().and_then(|a| err::(a as u32)).collect::>(), - Err(1)); + assert_done(|| list().and_then(|a| err::(a as u32)).collect::>(), Err(1)); } #[test] fn then() { assert_done(|| list().then(|a| a.map(|e| e + 1)).collect(), Ok(vec![2, 3, 4])); - } #[test] fn or_else() { - assert_done(|| err_list().or_else(|a| { - ok::(a as i32) - }).collect(), Ok(vec![1, 2, 3])); + assert_done(|| err_list().or_else(|a| ok::(a as i32)).collect(), Ok(vec![1, 2, 3])); } #[test] fn flatten() { - assert_done(|| list().map(|_| list()).flatten().collect(), - Ok(vec![1, 2, 3, 1, 2, 3, 1, 2, 3])); - + assert_done(|| list().map(|_| list()).flatten().collect(), Ok(vec![1, 2, 3, 1, 2, 3, 1, 2, 3])); } #[test] @@ -132,9 +117,7 @@ fn skip() { #[test] fn skip_passes_errors_through() { - let mut s = block_on_stream( - iter(vec![Err(1), Err(2), Ok(3), Ok(4), Ok(5)]).skip(1) - ); + let mut s = block_on_stream(iter(vec![Err(1), Err(2), Ok(3), Ok(4), Ok(5)]).skip(1)); assert_eq!(s.next(), Some(Err(1))); assert_eq!(s.next(), Some(Err(2))); assert_eq!(s.next(), Some(Ok(4))); @@ -144,8 +127,7 @@ fn skip_passes_errors_through() { #[test] fn skip_while() { - assert_done(|| list().skip_while(|e| Ok(*e % 2 == 1)).collect(), - Ok(vec![2, 3])); + assert_done(|| list().skip_while(|e| Ok(*e % 2 == 1)).collect(), Ok(vec![2, 3])); } #[test] fn take() { @@ -154,8 +136,7 @@ fn take() { #[test] fn take_while() { - assert_done(|| list().take_while(|e| Ok(*e < 3)).collect(), - Ok(vec![1, 2])); + assert_done(|| list().take_while(|e| Ok(*e < 3)).collect(), Ok(vec![1, 2])); } #[test] @@ -193,9 +174,9 @@ fn buffered() { let (a, b) = oneshot::channel::(); let (c, d) = oneshot::channel::(); - tx.send(Box::new(b.recover(|_| panic!())) as Box + Send>) - .and_then(|tx| tx.send(Box::new(d.map_err(|_| panic!())))) - .forget(); + tx.send(Box::new(b.recover(|_| panic!())) as Box + Send>) + .and_then(|tx| tx.send(Box::new(d.map_err(|_| panic!())))) + .forget(); let mut rx = rx.buffered(2); sassert_empty(&mut rx); @@ -211,9 +192,9 @@ fn buffered() { let (a, b) = oneshot::channel::(); let (c, d) = oneshot::channel::(); - tx.send(Box::new(b.recover(|_| panic!())) as Box + Send>) - .and_then(|tx| tx.send(Box::new(d.map_err(|_| panic!())))) - .forget(); + tx.send(Box::new(b.recover(|_| panic!())) as Box + Send>) + .and_then(|tx| tx.send(Box::new(d.map_err(|_| panic!())))) + .forget(); let mut rx = rx.buffered(1); sassert_empty(&mut rx); @@ -233,8 +214,8 @@ fn unordered() { let (c, d) = oneshot::channel::(); tx.send(Box::new(b.recover(|_| panic!())) as Box + Send>) - .and_then(|tx| tx.send(Box::new(d.recover(|_| panic!())))) - .forget(); + .and_then(|tx| tx.send(Box::new(d.recover(|_| panic!())))) + .forget(); let mut rx = rx.buffer_unordered(2); sassert_empty(&mut rx); @@ -250,8 +231,8 @@ fn unordered() { let (c, d) = oneshot::channel::(); tx.send(Box::new(b.recover(|_| panic!())) as Box + Send>) - .and_then(|tx| tx.send(Box::new(d.recover(|_| panic!())))) - .forget(); + .and_then(|tx| tx.send(Box::new(d.recover(|_| panic!())))) + .forget(); // We don't even get to see `c` until `a` completes. let mut rx = rx.buffer_unordered(1); @@ -267,21 +248,17 @@ fn unordered() { #[test] fn zip() { - assert_done(|| list().zip(list()).collect(), - Ok(vec![(1, 1), (2, 2), (3, 3)])); - assert_done(|| list().zip(list().take(2)).collect(), - Ok(vec![(1, 1), (2, 2)])); - assert_done(|| list().take(2).zip(list()).collect(), - Ok(vec![(1, 1), (2, 2)])); + assert_done(|| list().zip(list()).collect(), Ok(vec![(1, 1), (2, 2), (3, 3)])); + assert_done(|| list().zip(list().take(2)).collect(), Ok(vec![(1, 1), (2, 2)])); + assert_done(|| list().take(2).zip(list()).collect(), Ok(vec![(1, 1), (2, 2)])); assert_done(|| err_list().zip(list()).collect::>(), Err(3)); - assert_done(|| list().zip(list().map(|x| x + 1)).collect(), - Ok(vec![(1, 2), (2, 3), (3, 4)])); + assert_done(|| list().zip(list().map(|x| x + 1)).collect(), Ok(vec![(1, 2), (2, 3), (3, 4)])); } #[test] fn peek() { struct Peek { - inner: Peekable + Send>> + inner: Peekable + Send>>, } impl Future for Peek { @@ -299,15 +276,12 @@ fn peek() { } } - block_on(Peek { - inner: list().peekable(), - }).unwrap() + block_on(Peek { inner: list().peekable() }).unwrap() } #[test] fn wait() { - assert_eq!(block_on_stream(list()).collect::, _>>(), - Ok(vec![1, 2, 3])); + assert_eq!(block_on_stream(list()).collect::, _>>(), Ok(vec![1, 2, 3])); } #[test] @@ -337,12 +311,13 @@ fn forward() { let v = block_on(iter_ok::<_, Never>(vec![2, 3]).forward(v)).unwrap().1; assert_eq!(v, vec![0, 1, 2, 3]); - assert_done(move || iter_ok::<_, Never>(vec![4, 5]).forward(v).map(|(_, s)| s), - Ok(vec![0, 1, 2, 3, 4, 5])); + assert_done( + move || iter_ok::<_, Never>(vec![4, 5]).forward(v).map(|(_, s)| s), + Ok(vec![0, 1, 2, 3, 4, 5]), + ); } #[test] -#[allow(deprecated)] fn concat() { let a = iter_ok::<_, ()>(vec![vec![1, 2, 3], vec![4, 5, 6], vec![7, 8, 9]]); assert_done(move || a.concat(), Ok(vec![1, 2, 3, 4, 5, 6, 7, 8, 9])); diff --git a/triagebot.toml b/triagebot.toml new file mode 100644 index 0000000000..e4e7a2d785 --- /dev/null +++ b/triagebot.toml @@ -0,0 +1,2 @@ +[assign] +warn_non_default_branch = true